diff --git a/DEPS b/DEPS
index df90ad5..169c18f 100644
--- a/DEPS
+++ b/DEPS
@@ -100,7 +100,7 @@
   # 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': '558cf7d7ae9f4dda0bb1cad06be5275b891c9bb7',
+  'v8_revision': 'df9ba269106f9660bbfdcf71e7b68b906e9339da',
   # 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.
@@ -968,7 +968,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '7c0541da63f571512c49758cbc0767117997a270',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '739351d4760f1d442caf23e8296c99ff290ff9bd', # commit position 21742
+    Var('webrtc_git') + '/src.git' + '@' + '5702736f7e46383dcb5b5386608a8ad3fbc551c5', # commit position 21742
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
diff --git a/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/WebMessageBoundaryInterface.java b/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/WebMessageBoundaryInterface.java
index 56038eb..dff79906 100644
--- a/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/WebMessageBoundaryInterface.java
+++ b/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/WebMessageBoundaryInterface.java
@@ -9,7 +9,7 @@
 /**
  * Boundary interface for WebMessage.
  */
-public interface WebMessageBoundaryInterface {
+public interface WebMessageBoundaryInterface extends FeatureFlagHolderBoundaryInterface {
     String getData();
 
     /* WebMessagePort */ InvocationHandler[] getPorts();
diff --git a/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/WebMessageCallbackBoundaryInterface.java b/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/WebMessageCallbackBoundaryInterface.java
index c13ed4f..acac2dc 100644
--- a/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/WebMessageCallbackBoundaryInterface.java
+++ b/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/WebMessageCallbackBoundaryInterface.java
@@ -9,7 +9,7 @@
 /**
  * Boundary interface for WebMessagePort.WebMessageCallback.
  */
-public interface WebMessageCallbackBoundaryInterface {
+public interface WebMessageCallbackBoundaryInterface extends FeatureFlagHolderBoundaryInterface {
     void onMessage(/* WebMessagePort */ InvocationHandler port,
             /* WebMessage */ InvocationHandler message);
 }
diff --git a/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/util/Features.java b/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/util/Features.java
index e214471..caba2bed 100644
--- a/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/util/Features.java
+++ b/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/util/Features.java
@@ -98,4 +98,24 @@
     // SafeBrowsingResponse.showInterstitial
     public static final String SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL =
             "SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL";
+
+    // WebMessagePortCompat.postMessage
+    public static final String WEB_MESSAGE_PORT_POST_MESSAGE = "WEB_MESSAGE_PORT_POST_MESSAGE";
+
+    // WebMessagePortCompat.close
+    public static final String WEB_MESSAGE_PORT_CLOSE = "WEB_MESSAGE_PORT_CLOSE";
+
+    // WebMessagePortCompat.setWebMessageCallback(WebMessageCallbackCompat)
+    // WebMessagePortCompat.setWebMessageCallback(WebMessageCallbackCompat, Handler)
+    public static final String WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK =
+            "WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK";
+
+    // WebViewCompat.createWebMessageChannel
+    public static final String CREATE_WEB_MESSAGE_CHANNEL = "CREATE_WEB_MESSAGE_CHANNEL";
+
+    // WebViewCompat.postWebMessage
+    public static final String POST_WEB_MESSAGE = "POST_WEB_MESSAGE";
+
+    // WebMessageCallbackCompat.onMessage
+    public static final String WEB_MESSAGE_CALLBACK_ON_MESSAGE = "WEB_MESSAGE_CALLBACK_ON_MESSAGE";
 }
diff --git a/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebMessageCallbackAdapter.java b/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebMessageCallbackAdapter.java
index dd26c0e..d307c3c0 100644
--- a/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebMessageCallbackAdapter.java
+++ b/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebMessageCallbackAdapter.java
@@ -8,6 +8,7 @@
 import org.chromium.support_lib_boundary.WebMessageCallbackBoundaryInterface;
 import org.chromium.support_lib_boundary.WebMessagePortBoundaryInterface;
 import org.chromium.support_lib_boundary.util.BoundaryInterfaceReflectionUtil;
+import org.chromium.support_lib_boundary.util.Features;
 
 /**
  * Adapter working on top of WebMessageCallbackBoundaryInterface to provide methods using boundary
@@ -22,7 +23,13 @@
 
     public void onMessage(
             WebMessagePortBoundaryInterface port, WebMessageBoundaryInterface message) {
-        mImpl.onMessage(BoundaryInterfaceReflectionUtil.createInvocationHandlerFor(port),
-                BoundaryInterfaceReflectionUtil.createInvocationHandlerFor(message));
+        // Ensure WebMessageCallbackCompat.onMessage() is supported by the support library before
+        // calling it.
+        String[] supportedFeatures = mImpl.getSupportedFeatures();
+        if (BoundaryInterfaceReflectionUtil.containsFeature(
+                    supportedFeatures, Features.WEB_MESSAGE_CALLBACK_ON_MESSAGE)) {
+            mImpl.onMessage(BoundaryInterfaceReflectionUtil.createInvocationHandlerFor(port),
+                    BoundaryInterfaceReflectionUtil.createInvocationHandlerFor(message));
+        }
     }
 }
diff --git a/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebMessagePortAdapter.java b/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebMessagePortAdapter.java
index 8d912633..c111f10 100644
--- a/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebMessagePortAdapter.java
+++ b/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebMessagePortAdapter.java
@@ -84,6 +84,12 @@
         public /* WebMessagePort */ InvocationHandler[] getPorts() {
             return SupportLibWebMessagePortAdapter.fromMessagePorts(mPorts);
         }
+
+        @Override
+        public String[] getSupportedFeatures() {
+            // getData() and getPorts() are not covered by feature flags.
+            return new String[0];
+        }
     }
 
     public static /* WebMessagePort */ InvocationHandler[] fromMessagePorts(
diff --git a/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebViewChromiumFactory.java b/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebViewChromiumFactory.java
index 22972b6..5a9b65a 100644
--- a/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebViewChromiumFactory.java
+++ b/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebViewChromiumFactory.java
@@ -54,7 +54,13 @@
                     Features.WEB_RESOURCE_ERROR_GET_CODE,
                     Features.SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY,
                     Features.SAFE_BROWSING_RESPONSE_PROCEED,
-                    Features.SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL
+                    Features.SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL,
+                    Features.WEB_MESSAGE_PORT_POST_MESSAGE,
+                    Features.WEB_MESSAGE_PORT_CLOSE,
+                    Features.WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK,
+                    Features.CREATE_WEB_MESSAGE_CHANNEL,
+                    Features.POST_WEB_MESSAGE,
+                    Features.WEB_MESSAGE_CALLBACK_ON_MESSAGE
             };
     // clang-format on
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java b/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java
index 57d3e79..15a2a91 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java
@@ -47,7 +47,7 @@
 import org.chromium.chrome.browser.tab.AuthenticatorNavigationInterceptor;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.webapps.GooglePlayWebApkInstallDelegate;
-import org.chromium.chrome.browser.webauth.Fido2ApiHandler;
+import org.chromium.chrome.browser.webauth.U2fApiHandler;
 import org.chromium.components.signin.AccountManagerDelegate;
 import org.chromium.components.signin.SystemAccountManagerDelegate;
 import org.chromium.policy.AppRestrictionsProvider;
@@ -337,9 +337,9 @@
     }
 
     /**
-     * @return a new {@link Fido2ApiHandler} instance.
+     * @return a new {@link U2fApiHandler} instance.
      */
-    public Fido2ApiHandler createFido2ApiHandler() {
-        return new Fido2ApiHandler();
+    public U2fApiHandler createU2fApiHandler() {
+        return new U2fApiHandler();
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromeBaseCheckBoxPreference.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromeBaseCheckBoxPreference.java
index 70d76531..7b9e1151 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromeBaseCheckBoxPreference.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromeBaseCheckBoxPreference.java
@@ -29,19 +29,19 @@
      */
     public void setManagedPreferenceDelegate(ManagedPreferenceDelegate delegate) {
         mManagedPrefDelegate = delegate;
-        if (mManagedPrefDelegate != null) mManagedPrefDelegate.initPreference(this);
+        ManagedPreferencesUtils.initPreference(mManagedPrefDelegate, this);
     }
 
     @Override
     protected void onBindView(View view) {
         super.onBindView(view);
         ((TextView) view.findViewById(android.R.id.title)).setSingleLine(false);
-        if (mManagedPrefDelegate != null) mManagedPrefDelegate.onBindViewToPreference(this, view);
+        ManagedPreferencesUtils.onBindViewToPreference(mManagedPrefDelegate, this, view);
     }
 
     @Override
     protected void onClick() {
-        if (mManagedPrefDelegate != null && mManagedPrefDelegate.onClickPreference(this)) return;
+        if (ManagedPreferencesUtils.onClickPreference(mManagedPrefDelegate, this)) return;
         super.onClick();
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromeBaseListPreference.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromeBaseListPreference.java
index 7ef2b2af..864c539 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromeBaseListPreference.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromeBaseListPreference.java
@@ -31,19 +31,19 @@
      */
     public void setManagedPreferenceDelegate(ManagedPreferenceDelegate delegate) {
         mManagedPrefDelegate = delegate;
-        if (mManagedPrefDelegate != null) mManagedPrefDelegate.initPreference(this);
+        ManagedPreferencesUtils.initPreference(mManagedPrefDelegate, this);
     }
 
     @Override
     protected void onBindView(View view) {
         super.onBindView(view);
         ((TextView) view.findViewById(android.R.id.title)).setSingleLine(false);
-        if (mManagedPrefDelegate != null) mManagedPrefDelegate.onBindViewToPreference(this, view);
+        ManagedPreferencesUtils.onBindViewToPreference(mManagedPrefDelegate, this, view);
     }
 
     @Override
     protected void onClick() {
-        if (mManagedPrefDelegate != null && mManagedPrefDelegate.onClickPreference(this)) return;
+        if (ManagedPreferencesUtils.onClickPreference(mManagedPrefDelegate, this)) return;
         super.onClick();
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromeBasePreference.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromeBasePreference.java
index d84631d8..400970b1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromeBasePreference.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromeBasePreference.java
@@ -41,19 +41,19 @@
      */
     public void setManagedPreferenceDelegate(ManagedPreferenceDelegate delegate) {
         mManagedPrefDelegate = delegate;
-        if (mManagedPrefDelegate != null) mManagedPrefDelegate.initPreference(this);
+        ManagedPreferencesUtils.initPreference(mManagedPrefDelegate, this);
     }
 
     @Override
     protected void onBindView(View view) {
         super.onBindView(view);
         ((TextView) view.findViewById(android.R.id.title)).setSingleLine(false);
-        if (mManagedPrefDelegate != null) mManagedPrefDelegate.onBindViewToPreference(this, view);
+        ManagedPreferencesUtils.onBindViewToPreference(mManagedPrefDelegate, this, view);
     }
 
     @Override
     protected void onClick() {
-        if (mManagedPrefDelegate != null && mManagedPrefDelegate.onClickPreference(this)) return;
+        if (ManagedPreferencesUtils.onClickPreference(mManagedPrefDelegate, this)) return;
         super.onClick();
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromeSwitchPreference.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromeSwitchPreference.java
index 087eba3..43f861d9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromeSwitchPreference.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromeSwitchPreference.java
@@ -47,7 +47,7 @@
      */
     public void setManagedPreferenceDelegate(ManagedPreferenceDelegate delegate) {
         mManagedPrefDelegate = delegate;
-        if (mManagedPrefDelegate != null) mManagedPrefDelegate.initPreference(this);
+        ManagedPreferencesUtils.initPreference(mManagedPrefDelegate, this);
     }
 
     /**
@@ -90,12 +90,12 @@
             summary.setVisibility(View.GONE);
         }
 
-        if (mManagedPrefDelegate != null) mManagedPrefDelegate.onBindViewToPreference(this, view);
+        ManagedPreferencesUtils.onBindViewToPreference(mManagedPrefDelegate, this, view);
     }
 
     @Override
     protected void onClick() {
-        if (mManagedPrefDelegate != null && mManagedPrefDelegate.onClickPreference(this)) return;
+        if (ManagedPreferencesUtils.onClickPreference(mManagedPrefDelegate, this)) return;
         super.onClick();
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ManagedPreferenceDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ManagedPreferenceDelegate.java
index ffb00f0..0b6f31837 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ManagedPreferenceDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ManagedPreferenceDelegate.java
@@ -5,10 +5,6 @@
 package org.chromium.chrome.browser.preferences;
 
 import android.preference.Preference;
-import android.view.View;
-
-import org.chromium.chrome.R;
-import org.chromium.chrome.browser.util.ViewUtils;
 
 /**
  * A delegate that determines whether a Preference is managed by enterprise policy. This is used
@@ -56,72 +52,7 @@
      */
     // TODO(bauerb): Rename to isPreferenceClickDisabled.
     default boolean isPreferenceClickDisabledByPolicy(Preference preference) {
-        return isPreferenceControlledByPolicy(preference) ||
-                isPreferenceControlledByCustodian(preference);
-    }
-
-    /**
-     * Initializes the Preference based on the state of any policies that may affect it,
-     * e.g. by showing a managed icon or disabling clicks on the preference.
-     *
-     * This should be called once, before the preference is displayed.
-     */
-    default void initPreference(Preference preference) {
-        if (isPreferenceControlledByPolicy(preference)) {
-            preference.setIcon(ManagedPreferencesUtils.getManagedByEnterpriseIconId());
-        } else if (isPreferenceControlledByCustodian(preference)) {
-            preference.setIcon(R.drawable.ic_account_child_grey600_36dp);
-        }
-
-        if (isPreferenceClickDisabledByPolicy(preference)) {
-            // Disable the views and prevent the Preference from mucking with the enabled state.
-            preference.setShouldDisableView(false);
-
-            // Prevent default click behavior.
-            preference.setFragment(null);
-            preference.setIntent(null);
-            preference.setOnPreferenceClickListener(null);
-        }
-    }
-
-    /**
-     * Disables the Preference's views if the preference is not clickable.
-     *
-     * Note: this disables the View instead of disabling the Preference, so that the Preference
-     * still receives click events, which will trigger a "Managed by your administrator" toast.
-     *
-     * This should be called from the Preference's onBindView() method.
-     *
-     * @param preference The Preference that owns the view
-     * @param view The View that was bound to the Preference
-     */
-    default void onBindViewToPreference(Preference preference, View view) {
-        if (isPreferenceClickDisabledByPolicy(preference)) {
-            ViewUtils.setEnabledRecursive(view, false);
-        }
-    }
-
-    /**
-     * Intercepts the click event if the given Preference is managed and shows a toast in that case.
-     *
-     * This should be called from the Preference's onClick() method.
-     *
-     * @param preference The Preference that was clicked.
-     * @return true if the click event was handled by this helper and shouldn't be further
-     *         propagated; false otherwise.
-     */
-    default boolean onClickPreference(Preference preference) {
-        if (!isPreferenceClickDisabledByPolicy(preference)) return false;
-
-        if (isPreferenceControlledByPolicy(preference)) {
-            ManagedPreferencesUtils.showManagedByAdministratorToast(preference.getContext());
-        } else if (isPreferenceControlledByCustodian(preference)) {
-            ManagedPreferencesUtils.showManagedByParentToast(preference.getContext());
-        } else {
-            // If the preference is disabled, it should be either because it's managed by enterprise
-            // policy or by the custodian.
-            assert false;
-        }
-        return true;
+        return isPreferenceControlledByPolicy(preference)
+                || isPreferenceControlledByCustodian(preference);
     }
 }
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ManagedPreferencesUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ManagedPreferencesUtils.java
index 657459d6..923db29 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ManagedPreferencesUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ManagedPreferencesUtils.java
@@ -5,8 +5,12 @@
 package org.chromium.chrome.browser.preferences;
 
 import android.content.Context;
+import android.preference.Preference;
+import android.support.annotation.Nullable;
+import android.view.View;
 
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.util.ViewUtils;
 import org.chromium.ui.widget.Toast;
 
 /**
@@ -46,4 +50,84 @@
     public static int getManagedByEnterpriseIconId() {
         return R.drawable.controlled_setting_mandatory;
     }
+
+    /**
+     * Initializes the Preference based on the state of any policies that may affect it,
+     * e.g. by showing a managed icon or disabling clicks on the preference.
+     *
+     * This should be called once, before the preference is displayed.
+     *
+     * @param delegate The delegate that controls whether the preference is managed. May be null,
+     *         then this method does nothing.
+     * @param preference The Preference that is being initialized
+     */
+    public static void initPreference(
+            @Nullable ManagedPreferenceDelegate delegate, Preference preference) {
+        if (delegate == null) return;
+
+        if (delegate.isPreferenceControlledByPolicy(preference)) {
+            preference.setIcon(getManagedByEnterpriseIconId());
+        } else if (delegate.isPreferenceControlledByCustodian(preference)) {
+            preference.setIcon(R.drawable.ic_account_child_grey600_36dp);
+        }
+
+        if (delegate.isPreferenceClickDisabledByPolicy(preference)) {
+            // Disable the views and prevent the Preference from mucking with the enabled state.
+            preference.setShouldDisableView(false);
+
+            // Prevent default click behavior.
+            preference.setFragment(null);
+            preference.setIntent(null);
+            preference.setOnPreferenceClickListener(null);
+        }
+    }
+
+    /**
+     * Disables the Preference's views if the preference is not clickable.
+     *
+     * Note: this disables the View instead of disabling the Preference, so that the Preference
+     * still receives click events, which will trigger a "Managed by your administrator" toast.
+     *
+     * This should be called from the Preference's onBindView() method.
+     *
+     * @param delegate The delegate that controls whether the preference is managed. May be null,
+     *         then this method does nothing.
+     * @param preference The Preference that owns the view
+     * @param view The View that was bound to the Preference
+     */
+    public static void onBindViewToPreference(
+            @Nullable ManagedPreferenceDelegate delegate, Preference preference, View view) {
+        if (delegate != null && delegate.isPreferenceClickDisabledByPolicy(preference)) {
+            ViewUtils.setEnabledRecursive(view, false);
+        }
+    }
+
+    /**
+     * Intercepts the click event if the given Preference is managed and shows a toast in that case.
+     *
+     * This should be called from the Preference's onClick() method.
+     *
+     * @param delegate The delegate that controls whether the preference is managed. May be null,
+     *         then this method does nothing and returns false.
+     * @param preference The Preference that was clicked.
+     * @return true if the click event was handled by this helper and shouldn't be further
+     *         propagated; false otherwise.
+     */
+    public static boolean onClickPreference(
+            @Nullable ManagedPreferenceDelegate delegate, Preference preference) {
+        if (delegate == null || !delegate.isPreferenceClickDisabledByPolicy(preference)) {
+            return false;
+        }
+
+        if (delegate.isPreferenceControlledByPolicy(preference)) {
+            showManagedByAdministratorToast(preference.getContext());
+        } else if (delegate.isPreferenceControlledByCustodian(preference)) {
+            showManagedByParentToast(preference.getContext());
+        } else {
+            // If the preference is disabled, it should be either because it's managed by enterprise
+            // policy or by the custodian.
+            assert false;
+        }
+        return true;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webauth/Fido2ApiHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/webauth/Fido2ApiHandler.java
deleted file mode 100644
index aec6802b..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/webauth/Fido2ApiHandler.java
+++ /dev/null
@@ -1,35 +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.webauth;
-
-import org.chromium.base.ThreadUtils;
-import org.chromium.chrome.browser.AppHooks;
-import org.chromium.webauth.mojom.PublicKeyCredentialCreationOptions;
-import org.chromium.webauth.mojom.PublicKeyCredentialRequestOptions;
-
-/**
- * Android implementation of the Authenticator service defined in
- * components/webauth/authenticator.mojom.
- */
-public class Fido2ApiHandler {
-    private static Fido2ApiHandler sInstance;
-
-    /**
-     * @return The Fido2ApiHandler for use during the lifetime of the browser process.
-     */
-    public static Fido2ApiHandler getInstance() {
-        ThreadUtils.checkUiThread();
-        if (sInstance == null) {
-            sInstance = AppHooks.get().createFido2ApiHandler();
-        }
-        return sInstance;
-    }
-
-    protected void makeCredential(
-            PublicKeyCredentialCreationOptions options, HandlerResponseCallback callback) {}
-
-    protected void getAssertion(
-            PublicKeyCredentialRequestOptions options, HandlerResponseCallback callback) {}
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webauth/U2fApiHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/webauth/U2fApiHandler.java
index b58b0b7..d9447fd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webauth/U2fApiHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webauth/U2fApiHandler.java
@@ -27,9 +27,9 @@
         return sInstance;
     }
 
-    protected void makeCredential(
+    protected void u2fRegister(
             PublicKeyCredentialCreationOptions options, HandlerResponseCallback callback) {}
 
-    protected void getAssertion(
+    protected void u2fSign(
             PublicKeyCredentialRequestOptions options, HandlerResponseCallback callback) {}
 }
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 984990d8..4b6c2bf2 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -1404,7 +1404,7 @@
   "java/src/org/chromium/chrome/browser/webauth/AuthenticatorFactory.java",
   "java/src/org/chromium/chrome/browser/webauth/AuthenticatorImpl.java",
   "java/src/org/chromium/chrome/browser/webauth/HandlerResponseCallback.java",
-  "java/src/org/chromium/chrome/browser/webauth/Fido2ApiHandler.java",
+  "java/src/org/chromium/chrome/browser/webauth/U2fApiHandler.java",
   "java/src/org/chromium/chrome/browser/webshare/ShareServiceImpl.java",
   "java/src/org/chromium/chrome/browser/webshare/ShareServiceImplementationFactory.java",
   "java/src/org/chromium/chrome/browser/widget/AlertDialogEditText.java",
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index aac3d0f..547979b 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-68.0.3431.0_rc-r1.afdo.bz2
\ No newline at end of file
+chromeos-chrome-amd64-68.0.3431.0_rc-r2.afdo.bz2
\ No newline at end of file
diff --git a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
index 9055636..fdd51be 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
@@ -16,14 +16,30 @@
 
 namespace file_manager {
 
-// FileManagerBrowserTest parameters: first controls IN_GUEST_MODE or not, the
-// second is the JS test case name.
-typedef std::tuple<GuestMode, const char*> TestParameter;
+// TestCase: FileManagerBrowserTest parameters.
+struct TestCase {
+  TestCase(const char* name, GuestMode mode)
+    : test_name(name), guest_mode(mode) {}
 
-// FileManager browser test class.
-class FileManagerBrowserTest :
-      public FileManagerBrowserTestBase,
-      public ::testing::WithParamInterface<TestParameter> {
+  explicit TestCase(const char* name)
+    : test_name(name) {}
+
+  const char* GetTestName() const {
+    CHECK(test_name) << "FATAL: no test name";
+    return test_name;
+  }
+
+  GuestMode GetGuestMode() const {
+    return guest_mode;
+  }
+
+  const char* test_name = nullptr;
+  GuestMode guest_mode = NOT_IN_GUEST_MODE;
+};
+
+// FileManager browser test.
+class FileManagerBrowserTest : public FileManagerBrowserTestBase,
+                               public ::testing::WithParamInterface<TestCase> {
  public:
   FileManagerBrowserTest() = default;
 
@@ -38,11 +54,11 @@
   }
 
   GuestMode GetGuestMode() const override {
-    return std::get<0>(GetParam());
+    return GetParam().GetGuestMode();
   }
 
   const char* GetTestCaseName() const override {
-    return std::get<1>(GetParam());
+    return GetParam().GetTestName();
   }
 
   const char* GetTestExtensionManifestName() const override {
@@ -98,20 +114,20 @@
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     FileDisplay,
     FileManagerBrowserTest,
-    ::testing::Values(TestParameter(NOT_IN_GUEST_MODE, "fileDisplayDownloads"),
-                      TestParameter(IN_GUEST_MODE, "fileDisplayDownloads"),
-                      TestParameter(NOT_IN_GUEST_MODE, "fileDisplayDrive"),
-                      TestParameter(NOT_IN_GUEST_MODE, "fileDisplayMtp"),
-                      TestParameter(NOT_IN_GUEST_MODE, "fileSearch"),
-                      TestParameter(NOT_IN_GUEST_MODE, "fileSearchCaseInsensitive"),
-                      TestParameter(NOT_IN_GUEST_MODE, "fileSearchNotFound")));
+    ::testing::Values(TestCase("fileDisplayDownloads"),
+                      TestCase("fileDisplayDownloads", IN_GUEST_MODE),
+                      TestCase("fileDisplayDrive"),
+                      TestCase("fileDisplayMtp"),
+                      TestCase("fileSearch"),
+                      TestCase("fileSearchCaseInsensitive"),
+                      TestCase("fileSearchNotFound")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     OpenVideoFiles,
     FileManagerBrowserTest,
-    ::testing::Values(TestParameter(IN_GUEST_MODE, "videoOpenDownloads"),
-                      TestParameter(NOT_IN_GUEST_MODE, "videoOpenDownloads"),
-                      TestParameter(NOT_IN_GUEST_MODE, "videoOpenDrive")));
+    ::testing::Values(TestCase("videoOpenDownloads", IN_GUEST_MODE),
+                      TestCase("videoOpenDownloads"),
+                      TestCase("videoOpenDrive")));
 
 // TIMEOUT PASS on MSAN, https://crbug.com/836254
 #if defined(MEMORY_SANITIZER)
@@ -123,17 +139,16 @@
     MAYBE_OpenAudioFiles,
     FileManagerBrowserTest,
     ::testing::Values(
-        TestParameter(IN_GUEST_MODE, "audioOpenDownloads"),
-        TestParameter(NOT_IN_GUEST_MODE, "audioOpenDownloads"),
-        TestParameter(NOT_IN_GUEST_MODE, "audioOpenDrive"),
-        TestParameter(NOT_IN_GUEST_MODE, "audioAutoAdvanceDrive"),
-        TestParameter(NOT_IN_GUEST_MODE, "audioRepeatAllModeSingleFileDrive"),
-        TestParameter(NOT_IN_GUEST_MODE, "audioNoRepeatModeSingleFileDrive"),
-        TestParameter(NOT_IN_GUEST_MODE, "audioRepeatOneModeSingleFileDrive"),
-        TestParameter(NOT_IN_GUEST_MODE, "audioRepeatAllModeMultipleFileDrive"),
-        TestParameter(NOT_IN_GUEST_MODE, "audioNoRepeatModeMultipleFileDrive"),
-        TestParameter(NOT_IN_GUEST_MODE,
-                      "audioRepeatOneModeMultipleFileDrive")));
+        TestCase("audioOpenDownloads", IN_GUEST_MODE),
+        TestCase("audioOpenDownloads"),
+        TestCase("audioOpenDrive"),
+        TestCase("audioAutoAdvanceDrive"),
+        TestCase("audioRepeatAllModeSingleFileDrive"),
+        TestCase("audioNoRepeatModeSingleFileDrive"),
+        TestCase("audioRepeatOneModeSingleFileDrive"),
+        TestCase("audioRepeatAllModeMultipleFileDrive"),
+        TestCase("audioNoRepeatModeMultipleFileDrive"),
+        TestCase("audioRepeatOneModeMultipleFileDrive")));
 
 // Fails on the MSAN bots, https://crbug.com/837551
 #if defined(MEMORY_SANITIZER)
@@ -144,263 +159,258 @@
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     MAYBE_OpenImageFiles,
     FileManagerBrowserTest,
-    ::testing::Values(TestParameter(IN_GUEST_MODE, "imageOpenDownloads"),
-                      TestParameter(NOT_IN_GUEST_MODE, "imageOpenDownloads"),
-                      TestParameter(NOT_IN_GUEST_MODE, "imageOpenDrive")));
+    ::testing::Values(TestCase("imageOpenDownloads", IN_GUEST_MODE),
+                      TestCase("imageOpenDownloads"),
+                      TestCase("imageOpenDrive")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     CreateNewFolder,
     FileManagerBrowserTest,
     ::testing::Values(
-        TestParameter(NOT_IN_GUEST_MODE, "selectCreateFolderDownloads"),
-        TestParameter(IN_GUEST_MODE, "createFolderDownloads"),
-        TestParameter(NOT_IN_GUEST_MODE, "createFolderDownloads"),
-        TestParameter(NOT_IN_GUEST_MODE, "createFolderDrive")));
+        TestCase("selectCreateFolderDownloads"),
+        TestCase("createFolderDownloads", IN_GUEST_MODE),
+        TestCase("createFolderDownloads"),
+        TestCase("createFolderDrive")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     KeyboardOperations,
     FileManagerBrowserTest,
     ::testing::Values(
-        TestParameter(IN_GUEST_MODE, "keyboardDeleteDownloads"),
-        TestParameter(NOT_IN_GUEST_MODE, "keyboardDeleteDownloads"),
-        TestParameter(NOT_IN_GUEST_MODE, "keyboardDeleteDrive"),
-        TestParameter(IN_GUEST_MODE, "keyboardCopyDownloads"),
-        TestParameter(NOT_IN_GUEST_MODE, "keyboardCopyDownloads"),
-        TestParameter(NOT_IN_GUEST_MODE, "keyboardCopyDrive"),
-        TestParameter(IN_GUEST_MODE, "renameFileDownloads"),
-        TestParameter(NOT_IN_GUEST_MODE, "renameFileDownloads"),
-        TestParameter(NOT_IN_GUEST_MODE, "renameFileDrive"),
-        TestParameter(IN_GUEST_MODE, "renameNewFolderDownloads"),
-        TestParameter(NOT_IN_GUEST_MODE, "renameNewFolderDownloads"),
-        TestParameter(NOT_IN_GUEST_MODE, "renameNewFolderDrive")));
+        TestCase("keyboardDeleteDownloads", IN_GUEST_MODE),
+        TestCase("keyboardDeleteDownloads"),
+        TestCase("keyboardDeleteDrive"),
+        TestCase("keyboardCopyDownloads", IN_GUEST_MODE),
+        TestCase("keyboardCopyDownloads"),
+        TestCase("keyboardCopyDrive"),
+        TestCase("renameFileDownloads", IN_GUEST_MODE),
+        TestCase("renameFileDownloads"),
+        TestCase("renameFileDrive"),
+        TestCase("renameNewFolderDownloads", IN_GUEST_MODE),
+        TestCase("renameNewFolderDownloads"),
+        TestCase("renameNewFolderDrive")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     Delete,
     FileManagerBrowserTest,
     ::testing::Values(
-        TestParameter(NOT_IN_GUEST_MODE, "deleteMenuItemNoEntrySelected"),
-        TestParameter(NOT_IN_GUEST_MODE, "deleteEntryWithToolbar")));
+        TestCase("deleteMenuItemNoEntrySelected"),
+        TestCase("deleteEntryWithToolbar")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     QuickView,
     FileManagerBrowserTest,
-    ::testing::Values(TestParameter(NOT_IN_GUEST_MODE, "openQuickView"),
-                      TestParameter(NOT_IN_GUEST_MODE, "closeQuickView")));
+    ::testing::Values(TestCase("openQuickView"),
+                      TestCase("closeQuickView")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     DirectoryTreeContextMenu,
     FileManagerBrowserTest,
     ::testing::Values(
-        TestParameter(NOT_IN_GUEST_MODE, "dirCopyWithContextMenu"),
-        TestParameter(IN_GUEST_MODE, "dirCopyWithContextMenu"),
-        TestParameter(NOT_IN_GUEST_MODE, "dirCopyWithKeyboard"),
-        TestParameter(IN_GUEST_MODE, "dirCopyWithKeyboard"),
-        TestParameter(NOT_IN_GUEST_MODE, "dirCopyWithoutChangingCurrent"),
-        TestParameter(NOT_IN_GUEST_MODE, "dirCutWithContextMenu"),
-        TestParameter(IN_GUEST_MODE, "dirCutWithContextMenu"),
-        TestParameter(NOT_IN_GUEST_MODE, "dirCutWithKeyboard"),
-        TestParameter(IN_GUEST_MODE, "dirCutWithKeyboard"),
-        TestParameter(NOT_IN_GUEST_MODE, "dirPasteWithoutChangingCurrent"),
-        TestParameter(NOT_IN_GUEST_MODE, "dirPasteWithContextMenu"),
-        TestParameter(IN_GUEST_MODE, "dirPasteWithContextMenu"),
-        TestParameter(NOT_IN_GUEST_MODE, "dirPasteWithoutChangingCurrent"),
-        TestParameter(NOT_IN_GUEST_MODE, "dirRenameWithContextMenu"),
-        TestParameter(IN_GUEST_MODE, "dirRenameWithContextMenu"),
-        TestParameter(NOT_IN_GUEST_MODE, "dirRenameWithKeyboard"),
-        TestParameter(IN_GUEST_MODE, "dirRenameWithKeyboard"),
-        TestParameter(NOT_IN_GUEST_MODE, "dirRenameWithoutChangingCurrent"),
-        TestParameter(NOT_IN_GUEST_MODE, "dirRenameToEmptyString"),
-        TestParameter(IN_GUEST_MODE, "dirRenameToEmptyString"),
-        TestParameter(NOT_IN_GUEST_MODE, "dirRenameToExisting"),
-        TestParameter(IN_GUEST_MODE, "dirRenameToExisting"),
-        TestParameter(NOT_IN_GUEST_MODE, "dirCreateWithContextMenu"),
-        TestParameter(NOT_IN_GUEST_MODE, "dirCreateWithKeyboard"),
-        TestParameter(NOT_IN_GUEST_MODE, "dirCreateWithoutChangingCurrent")));
+        TestCase("dirCopyWithContextMenu"),
+        TestCase("dirCopyWithContextMenu", IN_GUEST_MODE),
+        TestCase("dirCopyWithKeyboard"),
+        TestCase("dirCopyWithKeyboard", IN_GUEST_MODE),
+        TestCase("dirCopyWithoutChangingCurrent"),
+        TestCase("dirCutWithContextMenu"),
+        TestCase("dirCutWithContextMenu", IN_GUEST_MODE),
+        TestCase("dirCutWithKeyboard"),
+        TestCase("dirCutWithKeyboard", IN_GUEST_MODE),
+        TestCase("dirPasteWithoutChangingCurrent"),
+        TestCase("dirPasteWithContextMenu"),
+        TestCase("dirPasteWithContextMenu", IN_GUEST_MODE),
+        TestCase("dirPasteWithoutChangingCurrent"),
+        TestCase("dirRenameWithContextMenu"),
+        TestCase("dirRenameWithContextMenu", IN_GUEST_MODE),
+        TestCase("dirRenameWithKeyboard"),
+        TestCase("dirRenameWithKeyboard", IN_GUEST_MODE),
+        TestCase("dirRenameWithoutChangingCurrent"),
+        TestCase("dirRenameToEmptyString"),
+        TestCase("dirRenameToEmptyString", IN_GUEST_MODE),
+        TestCase("dirRenameToExisting"),
+        TestCase("dirRenameToExisting", IN_GUEST_MODE),
+        TestCase("dirCreateWithContextMenu"),
+        TestCase("dirCreateWithKeyboard"),
+        TestCase("dirCreateWithoutChangingCurrent")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     DriveSpecific,
     FileManagerBrowserTest,
     ::testing::Values(
-        TestParameter(NOT_IN_GUEST_MODE, "driveOpenSidebarOffline"),
-        TestParameter(NOT_IN_GUEST_MODE, "driveOpenSidebarSharedWithMe"),
-        TestParameter(NOT_IN_GUEST_MODE, "driveAutoCompleteQuery"),
-        TestParameter(NOT_IN_GUEST_MODE, "drivePinFileMobileNetwork"),
-        TestParameter(NOT_IN_GUEST_MODE, "driveClickFirstSearchResult"),
-        TestParameter(NOT_IN_GUEST_MODE, "drivePressEnterToSearch")));
+        TestCase("driveOpenSidebarOffline"),
+        TestCase("driveOpenSidebarSharedWithMe"),
+        TestCase("driveAutoCompleteQuery"),
+        TestCase("drivePinFileMobileNetwork"),
+        TestCase("driveClickFirstSearchResult"),
+        TestCase("drivePressEnterToSearch")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     Transfer,
     FileManagerBrowserTest,
     ::testing::Values(
-        TestParameter(NOT_IN_GUEST_MODE, "transferFromDriveToDownloads"),
-        TestParameter(NOT_IN_GUEST_MODE, "transferFromDownloadsToDrive"),
-        TestParameter(NOT_IN_GUEST_MODE, "transferFromSharedToDownloads"),
-        TestParameter(NOT_IN_GUEST_MODE, "transferFromSharedToDrive"),
-        TestParameter(NOT_IN_GUEST_MODE, "transferFromOfflineToDownloads"),
-        TestParameter(NOT_IN_GUEST_MODE, "transferFromOfflineToDrive")));
+        TestCase("transferFromDriveToDownloads"),
+        TestCase("transferFromDownloadsToDrive"),
+        TestCase("transferFromSharedToDownloads"),
+        TestCase("transferFromSharedToDrive"),
+        TestCase("transferFromOfflineToDownloads"),
+        TestCase("transferFromOfflineToDrive")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     RestorePrefs,
     FileManagerBrowserTest,
-    ::testing::Values(TestParameter(IN_GUEST_MODE, "restoreSortColumn"),
-                      TestParameter(NOT_IN_GUEST_MODE, "restoreSortColumn"),
-                      TestParameter(IN_GUEST_MODE, "restoreCurrentView"),
-                      TestParameter(NOT_IN_GUEST_MODE, "restoreCurrentView")));
+    ::testing::Values(TestCase("restoreSortColumn", IN_GUEST_MODE),
+                      TestCase("restoreSortColumn"),
+                      TestCase("restoreCurrentView", IN_GUEST_MODE),
+                      TestCase("restoreCurrentView")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     ShareDialog,
     FileManagerBrowserTest,
-    ::testing::Values(TestParameter(NOT_IN_GUEST_MODE, "shareFile"),
-                      TestParameter(NOT_IN_GUEST_MODE, "shareDirectory")));
+    ::testing::Values(TestCase("shareFile"),
+                      TestCase("shareDirectory")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     RestoreGeometry,
     FileManagerBrowserTest,
-    ::testing::Values(TestParameter(NOT_IN_GUEST_MODE, "restoreGeometry"),
-                      TestParameter(IN_GUEST_MODE, "restoreGeometry"),
-                      TestParameter(NOT_IN_GUEST_MODE,
-                                    "restoreGeometryMaximized")));
+    ::testing::Values(TestCase("restoreGeometry"),
+                      TestCase("restoreGeometry", IN_GUEST_MODE),
+                      TestCase("restoreGeometryMaximized")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     Traverse,
     FileManagerBrowserTest,
-    ::testing::Values(TestParameter(IN_GUEST_MODE, "traverseDownloads"),
-                      TestParameter(NOT_IN_GUEST_MODE, "traverseDownloads"),
-                      TestParameter(NOT_IN_GUEST_MODE, "traverseDrive")));
+    ::testing::Values(TestCase("traverseDownloads", IN_GUEST_MODE),
+                      TestCase("traverseDownloads"),
+                      TestCase("traverseDrive")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     SuggestAppDialog,
     FileManagerBrowserTest,
-    ::testing::Values(TestParameter(NOT_IN_GUEST_MODE, "suggestAppDialog")));
+    ::testing::Values(TestCase("suggestAppDialog")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     ExecuteDefaultTaskOnDownloads,
     FileManagerBrowserTest,
     ::testing::Values(
-        TestParameter(NOT_IN_GUEST_MODE, "executeDefaultTaskDownloads"),
-        TestParameter(IN_GUEST_MODE, "executeDefaultTaskDownloads")));
+        TestCase("executeDefaultTaskDownloads"),
+        TestCase("executeDefaultTaskDownloads",IN_GUEST_MODE)));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     ExecuteDefaultTaskOnDrive,
     FileManagerBrowserTest,
-    ::testing::Values(TestParameter(NOT_IN_GUEST_MODE,
-                                    "executeDefaultTaskDrive")));
+    ::testing::Values(TestCase("executeDefaultTaskDrive")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     DefaultTaskDialog,
     FileManagerBrowserTest,
     ::testing::Values(
-        TestParameter(NOT_IN_GUEST_MODE, "defaultTaskDialogDownloads"),
-        TestParameter(IN_GUEST_MODE, "defaultTaskDialogDownloads"),
-        TestParameter(NOT_IN_GUEST_MODE, "defaultTaskDialogDrive")));
+        TestCase("defaultTaskDialogDownloads"),
+        TestCase("defaultTaskDialogDownloads", IN_GUEST_MODE),
+        TestCase("defaultTaskDialogDrive")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     GenericTask,
     FileManagerBrowserTest,
     ::testing::Values(
-        TestParameter(NOT_IN_GUEST_MODE, "genericTaskIsNotExecuted"),
-        TestParameter(NOT_IN_GUEST_MODE, "genericTaskAndNonGenericTask")));
+        TestCase("genericTaskIsNotExecuted"),
+        TestCase("genericTaskAndNonGenericTask")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     FolderShortcuts,
     FileManagerBrowserTest,
     ::testing::Values(
-        TestParameter(NOT_IN_GUEST_MODE, "traverseFolderShortcuts"),
-        TestParameter(NOT_IN_GUEST_MODE, "addRemoveFolderShortcuts")));
+        TestCase("traverseFolderShortcuts"),
+        TestCase("addRemoveFolderShortcuts")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     SortColumns,
     FileManagerBrowserTest,
-    ::testing::Values(TestParameter(NOT_IN_GUEST_MODE, "sortColumns"),
-                      TestParameter(IN_GUEST_MODE, "sortColumns")));
+    ::testing::Values(TestCase("sortColumns"),
+                      TestCase("sortColumns",IN_GUEST_MODE)));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     TabIndex,
     FileManagerBrowserTestWithLegacyEventDispatch,
     ::testing::Values(
-        TestParameter(NOT_IN_GUEST_MODE, "tabindexSearchBoxFocus")));
+        TestCase("tabindexSearchBoxFocus")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     TabindexFocus,
     FileManagerBrowserTestWithLegacyEventDispatch,
-    ::testing::Values(TestParameter(NOT_IN_GUEST_MODE, "tabindexFocus")));
+    ::testing::Values(TestCase("tabindexFocus")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     TabindexFocusDownloads,
     FileManagerBrowserTestWithLegacyEventDispatch,
-    ::testing::Values(TestParameter(NOT_IN_GUEST_MODE,
-                                    "tabindexFocusDownloads"),
-                      TestParameter(IN_GUEST_MODE, "tabindexFocusDownloads")));
+    ::testing::Values(TestCase("tabindexFocusDownloads"),
+                      TestCase("tabindexFocusDownloads", IN_GUEST_MODE)));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     TabindexFocusDirectorySelected,
     FileManagerBrowserTestWithLegacyEventDispatch,
-    ::testing::Values(TestParameter(NOT_IN_GUEST_MODE,
-                                    "tabindexFocusDirectorySelected")));
+    ::testing::Values(TestCase("tabindexFocusDirectorySelected")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     TabindexOpenDialog,
     FileManagerBrowserTest,
     ::testing::Values(
-        TestParameter(NOT_IN_GUEST_MODE, "tabindexOpenDialogDrive"),
-        TestParameter(NOT_IN_GUEST_MODE, "tabindexOpenDialogDownloads"),
-        TestParameter(IN_GUEST_MODE, "tabindexOpenDialogDownloads")));
+        TestCase("tabindexOpenDialogDrive"),
+        TestCase("tabindexOpenDialogDownloads"),
+        TestCase("tabindexOpenDialogDownloads", IN_GUEST_MODE)));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     TabindexSaveFileDialog,
     FileManagerBrowserTest,
     ::testing::Values(
-        TestParameter(NOT_IN_GUEST_MODE, "tabindexSaveFileDialogDrive"),
-        TestParameter(NOT_IN_GUEST_MODE, "tabindexSaveFileDialogDownloads"),
-        TestParameter(IN_GUEST_MODE, "tabindexSaveFileDialogDownloads")));
+        TestCase("tabindexSaveFileDialogDrive"),
+        TestCase("tabindexSaveFileDialogDownloads"),
+        TestCase("tabindexSaveFileDialogDownloads", IN_GUEST_MODE)));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     OpenFileDialog,
     FileManagerBrowserTest,
-    ::testing::Values(TestParameter(NOT_IN_GUEST_MODE,
-                                    "openFileDialogDownloads"),
-                      TestParameter(IN_GUEST_MODE, "openFileDialogDownloads"),
-                      TestParameter(NOT_IN_GUEST_MODE, "openFileDialogDrive"),
-                      TestParameter(IN_INCOGNITO, "openFileDialogDownloads"),
-                      TestParameter(IN_INCOGNITO, "openFileDialogDrive"),
-                      TestParameter(NOT_IN_GUEST_MODE, "openFileDialogUnload")));
+    ::testing::Values(TestCase("openFileDialogDownloads"),
+                      TestCase("openFileDialogDownloads", IN_GUEST_MODE),
+                      TestCase("openFileDialogDrive"),
+                      TestCase("openFileDialogDownloads", IN_INCOGNITO),
+                      TestCase("openFileDialogDrive", IN_INCOGNITO),
+                      TestCase("openFileDialogUnload")));
 
 // Test does too much? Flaky on all bots: http://crbug.com/500966
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     DISABLED_CopyBetweenWindows,
     FileManagerBrowserTest,
     ::testing::Values(
-        TestParameter(NOT_IN_GUEST_MODE, "copyBetweenWindowsLocalToDrive"),
-        TestParameter(NOT_IN_GUEST_MODE, "copyBetweenWindowsLocalToUsb"),
-        TestParameter(NOT_IN_GUEST_MODE, "copyBetweenWindowsUsbToDrive"),
-        TestParameter(NOT_IN_GUEST_MODE, "copyBetweenWindowsDriveToLocal"),
-        TestParameter(NOT_IN_GUEST_MODE, "copyBetweenWindowsDriveToUsb"),
-        TestParameter(NOT_IN_GUEST_MODE, "copyBetweenWindowsUsbToLocal")));
+        TestCase("copyBetweenWindowsLocalToDrive"),
+        TestCase("copyBetweenWindowsLocalToUsb"),
+        TestCase("copyBetweenWindowsUsbToDrive"),
+        TestCase("copyBetweenWindowsDriveToLocal"),
+        TestCase("copyBetweenWindowsDriveToUsb"),
+        TestCase("copyBetweenWindowsUsbToLocal")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     ShowGridView,
     FileManagerBrowserTest,
-    ::testing::Values(TestParameter(NOT_IN_GUEST_MODE, "showGridViewDownloads"),
-                      TestParameter(IN_GUEST_MODE, "showGridViewDownloads"),
-                      TestParameter(NOT_IN_GUEST_MODE, "showGridViewDrive")));
+    ::testing::Values(TestCase("showGridViewDownloads"),
+                      TestCase("showGridViewDownloads", IN_GUEST_MODE),
+                      TestCase("showGridViewDrive")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     Providers,
     FileManagerBrowserTest,
     ::testing::Values(
-        TestParameter(NOT_IN_GUEST_MODE, "requestMount"),
-        TestParameter(NOT_IN_GUEST_MODE, "requestMountMultipleMounts"),
-        TestParameter(NOT_IN_GUEST_MODE, "requestMountSourceDevice"),
-        TestParameter(NOT_IN_GUEST_MODE, "requestMountSourceFile")));
+        TestCase("requestMount"),
+        TestCase("requestMountMultipleMounts"),
+        TestCase("requestMountSourceDevice"),
+        TestCase("requestMountSourceFile")));
 
 WRAPPED_INSTANTIATE_TEST_CASE_P(
     GearMenu,
     FileManagerBrowserTest,
     ::testing::Values(
-        TestParameter(NOT_IN_GUEST_MODE, "showHiddenFilesDownloads"),
-        TestParameter(NOT_IN_GUEST_MODE, "showHiddenFilesDrive"),
-        TestParameter(NOT_IN_GUEST_MODE, "toogleGoogleDocsDrive"),
-        TestParameter(NOT_IN_GUEST_MODE, "showPasteIntoCurrentFolder")));
+        TestCase("showHiddenFilesDownloads"),
+        TestCase("showHiddenFilesDrive"),
+        TestCase("toogleGoogleDocsDrive"),
+        TestCase("showPasteIntoCurrentFolder")));
 
 // Structure to describe an account info.
 struct TestAccountInfo {
diff --git a/chrome/browser/conflicts/enumerate_input_method_editors_win.cc b/chrome/browser/conflicts/enumerate_input_method_editors_win.cc
index cf54d54..c441c8a5 100644
--- a/chrome/browser/conflicts/enumerate_input_method_editors_win.cc
+++ b/chrome/browser/conflicts/enumerate_input_method_editors_win.cc
@@ -48,7 +48,7 @@
       L"{fa445657-9379-11d6-b41a-00065b83ee53}",
   };
 
-  constexpr auto comp = [](const wchar_t* lhs, const wchar_t* rhs) -> bool {
+  auto comp = [](const wchar_t* lhs, const wchar_t* rhs) -> bool {
     return base::CompareCaseInsensitiveASCII(lhs, rhs) == -1;
   };
 
diff --git a/chrome/browser/net/predictor.cc b/chrome/browser/net/predictor.cc
index aa74cab..21257bf 100644
--- a/chrome/browser/net/predictor.cc
+++ b/chrome/browser/net/predictor.cc
@@ -661,8 +661,6 @@
     DnsPrefetchMotivatedList(startup_urls, UrlInfo::STARTUP_LIST_MOTIVATED);
 
   DeserializeReferrers(*referral_list);
-
-  LogStartupMetrics();
 }
 
 //-----------------------------------------------------------------------------
@@ -809,8 +807,6 @@
   if (!getter)
     return;
 
-  UMA_HISTOGRAM_ENUMERATION("Net.PreconnectMotivation", motivation,
-                            UrlInfo::MAX_MOTIVATED);
   content::PreconnectUrl(getter, url, site_for_cookies, count,
                          allow_credentials);
 }
@@ -893,14 +889,9 @@
       UrlInfo::LEARNED_REFERAL_MOTIVATED;
   for (std::map<GURL, ReferrerValue>::iterator future_url = referrer->begin();
        future_url != referrer->end();) {
-    SubresourceValue evalution(TOO_NEW);
     double connection_expectation = future_url->second.subresource_use_rate();
-    UMA_HISTOGRAM_CUSTOM_COUNTS("Net.PreconnectSubresourceExpectation",
-                                static_cast<int>(connection_expectation * 100),
-                                10, 5000, 50);
     future_url->second.ReferrerWasObserved();
     if (connection_expectation > kPreconnectWorthyExpectedValue) {
-      evalution = PRECONNECTION;
       future_url->second.IncrementPreconnectionCount();
       int count = static_cast<int>(std::ceil(connection_expectation));
       if (url.host_piece() == future_url->first.host_piece())
@@ -908,7 +899,6 @@
       PreconnectUrlOnIOThread(future_url->first, site_for_cookies, motivation,
                               kAllowCredentialsOnPreconnectByDefault, count);
     } else if (connection_expectation > kDNSPreresolutionWorthyExpectedValue) {
-      evalution = PRERESOLUTION;
       future_url->second.preresolution_increment();
       AppendToResolutionQueue(future_url->first, motivation);
     }
@@ -920,8 +910,6 @@
     } else {
       ++future_url;
     }
-    UMA_HISTOGRAM_ENUMERATION("Net.PreconnectSubresourceEval", evalution,
-                              SUBRESOURCE_VALUE_MAX);
   }
   // If the Referrer has no URLs associated with it, remove it from the map.
   if (referrer->empty())
@@ -1066,21 +1054,6 @@
   return url.ReplaceComponents(replacements);
 }
 
-void Predictor::LogStartupMetrics() {
-  size_t total_bytes = 0;
-  for (const auto& referrer : referrers_) {
-    total_bytes += referrer.first.spec().size();
-    total_bytes += sizeof(Referrer);
-    for (const auto& subresource : referrer.second) {
-      total_bytes += subresource.first.spec().size();
-      total_bytes += sizeof(ReferrerValue);
-    }
-  }
-  UMA_HISTOGRAM_CUSTOM_COUNTS("Net.Predictor.Startup.DBSize", total_bytes, 1,
-                              10 * 1000 * 1000, 50);
-}
-
-
 // ---------------------- End IO methods. -------------------------------------
 
 //-----------------------------------------------------------------------------
diff --git a/chrome/browser/net/predictor.h b/chrome/browser/net/predictor.h
index 8d082def..713b433 100644
--- a/chrome/browser/net/predictor.h
+++ b/chrome/browser/net/predictor.h
@@ -412,8 +412,6 @@
   // Applies the HSTS redirect for |url|, if any.
   GURL GetHSTSRedirectOnIOThread(const GURL& url);
 
-  void LogStartupMetrics();
-
   // ------------- End IO thread methods.
 
   std::unique_ptr<InitialObserver> initial_observer_;
diff --git a/chrome/browser/password_manager/password_manager_browsertest.cc b/chrome/browser/password_manager/password_manager_browsertest.cc
index 526d062..fbd9002 100644
--- a/chrome/browser/password_manager/password_manager_browsertest.cc
+++ b/chrome/browser/password_manager/password_manager_browsertest.cc
@@ -650,12 +650,16 @@
 
   // Verify that if XHR navigation occurs and the form is properly filled out,
   // we try and save the password even though onsubmit hasn't been called.
-  FillElementWithValue("username_field", "USER");
-  FillElementWithValue("password_field", "1234");
   NavigationObserver observer(WebContents());
-  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), "send_xhr()"));
+  std::unique_ptr<BubbleObserver> prompt_observer(
+      new BubbleObserver(WebContents()));
+  std::string fill_and_navigate =
+      "document.getElementById('username_field').value = 'temp';"
+      "document.getElementById('password_field').value = 'random';"
+      "send_xhr()";
+  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_navigate));
   observer.Wait();
-  EXPECT_TRUE(BubbleObserver(WebContents()).IsSavePromptShownAutomatically());
+  EXPECT_TRUE(prompt_observer->IsSavePromptShownAutomatically());
 }
 
 IN_PROC_BROWSER_TEST_P(PasswordManagerBrowserTestWithViewsFeature,
@@ -666,13 +670,17 @@
   // we try and save the password even though onsubmit hasn't been called.
   // Specifically verify that the password form saving new passwords is treated
   // the same as a login form.
-  FillElementWithValue("signup_username_field", "USER");
-  FillElementWithValue("signup_password_field", "1234");
-  FillElementWithValue("confirmation_password_field", "1234");
   NavigationObserver observer(WebContents());
-  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), "send_xhr()"));
+  std::unique_ptr<BubbleObserver> prompt_observer(
+      new BubbleObserver(WebContents()));
+  std::string fill_and_navigate =
+      "document.getElementById('signup_username_field').value = 'temp';"
+      "document.getElementById('signup_password_field').value = 'random';"
+      "document.getElementById('confirmation_password_field').value = 'random';"
+      "send_xhr()";
+  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_navigate));
   observer.Wait();
-  EXPECT_TRUE(BubbleObserver(WebContents()).IsSavePromptShownAutomatically());
+  EXPECT_TRUE(prompt_observer->IsSavePromptShownAutomatically());
 }
 
 IN_PROC_BROWSER_TEST_P(PasswordManagerBrowserTestWithViewsFeature,
@@ -815,13 +823,16 @@
 
   // Verify that if Fetch navigation occurs and the form is properly filled out,
   // we try and save the password even though onsubmit hasn't been called.
-  FillElementWithValue("username_field", "USER");
-  FillElementWithValue("password_field", "1234");
-
   NavigationObserver observer(WebContents());
-  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), "send_fetch()"));
+  std::unique_ptr<BubbleObserver> prompt_observer(
+      new BubbleObserver(WebContents()));
+  std::string fill_and_navigate =
+      "document.getElementById('username_field').value = 'temp';"
+      "document.getElementById('password_field').value = 'random';"
+      "send_fetch()";
+  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_navigate));
   observer.Wait();
-  EXPECT_TRUE(BubbleObserver(WebContents()).IsSavePromptShownAutomatically());
+  EXPECT_TRUE(prompt_observer->IsSavePromptShownAutomatically());
 }
 
 IN_PROC_BROWSER_TEST_P(PasswordManagerBrowserTestWithViewsFeature,
@@ -832,13 +843,17 @@
   // we try and save the password even though onsubmit hasn't been called.
   // Specifically verify that the password form saving new passwords is treated
   // the same as a login form.
-  FillElementWithValue("signup_username_field", "USER");
-  FillElementWithValue("signup_password_field", "1234");
-  FillElementWithValue("confirmation_password_field", "1234");
   NavigationObserver observer(WebContents());
-  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), "send_fetch()"));
+  std::unique_ptr<BubbleObserver> prompt_observer(
+      new BubbleObserver(WebContents()));
+  std::string fill_and_navigate =
+      "document.getElementById('signup_username_field').value = 'temp';"
+      "document.getElementById('signup_password_field').value = 'random';"
+      "document.getElementById('confirmation_password_field').value = 'random';"
+      "send_fetch()";
+  ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_navigate));
   observer.Wait();
-  EXPECT_TRUE(BubbleObserver(WebContents()).IsSavePromptShownAutomatically());
+  EXPECT_TRUE(prompt_observer->IsSavePromptShownAutomatically());
 }
 
 IN_PROC_BROWSER_TEST_P(PasswordManagerBrowserTestWithViewsFeature,
@@ -1747,9 +1762,10 @@
 IN_PROC_BROWSER_TEST_P(PasswordManagerBrowserTestWithViewsFeature,
                        AutofillSuggestionsForPasswordFormWithoutUsernameField) {
   std::string submit =
+      "document.getElementById('password').value = 'mypassword';"
       "document.getElementById('submit-button').click();";
   VerifyPasswordIsSavedAndFilled("/password/form_with_only_password_field.html",
-                                 std::string(), "password", submit);
+                                 submit, "password", "mypassword");
 }
 
 // Test that if a form gets autofilled, then it gets autofilled on re-creation
@@ -2158,9 +2174,11 @@
 IN_PROC_BROWSER_TEST_P(PasswordManagerBrowserTestWithViewsFeature,
                        AutofillSuggestionsForLoginSignupForm) {
   std::string submit =
+      "document.getElementById('username').value = 'myusername';"
+      "document.getElementById('password').value = 'mypassword';"
       "document.getElementById('submit').click();";
-  VerifyPasswordIsSavedAndFilled("/password/login_signup_form.html", "username",
-                                 "password", submit);
+  VerifyPasswordIsSavedAndFilled("/password/login_signup_form.html",
+                                 submit, "password", "mypassword");
 }
 
 // Check that we can fill in cases where <base href> is set and the action of
@@ -2168,9 +2186,11 @@
 IN_PROC_BROWSER_TEST_P(PasswordManagerBrowserTestWithViewsFeature,
                        BaseTagWithNoActionTest) {
   std::string submit =
+      "document.getElementById('username_field').value = 'myusername';"
+      "document.getElementById('password_field').value = 'mypassword';"
       "document.getElementById('submit_button').click();";
   VerifyPasswordIsSavedAndFilled("/password/password_xhr_submit.html",
-                                 "username_field", "password_field", submit);
+                                 submit, "password_field", "mypassword");
 }
 
 // Check that a username and password are filled into forms in iframes
@@ -2394,9 +2414,14 @@
 
 IN_PROC_BROWSER_TEST_P(PasswordManagerBrowserTestWithViewsFeature,
                        NoFormElementTest) {
+  std::string submit =
+      "document.getElementById('username_field').value = 'myusername';"
+      "document.getElementById('password_field').value = 'mypassword';"
+      "send_xhr();";
   VerifyPasswordIsSavedAndFilled("/password/no_form_element.html",
-                                 "username_field", "password_field",
-                                 "send_xhr();");
+                                 submit,
+                                 "password_field",
+                                 "mypassword");
 }
 
 // The password manager driver will kill processes when they try to access
@@ -3238,10 +3263,12 @@
 IN_PROC_BROWSER_TEST_P(PasswordManagerBrowserTestWithViewsFeature,
                        AutofillSuggestionsForPasswordFormWithAutocompleteOff) {
   std::string submit =
+      "document.getElementById('username').value = 'temp';"
+      "document.getElementById('password').value = 'mypassword';"
       "document.getElementById('submit').click();";
   VerifyPasswordIsSavedAndFilled(
-      "/password/password_autocomplete_off_test.html", "username", "password",
-      submit);
+      "/password/password_autocomplete_off_test.html", submit, "password",
+      "mypassword");
 }
 
 IN_PROC_BROWSER_TEST_P(
diff --git a/chrome/browser/password_manager/password_manager_test_base.cc b/chrome/browser/password_manager/password_manager_test_base.cc
index 2b38019a..f7ffcad 100644
--- a/chrome/browser/password_manager/password_manager_test_base.cc
+++ b/chrome/browser/password_manager/password_manager_test_base.cc
@@ -37,8 +37,6 @@
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/url_request/url_request_context.h"
 #include "net/url_request/url_request_context_getter.h"
-#include "ui/events/keycodes/dom_us_layout_data.h"
-#include "ui/events/keycodes/keyboard_code_conversion.h"
 
 namespace {
 
@@ -490,10 +488,9 @@
 
 void PasswordManagerBrowserTestBase::VerifyPasswordIsSavedAndFilled(
     const std::string& filename,
-    const std::string& username_id,
-    const std::string& password_id,
-    const std::string& submission_script) {
-  EXPECT_FALSE(password_id.empty());
+    const std::string& submission_script,
+    const std::string& expected_element,
+    const std::string& expected_value) {
   password_manager::TestPasswordStore* password_store =
       static_cast<password_manager::TestPasswordStore*>(
           PasswordStoreFactory::GetForProfile(
@@ -503,11 +500,6 @@
   NavigateToFile(filename);
 
   NavigationObserver observer(WebContents());
-  const char kUsername[] = "U";
-  const char kPassword[] = "P";
-  if (!username_id.empty())
-    FillElementWithValue(username_id, kUsername);
-  FillElementWithValue(password_id, kPassword);
   ASSERT_TRUE(content::ExecuteScript(RenderFrameHost(), submission_script));
   observer.Wait();
   WaitForPasswordStore();
@@ -527,34 +519,7 @@
       WebContents(), 0, blink::WebMouseEvent::Button::kLeft, gfx::Point(1, 1));
 
   // Wait until that interaction causes the password value to be revealed.
-  if (!username_id.empty())
-    WaitForElementValue(username_id, kUsername);
-  WaitForElementValue(password_id, kPassword);
-}
-
-void PasswordManagerBrowserTestBase::FillElementWithValue(
-    const std::string& element_id,
-    const std::string& value) {
-  ASSERT_TRUE(content::ExecuteScript(
-      RenderFrameHost(),
-      base::StringPrintf("document.getElementById('%s').focus();",
-                         element_id.c_str())));
-  for (size_t i = 0; i < value.length(); ++i) {
-    ui::DomKey dom_key = ui::DomKey::FromCharacter(value[i]);
-    base::char16 character = value[i];
-    const ui::PrintableCodeEntry* code_entry = std::find_if(
-        std::begin(ui::kPrintableCodeMap), std::end(ui::kPrintableCodeMap),
-        [character](const ui::PrintableCodeEntry& entry) {
-          return entry.character[0] == character ||
-                 entry.character[1] == character;
-        });
-    ASSERT_TRUE(code_entry != std::end(ui::kPrintableCodeMap));
-    bool shift = code_entry->character[1] == character;
-    ui::DomCode dom_code = code_entry->dom_code;
-    content::SimulateKeyPress(WebContents(), dom_key, dom_code,
-                              ui::DomCodeToUsLayoutKeyboardCode(dom_code),
-                              false, shift, false, false);
-  }
+  WaitForElementValue(expected_element, expected_value);
 }
 
 void PasswordManagerBrowserTestBase::WaitForElementValue(
diff --git a/chrome/browser/password_manager/password_manager_test_base.h b/chrome/browser/password_manager/password_manager_test_base.h
index ea1701c..99deff3 100644
--- a/chrome/browser/password_manager/password_manager_test_base.h
+++ b/chrome/browser/password_manager/password_manager_test_base.h
@@ -59,12 +59,11 @@
   DISALLOW_COPY_AND_ASSIGN(NavigationObserver);
 };
 
-// Checks the save password prompt for a specified WebContents and allows
-// accepting saving passwords through it.
+// Observes the save password prompt for a specified WebContents, keeps track of
+// whether or not it is currently shown, and allows accepting saving passwords
+// through it.
 class BubbleObserver {
  public:
-  // The constructor doesn't start tracking |web_contents|. To check the status
-  // of the prompt one can even construct a temporary BubbleObserver.
   explicit BubbleObserver(content::WebContents* web_contents);
 
   // Checks if the save prompt is being currently available due to either manual
@@ -147,19 +146,13 @@
   // return immediately.
   void NavigateToFile(const std::string& path);
 
-  // Navigates to |filename|, fills |username_id| and |password_id| if nonempty
-  // and runs |submission_script| to submit. The credential is then saved.
-  // Navigates back to |filename| and then verifies that the same elements are
-  // filled.
+  // Navigates to |filename| and runs |submission_script| to submit. Navigates
+  // back to |filename| and then verifies that |expected_element| has
+  // |expected_value|.
   void VerifyPasswordIsSavedAndFilled(const std::string& filename,
-                                      const std::string& username_id,
-                                      const std::string& password_id,
-                                      const std::string& submission_script);
-
-  // Focuses an input element with id |element_id| in the main frame and
-  // emulates typing |value| into it.
-  void FillElementWithValue(const std::string& element_id,
-                            const std::string& value);
+                                      const std::string& submission_script,
+                                      const std::string& expected_element,
+                                      const std::string& expected_value);
 
   // Waits until the "value" attribute of the HTML element with |element_id| is
   // equal to |expected_value|. If the current value is not as expected, this
diff --git a/content/app/BUILD.gn b/content/app/BUILD.gn
index 518bcc9..70d55be 100644
--- a/content/app/BUILD.gn
+++ b/content/app/BUILD.gn
@@ -71,10 +71,10 @@
     content_app_deps += [ "//content/ppapi_plugin:ppapi_plugin_sources" ]
   }
 
-  # Compile content_main_runner_impl.[h, cc] in a separate target to exempt from
-  # GN header checking without exempting any other source file. These files
-  # includes headers of all process types and varies significantly per platform
-  # in between browser and child. Otherwise it would require many "nogncheck"
+  # Compile content_main_runner_impl.cc in a separate target to exempt from GN
+  # header checking without exempting any other source file. This file includes
+  # headers of all process types and varies significantly per platform in
+  # between browser and child. Otherwise it would require many "nogncheck"
   # annotations that would both be useless and add noise.
   #
   # This will generate :content_main_runner_both, :content_main_runner_browser,
@@ -85,7 +85,6 @@
 
     sources = [
       "content_main_runner_impl.cc",
-      "content_main_runner_impl.h",
     ]
 
     configs += extra_configs
diff --git a/content/app/content_main_runner_impl.cc b/content/app/content_main_runner_impl.cc
index cb79126..4863bbf 100644
--- a/content/app/content_main_runner_impl.cc
+++ b/content/app/content_main_runner_impl.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/app/content_main_runner_impl.h"
+#include "content/public/app/content_main_runner.h"
 
 #include <stddef.h>
 #include <stdlib.h>
@@ -34,16 +34,17 @@
 #include "base/process/memory.h"
 #include "base/process/process.h"
 #include "base/process/process_handle.h"
-#include "base/single_thread_task_runner.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/trace_event/trace_event.h"
+#include "build/build_config.h"
 #include "components/tracing/common/trace_startup.h"
 #include "content/app/mojo/mojo_init.h"
-#include "content/browser/browser_process_sub_thread.h"
 #include "content/common/url_schemes.h"
+#include "content/public/app/content_main.h"
 #include "content/public/app/content_main_delegate.h"
+#include "content/public/common/content_client.h"
 #include "content/public/common/content_constants.h"
 #include "content/public/common/content_descriptor_keys.h"
 #include "content/public/common/content_features.h"
@@ -70,8 +71,10 @@
 #include <cstring>
 
 #include "base/trace_event/trace_event_etw_export_win.h"
+#include "sandbox/win/src/sandbox_types.h"
 #include "ui/display/win/dpi.h"
 #elif defined(OS_MACOSX)
+#include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/power_monitor/power_monitor_device_source.h"
 #include "content/browser/mach_broker_mac.h"
 #endif  // OS_WIN
@@ -288,7 +291,7 @@
       switches::kVModule,
   };
   cmd_line->CopySwitchesFrom(*base::CommandLine::ForCurrentProcess(),
-                             kForwardSwitches, base::size(kForwardSwitches));
+                             kForwardSwitches, arraysize(kForwardSwitches));
 
   GetContentClient()->browser()->AppendExtraCommandLineSwitches(cmd_line, -1);
 
@@ -561,7 +564,7 @@
   if (sandbox_type == service_manager::SANDBOX_TYPE_PROFILING)
     sandbox::SetUseLocaltimeOverride(false);
 
-  for (size_t i = 0; i < base::size(kMainFunctions); ++i) {
+  for (size_t i = 0; i < arraysize(kMainFunctions); ++i) {
     if (process_type == kMainFunctions[i].name)
       return kMainFunctions[i].function(main_params);
   }
@@ -594,39 +597,17 @@
 #endif  // !CHROME_MULTIPLE_DLL_BROWSER && !CHROME_MULTIPLE_DLL_CHILD
 }
 
-#if !defined(CHROME_MULTIPLE_DLL_CHILD)
-// Run the main function for browser process.
-// Returns the exit code for this process.
-int RunBrowserProcessMain(
-    const MainFunctionParams& main_function_params,
-    ContentMainDelegate* delegate,
-    std::unique_ptr<BrowserProcessSubThread> service_manager_thread) {
-  if (delegate) {
-    int exit_code = delegate->RunProcess("", main_function_params);
-#if defined(OS_ANDROID)
-    // In Android's browser process, the negative exit code doesn't mean the
-    // default behavior should be used as the UI message loop is managed by
-    // the Java and the browser process's default behavior is always
-    // overridden.
-    return exit_code;
-#endif
-    if (exit_code >= 0)
-      return exit_code;
-  }
-  // GetServiceManagerTaskRunnerForEmbedderProcess() needs to be invoked before
-  // Run() for the browser process.
-  DCHECK(service_manager_thread);
-  return BrowserMain(main_function_params, std::move(service_manager_thread));
-}
-#endif  // !defined(CHROME_MULTIPLE_DLL_CHILD)
-
 // Run the FooMain() for a given process type.
+// If |process_type| is empty, runs BrowserMain().
 // Returns the exit code for this process.
-int RunOtherNamedProcessTypeMain(const std::string& process_type,
-                                 const MainFunctionParams& main_function_params,
-                                 ContentMainDelegate* delegate) {
-#if !defined(CHROME_MULTIPLE_DLL_BROWSER)
+int RunNamedProcessTypeMain(const std::string& process_type,
+                            const MainFunctionParams& main_function_params,
+                            ContentMainDelegate* delegate) {
   static const MainFunction kMainFunctions[] = {
+#if !defined(CHROME_MULTIPLE_DLL_CHILD)
+    {"", BrowserMain},
+#endif
+#if !defined(CHROME_MULTIPLE_DLL_BROWSER)
 #if BUILDFLAG(ENABLE_PLUGINS)
     {switches::kPpapiPluginProcess, PpapiPluginMain},
     {switches::kPpapiBrokerProcess, PpapiBrokerMain},
@@ -634,20 +615,30 @@
     {switches::kUtilityProcess, UtilityMain},
     {switches::kRendererProcess, RendererMain},
     {switches::kGpuProcess, GpuMain},
+#endif  // !CHROME_MULTIPLE_DLL_BROWSER
   };
 
-  for (size_t i = 0; i < base::size(kMainFunctions); ++i) {
+  RegisterMainThreadFactories();
+
+  for (size_t i = 0; i < arraysize(kMainFunctions); ++i) {
     if (process_type == kMainFunctions[i].name) {
       if (delegate) {
         int exit_code =
             delegate->RunProcess(process_type, main_function_params);
+#if defined(OS_ANDROID)
+        // In Android's browser process, the negative exit code doesn't mean the
+        // default behavior should be used as the UI message loop is managed by
+        // the Java and the browser process's default behavior is always
+        // overridden.
+        if (process_type.empty())
+          return exit_code;
+#endif
         if (exit_code >= 0)
           return exit_code;
       }
       return kMainFunctions[i].function(main_function_params);
     }
   }
-#endif  // !CHROME_MULTIPLE_DLL_BROWSER
 
 #if BUILDFLAG(USE_ZYGOTE_HANDLE)
   // Zygote startup is special -- see RunZygote comments above
@@ -664,342 +655,351 @@
   return 1;
 }
 
-// static
-ContentMainRunnerImpl* ContentMainRunnerImpl::Create() {
-  return new ContentMainRunnerImpl();
-}
-
-ContentMainRunnerImpl::ContentMainRunnerImpl() {
+class ContentMainRunnerImpl : public ContentMainRunner {
+ public:
+  ContentMainRunnerImpl() {
 #if defined(OS_WIN)
-  memset(&sandbox_info_, 0, sizeof(sandbox_info_));
+    memset(&sandbox_info_, 0, sizeof(sandbox_info_));
 #endif
-}
+  }
 
-ContentMainRunnerImpl::~ContentMainRunnerImpl() {
-  if (is_initialized_ && !is_shutdown_)
-    Shutdown();
-}
+  ~ContentMainRunnerImpl() override {
+    if (is_initialized_ && !is_shutdown_)
+      Shutdown();
+  }
 
-int ContentMainRunnerImpl::TerminateForFatalInitializationError() {
-  if (delegate_)
-    return delegate_->TerminateForFatalInitializationError();
+  int TerminateForFatalInitializationError() {
+    if (delegate_)
+      return delegate_->TerminateForFatalInitializationError();
 
-  CHECK(false);
-  return 0;
-}
+    CHECK(false);
+    return 0;
+  }
 
-int ContentMainRunnerImpl::Initialize(const ContentMainParams& params) {
-  ui_task_ = params.ui_task;
-  created_main_parts_closure_ = params.created_main_parts_closure;
+  int Initialize(const ContentMainParams& params) override {
+    ui_task_ = params.ui_task;
+    created_main_parts_closure_ = params.created_main_parts_closure;
 
 #if defined(OS_WIN)
-  sandbox_info_ = *params.sandbox_info;
+    sandbox_info_ = *params.sandbox_info;
 #else  // !OS_WIN
 
 #if defined(OS_MACOSX)
-  autorelease_pool_ = params.autorelease_pool;
+    autorelease_pool_ = params.autorelease_pool;
 #endif  // defined(OS_MACOSX)
 
 #if defined(OS_ANDROID)
-  // See note at the initialization of ExitManager, below; basically,
-  // only Android builds have the ctor/dtor handlers set up to use
-  // TRACE_EVENT right away.
-  TRACE_EVENT0("startup,benchmark,rail", "ContentMainRunnerImpl::Initialize");
+    // See note at the initialization of ExitManager, below; basically,
+    // only Android builds have the ctor/dtor handlers set up to use
+    // TRACE_EVENT right away.
+    TRACE_EVENT0("startup,benchmark,rail", "ContentMainRunnerImpl::Initialize");
 #endif  // OS_ANDROID
 
-  base::GlobalDescriptors* g_fds = base::GlobalDescriptors::GetInstance();
-  ALLOW_UNUSED_LOCAL(g_fds);
+    base::GlobalDescriptors* g_fds = base::GlobalDescriptors::GetInstance();
+    ALLOW_UNUSED_LOCAL(g_fds);
 
 // On Android, the ipc_fd is passed through the Java service.
 #if !defined(OS_ANDROID)
-  g_fds->Set(kMojoIPCChannel,
-             kMojoIPCChannel + base::GlobalDescriptors::kBaseDescriptor);
+    g_fds->Set(kMojoIPCChannel,
+               kMojoIPCChannel + base::GlobalDescriptors::kBaseDescriptor);
 
-  g_fds->Set(kFieldTrialDescriptor,
-             kFieldTrialDescriptor + base::GlobalDescriptors::kBaseDescriptor);
+    g_fds->Set(
+        kFieldTrialDescriptor,
+        kFieldTrialDescriptor + base::GlobalDescriptors::kBaseDescriptor);
 #endif  // !OS_ANDROID
 
 #if defined(OS_LINUX) || defined(OS_OPENBSD)
-  g_fds->Set(kCrashDumpSignal,
-             kCrashDumpSignal + base::GlobalDescriptors::kBaseDescriptor);
+    g_fds->Set(kCrashDumpSignal,
+               kCrashDumpSignal + base::GlobalDescriptors::kBaseDescriptor);
 #endif  // OS_LINUX || OS_OPENBSD
 
 #endif  // !OS_WIN
 
-  is_initialized_ = true;
-  delegate_ = params.delegate;
+    is_initialized_ = true;
+    delegate_ = params.delegate;
 
 // The exit manager is in charge of calling the dtors of singleton objects.
 // On Android, AtExitManager is set up when library is loaded.
 // A consequence of this is that you can't use the ctor/dtor-based
 // TRACE_EVENT methods on Linux or iOS builds till after we set this up.
 #if !defined(OS_ANDROID)
-  if (!ui_task_) {
-    // When running browser tests, don't create a second AtExitManager as that
-    // interfers with shutdown when objects created before ContentMain is
-    // called are destructed when it returns.
-    exit_manager_.reset(new base::AtExitManager);
-  }
+    if (!ui_task_) {
+      // When running browser tests, don't create a second AtExitManager as that
+      // interfers with shutdown when objects created before ContentMain is
+      // called are destructed when it returns.
+      exit_manager_.reset(new base::AtExitManager);
+    }
 #endif  // !OS_ANDROID
 
-  int exit_code = 0;
-  if (delegate_ && delegate_->BasicStartupComplete(&exit_code))
-    return exit_code;
-  completed_basic_startup_ = true;
+    int exit_code = 0;
+    if (delegate_ && delegate_->BasicStartupComplete(&exit_code))
+      return exit_code;
+    completed_basic_startup_ = true;
 
-  // We will need to use data from resources.pak in later cl, so load the file
-  // now.
-  if (IsRootProcess()) {
-    ui::DataPack* data_pack = delegate_->LoadServiceManifestDataPack();
-    // TODO(ranj): Read manifest from this data pack.
-    ignore_result(data_pack);
-  }
+    // We will need to use data from resources.pak in later cl, so load the file
+    // now.
+    if (IsRootProcess()) {
+      ui::DataPack* data_pack = delegate_->LoadServiceManifestDataPack();
+      // TODO(ranj): Read manifest from this data pack.
+      ignore_result(data_pack);
+    }
 
-  const base::CommandLine& command_line =
-      *base::CommandLine::ForCurrentProcess();
-  std::string process_type =
-      command_line.GetSwitchValueASCII(switches::kProcessType);
-
-#if defined(OS_WIN)
-  if (command_line.HasSwitch(switches::kDeviceScaleFactor)) {
-    std::string scale_factor_string =
-        command_line.GetSwitchValueASCII(switches::kDeviceScaleFactor);
-    double scale_factor = 0;
-    if (base::StringToDouble(scale_factor_string, &scale_factor))
-      display::win::SetDefaultDeviceScaleFactor(scale_factor);
-  }
-#endif
-
-  if (!GetContentClient())
-    SetContentClient(&empty_content_client_);
-  ContentClientInitializer::Set(process_type, delegate_);
-
-#if !defined(OS_ANDROID)
-  // Enable startup tracing asap to avoid early TRACE_EVENT calls being
-  // ignored. For Android, startup tracing is enabled in an even earlier place
-  // content/app/android/library_loader_hooks.cc.
-  //
-  // Startup tracing flags are not (and should not) passed to Zygote
-  // processes. We will enable tracing when forked, if needed.
-  if (process_type != switches::kZygoteProcess)
-    tracing::EnableStartupTracingIfNeeded();
-#endif  // !OS_ANDROID
-
-#if defined(OS_WIN)
-  // Enable exporting of events to ETW if requested on the command line.
-  if (command_line.HasSwitch(switches::kTraceExportEventsToETW))
-    base::trace_event::TraceEventETWExport::EnableETWExport();
-#endif  // OS_WIN
-
-#if !defined(OS_ANDROID)
-  // Android tracing started at the beginning of the method.
-  // Other OSes have to wait till we get here in order for all the memory
-  // management setup to be completed.
-  TRACE_EVENT0("startup,benchmark,rail", "ContentMainRunnerImpl::Initialize");
-#endif  // !OS_ANDROID
-
-#if defined(OS_MACOSX)
-  // We need to allocate the IO Ports before the Sandbox is initialized or
-  // the first instance of PowerMonitor is created.
-  // It's important not to allocate the ports for processes which don't
-  // register with the power monitor - see https://crbug.com/88867.
-  if (process_type.empty() ||
-      (delegate_ &&
-       delegate_->ProcessRegistersWithSystemProcess(process_type))) {
-    base::PowerMonitorDeviceSource::AllocateSystemIOPorts();
-  }
-
-  if (!process_type.empty() &&
-      (!delegate_ || delegate_->ShouldSendMachPort(process_type))) {
-    MachBroker::ChildSendTaskPortToParent();
-  }
-#endif
-
-  // If we are on a platform where the default allocator is overridden (shim
-  // layer on windows, tcmalloc on Linux Desktop) smoke-tests that the
-  // overriding logic is working correctly. If not causes a hard crash, as its
-  // unexpected absence has security implications.
-  CHECK(base::allocator::IsAllocatorInitialized());
-
-#if defined(OS_POSIX) || defined(OS_FUCHSIA)
-  if (!process_type.empty()) {
-    // When you hit Ctrl-C in a terminal running the browser
-    // process, a SIGINT is delivered to the entire process group.
-    // When debugging the browser process via gdb, gdb catches the
-    // SIGINT for the browser process (and dumps you back to the gdb
-    // console) but doesn't for the child processes, killing them.
-    // The fix is to have child processes ignore SIGINT; they'll die
-    // on their own when the browser process goes away.
-    //
-    // Note that we *can't* rely on BeingDebugged to catch this case because
-    // we are the child process, which is not being debugged.
-    // TODO(evanm): move this to some shared subprocess-init function.
-    if (!base::debug::BeingDebugged())
-      signal(SIGINT, SIG_IGN);
-  }
-#endif
-
-  RegisterPathProvider();
-  RegisterContentSchemes(true);
-
-#if defined(OS_ANDROID) && (ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE)
-  int icudata_fd = g_fds->MaybeGet(kAndroidICUDataDescriptor);
-  if (icudata_fd != -1) {
-    auto icudata_region = g_fds->GetRegion(kAndroidICUDataDescriptor);
-    if (!base::i18n::InitializeICUWithFileDescriptor(icudata_fd,
-                                                     icudata_region))
-      return TerminateForFatalInitializationError();
-  } else {
-    if (!base::i18n::InitializeICU())
-      return TerminateForFatalInitializationError();
-  }
-#else
-  if (!base::i18n::InitializeICU())
-    return TerminateForFatalInitializationError();
-#endif  // OS_ANDROID && (ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE)
-
-  InitializeV8IfNeeded(command_line, process_type);
-
-  blink::TrialTokenValidator::SetOriginTrialPolicyGetter(
-      base::BindRepeating([]() -> blink::OriginTrialPolicy* {
-        if (auto* client = GetContentClient())
-          return client->GetOriginTrialPolicy();
-        return nullptr;
-      }));
-
-#if !defined(OFFICIAL_BUILD)
-#if defined(OS_WIN)
-  bool should_enable_stack_dump = !process_type.empty();
-#else
-  bool should_enable_stack_dump = true;
-#endif
-  // Print stack traces to stderr when crashes occur. This opens up security
-  // holes so it should never be enabled for official builds. This needs to
-  // happen before crash reporting is initialized (which for chrome happens in
-  // the call to PreSandboxStartup() on the delegate below), because otherwise
-  // this would interfere with signal handlers used by crash reporting.
-  if (should_enable_stack_dump &&
-      !command_line.HasSwitch(
-          service_manager::switches::kDisableInProcessStackTraces)) {
-    base::debug::EnableInProcessStackDumping();
-  }
-#endif  // !defined(OFFICIAL_BUILD)
-
-  if (delegate_)
-    delegate_->PreSandboxStartup();
-
-#if defined(OS_WIN)
-  if (!InitializeSandbox(
-          service_manager::SandboxTypeFromCommandLine(command_line),
-          params.sandbox_info))
-    return TerminateForFatalInitializationError();
-#elif defined(OS_MACOSX)
-  // Do not initialize the sandbox at this point if the V2
-  // sandbox is enabled for the process type.
-  bool v2_enabled = base::CommandLine::ForCurrentProcess()->HasSwitch(
-      switches::kEnableV2Sandbox);
-
-  if (process_type == switches::kRendererProcess ||
-      process_type == switches::kPpapiPluginProcess || v2_enabled ||
-      (delegate_ && delegate_->DelaySandboxInitialization(process_type))) {
-    // On OS X the renderer sandbox needs to be initialized later in the
-    // startup sequence in RendererMainPlatformDelegate::EnableSandbox().
-  } else {
-    if (!InitializeSandbox())
-      return TerminateForFatalInitializationError();
-  }
-#endif
-
-  if (delegate_)
-    delegate_->SandboxInitialized(process_type);
-
-#if BUILDFLAG(USE_ZYGOTE_HANDLE)
-  if (process_type.empty()) {
-    // The sandbox host needs to be initialized before forking a thread to
-    // start the ServiceManager, and after setting up the sandbox and invoking
-    // SandboxInitialized().
-    InitializeZygoteSandboxForBrowserProcess(
-        *base::CommandLine::ForCurrentProcess());
-  }
-#endif  // BUILDFLAG(USE_ZYGOTE_HANDLE)
-
-  // Return -1 to indicate no early termination.
-  return -1;
-}
-
-int ContentMainRunnerImpl::Run() {
-  DCHECK(is_initialized_);
-  DCHECK(!is_shutdown_);
-  const base::CommandLine& command_line =
-      *base::CommandLine::ForCurrentProcess();
-  std::string process_type =
-      command_line.GetSwitchValueASCII(switches::kProcessType);
-
-  // Run this logic on all child processes. Zygotes will run this at a later
-  // point in time when the command line has been updated.
-  std::unique_ptr<base::FieldTrialList> field_trial_list;
-  if (!process_type.empty() && process_type != switches::kZygoteProcess)
-    InitializeFieldTrialAndFeatureList(&field_trial_list);
-
-  MainFunctionParams main_params(command_line);
-  main_params.ui_task = ui_task_;
-  main_params.created_main_parts_closure = created_main_parts_closure_;
-#if defined(OS_WIN)
-  main_params.sandbox_info = &sandbox_info_;
-#elif defined(OS_MACOSX)
-  main_params.autorelease_pool = autorelease_pool_;
-#endif
-
-  RegisterMainThreadFactories();
-
-#if !defined(CHROME_MULTIPLE_DLL_CHILD)
-  // The thread used to start the ServiceManager is handed-off to
-  // BrowserMain() which may elect to promote it (e.g. to BrowserThread::IO).
-  if (process_type.empty()) {
-    return RunBrowserProcessMain(main_params, delegate_,
-                                 std::move(service_manager_thread_));
-  }
-#endif
-
-  return RunOtherNamedProcessTypeMain(process_type, main_params, delegate_);
-}
-
-void ContentMainRunnerImpl::Shutdown() {
-  DCHECK(is_initialized_);
-  DCHECK(!is_shutdown_);
-
-  if (completed_basic_startup_ && delegate_) {
     const base::CommandLine& command_line =
         *base::CommandLine::ForCurrentProcess();
     std::string process_type =
         command_line.GetSwitchValueASCII(switches::kProcessType);
 
-    delegate_->ProcessExiting(process_type);
+#if defined(OS_WIN)
+    if (command_line.HasSwitch(switches::kDeviceScaleFactor)) {
+      std::string scale_factor_string =
+          command_line.GetSwitchValueASCII(switches::kDeviceScaleFactor);
+      double scale_factor = 0;
+      if (base::StringToDouble(scale_factor_string, &scale_factor))
+        display::win::SetDefaultDeviceScaleFactor(scale_factor);
+    }
+#endif
+
+    if (!GetContentClient())
+      SetContentClient(&empty_content_client_);
+    ContentClientInitializer::Set(process_type, delegate_);
+
+#if !defined(OS_ANDROID)
+    // Enable startup tracing asap to avoid early TRACE_EVENT calls being
+    // ignored. For Android, startup tracing is enabled in an even earlier place
+    // content/app/android/library_loader_hooks.cc.
+    //
+    // Startup tracing flags are not (and should not) passed to Zygote
+    // processes. We will enable tracing when forked, if needed.
+    if (process_type != switches::kZygoteProcess)
+      tracing::EnableStartupTracingIfNeeded();
+#endif  // !OS_ANDROID
+
+#if defined(OS_WIN)
+    // Enable exporting of events to ETW if requested on the command line.
+    if (command_line.HasSwitch(switches::kTraceExportEventsToETW))
+      base::trace_event::TraceEventETWExport::EnableETWExport();
+#endif  // OS_WIN
+
+#if !defined(OS_ANDROID)
+    // Android tracing started at the beginning of the method.
+    // Other OSes have to wait till we get here in order for all the memory
+    // management setup to be completed.
+    TRACE_EVENT0("startup,benchmark,rail", "ContentMainRunnerImpl::Initialize");
+#endif  // !OS_ANDROID
+
+#if defined(OS_MACOSX)
+    // We need to allocate the IO Ports before the Sandbox is initialized or
+    // the first instance of PowerMonitor is created.
+    // It's important not to allocate the ports for processes which don't
+    // register with the power monitor - see crbug.com/88867.
+    if (process_type.empty() ||
+        (delegate_ &&
+         delegate_->ProcessRegistersWithSystemProcess(process_type))) {
+      base::PowerMonitorDeviceSource::AllocateSystemIOPorts();
+    }
+
+    if (!process_type.empty() &&
+        (!delegate_ || delegate_->ShouldSendMachPort(process_type))) {
+      MachBroker::ChildSendTaskPortToParent();
+    }
+#endif
+
+    // If we are on a platform where the default allocator is overridden (shim
+    // layer on windows, tcmalloc on Linux Desktop) smoke-tests that the
+    // overriding logic is working correctly. If not causes a hard crash, as its
+    // unexpected absence has security implications.
+    CHECK(base::allocator::IsAllocatorInitialized());
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+    if (!process_type.empty()) {
+      // When you hit Ctrl-C in a terminal running the browser
+      // process, a SIGINT is delivered to the entire process group.
+      // When debugging the browser process via gdb, gdb catches the
+      // SIGINT for the browser process (and dumps you back to the gdb
+      // console) but doesn't for the child processes, killing them.
+      // The fix is to have child processes ignore SIGINT; they'll die
+      // on their own when the browser process goes away.
+      //
+      // Note that we *can't* rely on BeingDebugged to catch this case because
+      // we are the child process, which is not being debugged.
+      // TODO(evanm): move this to some shared subprocess-init function.
+      if (!base::debug::BeingDebugged())
+        signal(SIGINT, SIG_IGN);
+    }
+#endif
+
+    RegisterPathProvider();
+    RegisterContentSchemes(true);
+
+#if defined(OS_ANDROID) && (ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE)
+    int icudata_fd = g_fds->MaybeGet(kAndroidICUDataDescriptor);
+    if (icudata_fd != -1) {
+      auto icudata_region = g_fds->GetRegion(kAndroidICUDataDescriptor);
+      if (!base::i18n::InitializeICUWithFileDescriptor(icudata_fd,
+                                                       icudata_region))
+        return TerminateForFatalInitializationError();
+    } else {
+      if (!base::i18n::InitializeICU())
+        return TerminateForFatalInitializationError();
+    }
+#else
+    if (!base::i18n::InitializeICU())
+      return TerminateForFatalInitializationError();
+#endif  // OS_ANDROID && (ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE)
+
+    InitializeV8IfNeeded(command_line, process_type);
+
+    blink::TrialTokenValidator::SetOriginTrialPolicyGetter(
+        base::BindRepeating([]() -> blink::OriginTrialPolicy* {
+          if (auto* client = GetContentClient())
+            return client->GetOriginTrialPolicy();
+          return nullptr;
+        }));
+
+#if !defined(OFFICIAL_BUILD)
+#if defined(OS_WIN)
+    bool should_enable_stack_dump = !process_type.empty();
+#else
+    bool should_enable_stack_dump = true;
+#endif
+    // Print stack traces to stderr when crashes occur. This opens up security
+    // holes so it should never be enabled for official builds. This needs to
+    // happen before crash reporting is initialized (which for chrome happens in
+    // the call to PreSandboxStartup() on the delegate below), because otherwise
+    // this would interfere with signal handlers used by crash reporting.
+    if (should_enable_stack_dump &&
+        !command_line.HasSwitch(
+            service_manager::switches::kDisableInProcessStackTraces)) {
+      base::debug::EnableInProcessStackDumping();
+    }
+#endif  // !defined(OFFICIAL_BUILD)
+
+    if (delegate_)
+      delegate_->PreSandboxStartup();
+
+#if defined(OS_WIN)
+    if (!InitializeSandbox(
+            service_manager::SandboxTypeFromCommandLine(command_line),
+            params.sandbox_info))
+      return TerminateForFatalInitializationError();
+#elif defined(OS_MACOSX)
+    // Do not initialize the sandbox at this point if the V2
+    // sandbox is enabled for the process type.
+    bool v2_enabled = base::CommandLine::ForCurrentProcess()->HasSwitch(
+        switches::kEnableV2Sandbox);
+
+    if (process_type == switches::kRendererProcess ||
+        process_type == switches::kPpapiPluginProcess || v2_enabled ||
+        (delegate_ && delegate_->DelaySandboxInitialization(process_type))) {
+      // On OS X the renderer sandbox needs to be initialized later in the
+      // startup sequence in RendererMainPlatformDelegate::EnableSandbox().
+    } else {
+      if (!InitializeSandbox())
+        return TerminateForFatalInitializationError();
+    }
+#endif
+
+    if (delegate_)
+      delegate_->SandboxInitialized(process_type);
+
+#if BUILDFLAG(USE_ZYGOTE_HANDLE)
+    if (process_type.empty()) {
+      // The sandbox host needs to be initialized before forking a thread to
+      // start the ServiceManager, and after setting up the sandbox and invoking
+      // SandboxInitialized().
+      InitializeZygoteSandboxForBrowserProcess(
+          *base::CommandLine::ForCurrentProcess());
+    }
+#endif  // BUILDFLAG(USE_ZYGOTE_HANDLE)
+
+    // Return -1 to indicate no early termination.
+    return -1;
   }
 
+  int Run() override {
+    DCHECK(is_initialized_);
+    DCHECK(!is_shutdown_);
+    const base::CommandLine& command_line =
+        *base::CommandLine::ForCurrentProcess();
+    std::string process_type =
+        command_line.GetSwitchValueASCII(switches::kProcessType);
+
+    // Run this logic on all child processes. Zygotes will run this at a later
+    // point in time when the command line has been updated.
+    std::unique_ptr<base::FieldTrialList> field_trial_list;
+    if (!process_type.empty() && process_type != switches::kZygoteProcess)
+      InitializeFieldTrialAndFeatureList(&field_trial_list);
+
+    MainFunctionParams main_params(command_line);
+    main_params.ui_task = ui_task_;
+    main_params.created_main_parts_closure = created_main_parts_closure_;
+#if defined(OS_WIN)
+    main_params.sandbox_info = &sandbox_info_;
+#elif defined(OS_MACOSX)
+    main_params.autorelease_pool = autorelease_pool_;
+#endif
+
+    return RunNamedProcessTypeMain(process_type, main_params, delegate_);
+  }
+
+  void Shutdown() override {
+    DCHECK(is_initialized_);
+    DCHECK(!is_shutdown_);
+
+    if (completed_basic_startup_ && delegate_) {
+      const base::CommandLine& command_line =
+          *base::CommandLine::ForCurrentProcess();
+      std::string process_type =
+          command_line.GetSwitchValueASCII(switches::kProcessType);
+
+      delegate_->ProcessExiting(process_type);
+    }
+
 #if defined(OS_WIN)
 #ifdef _CRTDBG_MAP_ALLOC
-  _CrtDumpMemoryLeaks();
+    _CrtDumpMemoryLeaks();
 #endif  // _CRTDBG_MAP_ALLOC
 #endif  // OS_WIN
 
-  exit_manager_.reset(nullptr);
+    exit_manager_.reset(nullptr);
 
-  delegate_ = nullptr;
-  is_shutdown_ = true;
-}
+    delegate_ = nullptr;
+    is_shutdown_ = true;
+  }
 
-#if !defined(CHROME_MULTIPLE_DLL_CHILD)
-scoped_refptr<base::SingleThreadTaskRunner>
-ContentMainRunnerImpl::GetServiceManagerTaskRunnerForEmbedderProcess() {
-  service_manager_thread_ = BrowserProcessSubThread::CreateIOThread();
-  return service_manager_thread_->task_runner();
-}
-#endif  // !defined(CHROME_MULTIPLE_DLL_CHILD)
+ private:
+  // True if the runner has been initialized.
+  bool is_initialized_ = false;
+
+  // True if the runner has been shut down.
+  bool is_shutdown_ = false;
+
+  // True if basic startup was completed.
+  bool completed_basic_startup_ = false;
+
+  // Used if the embedder doesn't set one.
+  ContentClient empty_content_client_;
+
+  // The delegate will outlive this object.
+  ContentMainDelegate* delegate_ = nullptr;
+
+  std::unique_ptr<base::AtExitManager> exit_manager_;
+#if defined(OS_WIN)
+  sandbox::SandboxInterfaceInfo sandbox_info_;
+#elif defined(OS_MACOSX)
+  base::mac::ScopedNSAutoreleasePool* autorelease_pool_ = nullptr;
+#endif
+
+  base::Closure* ui_task_ = nullptr;
+
+  CreatedMainPartsClosure* created_main_parts_closure_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(ContentMainRunnerImpl);
+};
 
 // static
 ContentMainRunner* ContentMainRunner::Create() {
-  return ContentMainRunnerImpl::Create();
+  return new ContentMainRunnerImpl();
 }
 
 }  // namespace content
diff --git a/content/app/content_main_runner_impl.h b/content/app/content_main_runner_impl.h
deleted file mode 100644
index 7fab6f7..0000000
--- a/content/app/content_main_runner_impl.h
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_APP_CONTENT_MAIN_RUNNER_IMPL_H_
-#define CONTENT_APP_CONTENT_MAIN_RUNNER_IMPL_H_
-
-#include <memory>
-
-#include "base/callback_forward.h"
-#include "base/memory/scoped_refptr.h"
-#include "build/build_config.h"
-#include "content/public/app/content_main.h"
-#include "content/public/app/content_main_runner.h"
-#include "content/public/common/content_client.h"
-
-#if defined(OS_WIN)
-#include "sandbox/win/src/sandbox_types.h"
-#elif defined(OS_MACOSX)
-#include "base/mac/scoped_nsautorelease_pool.h"
-#endif  // OS_WIN
-
-namespace base {
-class AtExitManager;
-class SingleThreadTaskRunner;
-}  // namespace base
-
-namespace content {
-class BrowserProcessSubThread;
-class ContentMainDelegate;
-struct ContentMainParams;
-
-class ContentMainRunnerImpl : public ContentMainRunner {
- public:
-  static ContentMainRunnerImpl* Create();
-
-  ContentMainRunnerImpl();
-  ~ContentMainRunnerImpl() override;
-
-  int TerminateForFatalInitializationError();
-
-  // ContentMainRunner:
-  int Initialize(const ContentMainParams& params) override;
-  int Run() override;
-  void Shutdown() override;
-
-#if !defined(CHROME_MULTIPLE_DLL_CHILD)
-  // Creates a thread and returns the SingleThreadTaskRunner on which
-  // ServiceManager should run.
-  scoped_refptr<base::SingleThreadTaskRunner>
-  GetServiceManagerTaskRunnerForEmbedderProcess();
-#endif  // !defined(CHROME_MULTIPLE_DLL_CHILD)
-
- private:
-  // True if the runner has been initialized.
-  bool is_initialized_ = false;
-
-  // True if the runner has been shut down.
-  bool is_shutdown_ = false;
-
-  // True if basic startup was completed.
-  bool completed_basic_startup_ = false;
-
-  // Used if the embedder doesn't set one.
-  ContentClient empty_content_client_;
-
-  // The delegate will outlive this object.
-  ContentMainDelegate* delegate_ = nullptr;
-
-  std::unique_ptr<base::AtExitManager> exit_manager_;
-
-#if defined(OS_WIN)
-  sandbox::SandboxInterfaceInfo sandbox_info_;
-#elif defined(OS_MACOSX)
-  base::mac::ScopedNSAutoreleasePool* autorelease_pool_ = nullptr;
-#endif
-
-  std::unique_ptr<BrowserProcessSubThread> service_manager_thread_;
-
-  base::Closure* ui_task_ = nullptr;
-
-  CreatedMainPartsClosure* created_main_parts_closure_ = nullptr;
-
-  DISALLOW_COPY_AND_ASSIGN(ContentMainRunnerImpl);
-};
-
-}  // namespace content
-
-#endif  // CONTENT_APP_CONTENT_MAIN_RUNNER_IMPL_H_
diff --git a/content/app/content_service_manager_main_delegate.cc b/content/app/content_service_manager_main_delegate.cc
index 119f39f..62dceb5 100644
--- a/content/app/content_service_manager_main_delegate.cc
+++ b/content/app/content_service_manager_main_delegate.cc
@@ -5,8 +5,8 @@
 #include "content/app/content_service_manager_main_delegate.h"
 
 #include "base/command_line.h"
-#include "content/app/content_main_runner_impl.h"
 #include "content/public/app/content_main_delegate.h"
+#include "content/public/app/content_main_runner.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/service_names.mojom.h"
 #include "services/service_manager/runner/common/client_util.h"
@@ -16,7 +16,7 @@
 ContentServiceManagerMainDelegate::ContentServiceManagerMainDelegate(
     const ContentMainParams& params)
     : content_main_params_(params),
-      content_main_runner_(ContentMainRunnerImpl::Create()) {}
+      content_main_runner_(ContentMainRunner::Create()) {}
 
 ContentServiceManagerMainDelegate::~ContentServiceManagerMainDelegate() =
     default;
@@ -124,11 +124,4 @@
   return nullptr;
 }
 
-#if !defined(CHROME_MULTIPLE_DLL_CHILD)
-scoped_refptr<base::SingleThreadTaskRunner> ContentServiceManagerMainDelegate::
-    GetServiceManagerTaskRunnerForEmbedderProcess() {
-  return content_main_runner_->GetServiceManagerTaskRunnerForEmbedderProcess();
-}
-#endif  // !defined(CHROME_MULTIPLE_DLL_CHILD)
-
 }  // namespace content
diff --git a/content/app/content_service_manager_main_delegate.h b/content/app/content_service_manager_main_delegate.h
index a6ab54a..4393a8f 100644
--- a/content/app/content_service_manager_main_delegate.h
+++ b/content/app/content_service_manager_main_delegate.h
@@ -8,15 +8,13 @@
 #include <memory>
 
 #include "base/macros.h"
-#include "base/memory/scoped_refptr.h"
-#include "base/single_thread_task_runner.h"
 #include "build/build_config.h"
 #include "content/public/app/content_main.h"
 #include "services/service_manager/embedder/main_delegate.h"
 
 namespace content {
 
-class ContentMainRunnerImpl;
+class ContentMainRunner;
 
 class ContentServiceManagerMainDelegate : public service_manager::MainDelegate {
  public:
@@ -25,10 +23,6 @@
 
   // service_manager::MainDelegate:
   int Initialize(const InitializeParams& params) override;
-#if !defined(CHROME_MULTIPLE_DLL_CHILD)
-  scoped_refptr<base::SingleThreadTaskRunner>
-  GetServiceManagerTaskRunnerForEmbedderProcess() override;
-#endif  // !defined(CHROME_MULTIPLE_DLL_CHILD)
   bool IsEmbedderSubprocess() override;
   int RunEmbedderProcess() override;
   void ShutDownEmbedderProcess() override;
@@ -48,7 +42,7 @@
 
  private:
   ContentMainParams content_main_params_;
-  std::unique_ptr<ContentMainRunnerImpl> content_main_runner_;
+  std::unique_ptr<ContentMainRunner> content_main_runner_;
 
 #if defined(OS_ANDROID)
   bool initialized_ = false;
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index ae3f5bc..2d20fa3 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -71,6 +71,7 @@
     "//content/browser/background_fetch:background_fetch_proto",
     "//content/browser/background_sync:background_sync_proto",
     "//content/browser/cache_storage:cache_storage_proto",
+    "//content/browser/cookie_store:cookie_store_proto",
     "//content/browser/devtools:devtools_resources",
     "//content/browser/devtools:protocol_sources",
     "//content/browser/dom_storage:local_storage_proto",
@@ -500,7 +501,6 @@
     "browser_main_loop.cc",
     "browser_main_loop.h",
     "browser_main_runner_impl.cc",
-    "browser_main_runner_impl.h",
     "browser_plugin/browser_plugin_embedder.cc",
     "browser_plugin/browser_plugin_embedder.h",
     "browser_plugin/browser_plugin_guest.cc",
@@ -576,6 +576,14 @@
     # needed on all platforms.
     "compositor/surface_utils.cc",
     "compositor/surface_utils.h",
+    "cookie_store/cookie_change_subscription.cc",
+    "cookie_store/cookie_change_subscription.h",
+    "cookie_store/cookie_store_context.cc",
+    "cookie_store/cookie_store_context.h",
+    "cookie_store/cookie_store_host.cc",
+    "cookie_store/cookie_store_host.h",
+    "cookie_store/cookie_store_manager.cc",
+    "cookie_store/cookie_store_manager.h",
     "dedicated_worker/dedicated_worker_host.cc",
     "dedicated_worker/dedicated_worker_host.h",
     "devtools/browser_devtools_agent_host.cc",
@@ -1060,6 +1068,10 @@
     "media/audible_metrics.h",
     "media/audio_input_stream_broker.cc",
     "media/audio_input_stream_broker.h",
+    "media/audio_loopback_stream_broker.cc",
+    "media/audio_loopback_stream_broker.h",
+    "media/audio_muting_session.cc",
+    "media/audio_muting_session.h",
     "media/audio_output_stream_broker.cc",
     "media/audio_output_stream_broker.h",
     "media/audio_stream_broker.cc",
diff --git a/content/browser/DEPS b/content/browser/DEPS
index 4630296..98d3fd6 100644
--- a/content/browser/DEPS
+++ b/content/browser/DEPS
@@ -127,6 +127,7 @@
   "+third_party/blink/public/platform/modules/bluetooth/web_bluetooth.mojom.h",
   "+third_party/blink/public/platform/modules/broadcastchannel/broadcast_channel.mojom.h",
   "+third_party/blink/public/platform/modules/cache_storage/cache_storage.mojom.h",
+  "+third_party/blink/public/platform/modules/cookie_store/cookie_store.mojom.h",
   "+third_party/blink/public/platform/modules/geolocation/geolocation_service.mojom.h",
   "+third_party/blink/public/platform/modules/installedapp/installed_app_provider.mojom.h",
   "+third_party/blink/public/platform/modules/installedapp/related_application.mojom.h",
diff --git a/content/browser/browser_main.cc b/content/browser/browser_main.cc
index f06e59b..d706269 100644
--- a/content/browser/browser_main.cc
+++ b/content/browser/browser_main.cc
@@ -7,9 +7,8 @@
 #include <memory>
 
 #include "base/trace_event/trace_event.h"
-#include "content/browser/browser_main_runner_impl.h"
-#include "content/browser/browser_process_sub_thread.h"
 #include "content/common/content_constants_internal.h"
+#include "content/public/browser/browser_main_runner.h"
 
 namespace content {
 
@@ -31,20 +30,16 @@
 }  // namespace
 
 // Main routine for running as the Browser process.
-int BrowserMain(
-    const MainFunctionParams& parameters,
-    std::unique_ptr<BrowserProcessSubThread> service_manager_thread) {
+int BrowserMain(const MainFunctionParams& parameters) {
   ScopedBrowserMainEvent scoped_browser_main_event;
 
   base::trace_event::TraceLog::GetInstance()->set_process_name("Browser");
   base::trace_event::TraceLog::GetInstance()->SetProcessSortIndex(
       kTraceEventBrowserProcessSortIndex);
 
-  std::unique_ptr<BrowserMainRunnerImpl> main_runner(
-      BrowserMainRunnerImpl::Create());
+  std::unique_ptr<BrowserMainRunner> main_runner(BrowserMainRunner::Create());
 
-  int exit_code =
-      main_runner->Initialize(parameters, std::move(service_manager_thread));
+  int exit_code = main_runner->Initialize(parameters);
   if (exit_code >= 0)
     return exit_code;
 
diff --git a/content/browser/browser_main.h b/content/browser/browser_main.h
index 02f3af60..8fab146 100644
--- a/content/browser/browser_main.h
+++ b/content/browser/browser_main.h
@@ -5,18 +5,13 @@
 #ifndef CONTENT_BROWSER_BROWSER_MAIN_H_
 #define CONTENT_BROWSER_BROWSER_MAIN_H_
 
-#include <memory>
-
 #include "content/common/content_export.h"
 
 namespace content {
 
-class BrowserProcessSubThread;
 struct MainFunctionParams;
 
-CONTENT_EXPORT int BrowserMain(
-    const content::MainFunctionParams& parameters,
-    std::unique_ptr<BrowserProcessSubThread> service_manager_thread);
+CONTENT_EXPORT int BrowserMain(const content::MainFunctionParams& parameters);
 
 }  // namespace content
 
diff --git a/content/browser/browser_main_loop.cc b/content/browser/browser_main_loop.cc
index 61b2e9b..efa1f5b1 100644
--- a/content/browser/browser_main_loop.cc
+++ b/content/browser/browser_main_loop.cc
@@ -56,6 +56,7 @@
 #include "components/viz/service/display_embedder/compositing_mode_reporter_impl.h"
 #include "components/viz/service/display_embedder/server_shared_bitmap_manager.h"
 #include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
+#include "content/browser/browser_process_sub_thread.h"
 #include "content/browser/browser_thread_impl.h"
 #include "content/browser/child_process_security_policy_impl.h"
 #include "content/browser/compositor/gpu_process_transport_factory.h"
@@ -524,13 +525,9 @@
   g_current_browser_main_loop = nullptr;
 }
 
-void BrowserMainLoop::Init(
-    std::unique_ptr<BrowserProcessSubThread> service_manager_thread) {
+void BrowserMainLoop::Init() {
   TRACE_EVENT0("startup", "BrowserMainLoop::Init");
 
-  // This is always invoked before |io_thread_| is initialized (i.e. never
-  // resets it).
-  io_thread_ = std::move(service_manager_thread);
   parts_.reset(
       GetContentClient()->browser()->CreateBrowserMainParts(parameters_));
 }
@@ -661,6 +658,11 @@
 
 void BrowserMainLoop::PostMainMessageLoopStart() {
   {
+    TRACE_EVENT0("startup",
+                 "BrowserMainLoop::Subsystem:CreateBrowserThread::IO");
+    InitializeIOThread();
+  }
+  {
     TRACE_EVENT0("startup", "BrowserMainLoop::Subsystem:SystemMonitor");
     system_monitor_.reset(new base::SystemMonitor);
   }
@@ -925,14 +927,12 @@
         *task_scheduler_init_params.get());
   }
 
-  // The |io_thread| can have optionally been injected into Init(), but if not,
-  // create it here. Thre thread is only tagged as BrowserThread::IO here in
-  // order to prevent any code from statically posting to it before
+  // The thread used for BrowserThread::IO is created in
+  // |PostMainMessageLoopStart()|, but it's only tagged as BrowserThread::IO
+  // here in order to prevent any code from statically posting to it before
   // CreateThreads() (as such maintaining the invariant that PreCreateThreads()
   // et al. "happen-before" BrowserThread::IO is "brought up").
-  if (!io_thread_) {
-    io_thread_ = BrowserProcessSubThread::CreateIOThread();
-  }
+  DCHECK(io_thread_);
   io_thread_->RegisterAsBrowserThread();
 
   created_threads_ = true;
@@ -1136,6 +1136,10 @@
   return audio_service_runner_.get();
 }
 
+void BrowserMainLoop::InitializeIOThreadForTesting() {
+  InitializeIOThread();
+}
+
 #if !defined(OS_ANDROID)
 viz::FrameSinkManagerImpl* BrowserMainLoop::GetFrameSinkManager() const {
   return frame_sink_manager_impl_.get();
@@ -1489,6 +1493,21 @@
 #endif
 }
 
+void BrowserMainLoop::InitializeIOThread() {
+  base::Thread::Options options;
+  options.message_loop_type = base::MessageLoop::TYPE_IO;
+#if defined(OS_ANDROID) || defined(OS_CHROMEOS)
+  // Up the priority of the |io_thread_| as some of its IPCs relate to
+  // display tasks.
+  options.priority = base::ThreadPriority::DISPLAY;
+#endif
+
+  io_thread_ = std::make_unique<BrowserProcessSubThread>(BrowserThread::IO);
+
+  if (!io_thread_->StartWithOptions(options))
+    LOG(FATAL) << "Failed to start BrowserThread::IO";
+}
+
 void BrowserMainLoop::InitializeMojo() {
   if (!parsed_command_line_.HasSwitch(switches::kSingleProcess)) {
     // Disallow mojo sync calls in the browser process. Note that we allow sync
diff --git a/content/browser/browser_main_loop.h b/content/browser/browser_main_loop.h
index bed92782..b25ed2a 100644
--- a/content/browser/browser_main_loop.h
+++ b/content/browser/browser_main_loop.h
@@ -13,7 +13,6 @@
 #include "base/memory/ref_counted.h"
 #include "base/timer/timer.h"
 #include "build/build_config.h"
-#include "content/browser/browser_process_sub_thread.h"
 #include "content/public/browser/browser_main_runner.h"
 #include "media/media_buildflags.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
@@ -96,6 +95,7 @@
 namespace content {
 class BrowserMainParts;
 class BrowserOnlineStateObserver;
+class BrowserProcessSubThread;
 class BrowserThreadImpl;
 class LoaderDelegateImpl;
 class MediaStreamManager;
@@ -129,10 +129,7 @@
   explicit BrowserMainLoop(const MainFunctionParams& parameters);
   virtual ~BrowserMainLoop();
 
-  // |service_manager_thread| is optional. If set, it will be registered as
-  // BrowserThread::IO in CreateThreads() instead of creating a brand new
-  // thread.
-  void Init(std::unique_ptr<BrowserProcessSubThread> service_manager_thread);
+  void Init();
 
   // Return value is exit status. Anything other than RESULT_CODE_NORMAL_EXIT
   // is considered an error.
@@ -160,6 +157,8 @@
   // through stopping threads to PostDestroyThreads.
   void ShutdownThreadsAndCleanUp();
 
+  void InitializeIOThreadForTesting();
+
   int GetResultCode() const { return result_code_; }
 
   media::AudioManager* audio_manager() const { return audio_manager_.get(); }
@@ -253,6 +252,10 @@
 
   void MainMessageLoopRun();
 
+  // Initializes |io_thread_|. It will not be promoted to BrowserThread::IO
+  // until CreateThreads().
+  void InitializeIOThread();
+
   void InitializeMojo();
   void InitStartupTracingForDuration();
   void EndStartupTracing();
diff --git a/content/browser/browser_main_loop_unittest.cc b/content/browser/browser_main_loop_unittest.cc
index 24e38ba8..728331b 100644
--- a/content/browser/browser_main_loop_unittest.cc
+++ b/content/browser/browser_main_loop_unittest.cc
@@ -29,7 +29,7 @@
         *scoped_command_line.GetProcessCommandLine());
     BrowserMainLoop browser_main_loop(main_function_params);
     browser_main_loop.MainMessageLoopStart();
-    browser_main_loop.Init(nullptr);
+    browser_main_loop.InitializeIOThreadForTesting();
     browser_main_loop.CreateThreads();
     EXPECT_GE(base::TaskScheduler::GetInstance()
                   ->GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated(
diff --git a/content/browser/browser_main_runner_impl.cc b/content/browser/browser_main_runner_impl.cc
index 6381234..8e5ef3c 100644
--- a/content/browser/browser_main_runner_impl.cc
+++ b/content/browser/browser_main_runner_impl.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/browser/browser_main_runner_impl.h"
+#include "content/public/browser/browser_main_runner.h"
 
 #include "base/base_switches.h"
 #include "base/command_line.h"
@@ -10,7 +10,7 @@
 #include "base/debug/leak_annotations.h"
 #include "base/lazy_instance.h"
 #include "base/logging.h"
-#include "base/message_loop/message_loop.h"
+#include "base/macros.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/run_loop.h"
 #include "base/sampling_heap_profiler/sampling_heap_profiler.h"
@@ -23,7 +23,6 @@
 #include "components/tracing/common/trace_startup_config.h"
 #include "components/tracing/common/tracing_switches.h"
 #include "content/browser/browser_main_loop.h"
-#include "content/browser/browser_process_sub_thread.h"
 #include "content/browser/browser_shutdown_profile_dumper.h"
 #include "content/browser/notification_service_impl.h"
 #include "content/common/content_switches_internal.h"
@@ -43,208 +42,217 @@
 #endif
 
 namespace content {
+
 namespace {
 
 base::LazyInstance<base::AtomicFlag>::Leaky g_exited_main_message_loop;
 
 }  // namespace
 
-// static
-BrowserMainRunnerImpl* BrowserMainRunnerImpl::Create() {
-  return new BrowserMainRunnerImpl();
-}
+class BrowserMainRunnerImpl : public BrowserMainRunner {
+ public:
+  BrowserMainRunnerImpl()
+      : initialization_started_(false), is_shutdown_(false) {}
 
-BrowserMainRunnerImpl::BrowserMainRunnerImpl()
-    : initialization_started_(false), is_shutdown_(false) {}
+  ~BrowserMainRunnerImpl() override {
+    if (initialization_started_ && !is_shutdown_)
+      Shutdown();
+  }
 
-BrowserMainRunnerImpl::~BrowserMainRunnerImpl() {
-  if (initialization_started_ && !is_shutdown_)
-    Shutdown();
-}
+  int Initialize(const MainFunctionParams& parameters) override {
+    SCOPED_UMA_HISTOGRAM_LONG_TIMER(
+        "Startup.BrowserMainRunnerImplInitializeLongTime");
+    TRACE_EVENT0("startup", "BrowserMainRunnerImpl::Initialize");
 
-int BrowserMainRunnerImpl::Initialize(const MainFunctionParams& parameters) {
-  return Initialize(parameters, nullptr);
-}
+    // On Android we normally initialize the browser in a series of UI thread
+    // tasks. While this is happening a second request can come from the OS or
+    // another application to start the browser. If this happens then we must
+    // not run these parts of initialization twice.
+    if (!initialization_started_) {
+      initialization_started_ = true;
 
-int BrowserMainRunnerImpl::Initialize(
-    const MainFunctionParams& parameters,
-    std::unique_ptr<BrowserProcessSubThread> service_manager_thread) {
-  SCOPED_UMA_HISTOGRAM_LONG_TIMER(
-      "Startup.BrowserMainRunnerImplInitializeLongTime");
-  TRACE_EVENT0("startup", "BrowserMainRunnerImpl::Initialize");
+      const base::TimeTicks start_time_step1 = base::TimeTicks::Now();
 
-  // On Android we normally initialize the browser in a series of UI thread
-  // tasks. While this is happening a second request can come from the OS or
-  // another application to start the browser. If this happens then we must
-  // not run these parts of initialization twice.
-  if (!initialization_started_) {
-    initialization_started_ = true;
+      base::SamplingHeapProfiler::InitTLSSlot();
+      if (parameters.command_line.HasSwitch(switches::kSamplingHeapProfiler)) {
+        base::SamplingHeapProfiler* profiler =
+            base::SamplingHeapProfiler::GetInstance();
+        unsigned sampling_interval = 0;
+        bool parsed =
+            base::StringToUint(parameters.command_line.GetSwitchValueASCII(
+                                   switches::kSamplingHeapProfiler),
+                               &sampling_interval);
+        if (parsed && sampling_interval > 0)
+          profiler->SetSamplingInterval(sampling_interval * 1024);
+        profiler->Start();
+      }
 
-    const base::TimeTicks start_time_step1 = base::TimeTicks::Now();
+      SkGraphics::Init();
 
-    base::SamplingHeapProfiler::InitTLSSlot();
-    if (parameters.command_line.HasSwitch(switches::kSamplingHeapProfiler)) {
-      base::SamplingHeapProfiler* profiler =
-          base::SamplingHeapProfiler::GetInstance();
-      unsigned sampling_interval = 0;
-      bool parsed =
-          base::StringToUint(parameters.command_line.GetSwitchValueASCII(
-                                 switches::kSamplingHeapProfiler),
-                             &sampling_interval);
-      if (parsed && sampling_interval > 0)
-        profiler->SetSamplingInterval(sampling_interval * 1024);
-      profiler->Start();
-    }
+      if (parameters.command_line.HasSwitch(switches::kWaitForDebugger))
+        base::debug::WaitForDebugger(60, true);
 
-    SkGraphics::Init();
+      if (parameters.command_line.HasSwitch(switches::kBrowserStartupDialog))
+        WaitForDebugger("Browser");
 
-    if (parameters.command_line.HasSwitch(switches::kWaitForDebugger))
-      base::debug::WaitForDebugger(60, true);
-
-    if (parameters.command_line.HasSwitch(switches::kBrowserStartupDialog))
-      WaitForDebugger("Browser");
-
-    notification_service_.reset(new NotificationServiceImpl);
+      notification_service_.reset(new NotificationServiceImpl);
 
 #if defined(OS_WIN)
-    // Ole must be initialized before starting message pump, so that TSF
-    // (Text Services Framework) module can interact with the message pump
-    // on Windows 8 Metro mode.
-    ole_initializer_.reset(new ui::ScopedOleInitializer);
-    // Enable DirectWrite font rendering if needed.
-    gfx::win::MaybeInitializeDirectWrite();
+      // Ole must be initialized before starting message pump, so that TSF
+      // (Text Services Framework) module can interact with the message pump
+      // on Windows 8 Metro mode.
+      ole_initializer_.reset(new ui::ScopedOleInitializer);
+      // Enable DirectWrite font rendering if needed.
+      gfx::win::MaybeInitializeDirectWrite();
 #endif  // OS_WIN
 
-    main_loop_.reset(new BrowserMainLoop(parameters));
+      main_loop_.reset(new BrowserMainLoop(parameters));
 
-    main_loop_->Init(std::move(service_manager_thread));
+      main_loop_->Init();
 
-    if (parameters.created_main_parts_closure) {
-      parameters.created_main_parts_closure->Run(main_loop_->parts());
-      delete parameters.created_main_parts_closure;
+      if (parameters.created_main_parts_closure) {
+        parameters.created_main_parts_closure->Run(main_loop_->parts());
+        delete parameters.created_main_parts_closure;
+      }
+
+      const int early_init_error_code = main_loop_->EarlyInitialization();
+      if (early_init_error_code > 0)
+        return early_init_error_code;
+
+      // Must happen before we try to use a message loop or display any UI.
+      if (!main_loop_->InitializeToolkit())
+        return 1;
+
+      main_loop_->PreMainMessageLoopStart();
+      main_loop_->MainMessageLoopStart();
+      main_loop_->PostMainMessageLoopStart();
+
+      // WARNING: If we get a WM_ENDSESSION, objects created on the stack here
+      // are NOT deleted. If you need something to run during WM_ENDSESSION add
+      // it to browser_shutdown::Shutdown or BrowserProcess::EndSession.
+
+      ui::InitializeInputMethod();
+      UMA_HISTOGRAM_TIMES("Startup.BrowserMainRunnerImplInitializeStep1Time",
+                          base::TimeTicks::Now() - start_time_step1);
     }
+    const base::TimeTicks start_time_step2 = base::TimeTicks::Now();
+    main_loop_->CreateStartupTasks();
+    int result_code = main_loop_->GetResultCode();
+    if (result_code > 0)
+      return result_code;
 
-    const int early_init_error_code = main_loop_->EarlyInitialization();
-    if (early_init_error_code > 0)
-      return early_init_error_code;
+    UMA_HISTOGRAM_TIMES("Startup.BrowserMainRunnerImplInitializeStep2Time",
+                        base::TimeTicks::Now() - start_time_step2);
 
-    // Must happen before we try to use a message loop or display any UI.
-    if (!main_loop_->InitializeToolkit())
-      return 1;
-
-    main_loop_->PreMainMessageLoopStart();
-    main_loop_->MainMessageLoopStart();
-    main_loop_->PostMainMessageLoopStart();
-
-    // WARNING: If we get a WM_ENDSESSION, objects created on the stack here
-    // are NOT deleted. If you need something to run during WM_ENDSESSION add it
-    // to browser_shutdown::Shutdown or BrowserProcess::EndSession.
-
-    ui::InitializeInputMethod();
-    UMA_HISTOGRAM_TIMES("Startup.BrowserMainRunnerImplInitializeStep1Time",
-                        base::TimeTicks::Now() - start_time_step1);
+    // Return -1 to indicate no early termination.
+    return -1;
   }
-  const base::TimeTicks start_time_step2 = base::TimeTicks::Now();
-  main_loop_->CreateStartupTasks();
-  int result_code = main_loop_->GetResultCode();
-  if (result_code > 0)
-    return result_code;
-
-  UMA_HISTOGRAM_TIMES("Startup.BrowserMainRunnerImplInitializeStep2Time",
-                      base::TimeTicks::Now() - start_time_step2);
-
-  // Return -1 to indicate no early termination.
-  return -1;
-}
 
 #if defined(OS_ANDROID)
-void BrowserMainRunnerImpl::SynchronouslyFlushStartupTasks() {
-  main_loop_->SynchronouslyFlushStartupTasks();
-}
+  void SynchronouslyFlushStartupTasks() override {
+    main_loop_->SynchronouslyFlushStartupTasks();
+  }
 #endif
 
-int BrowserMainRunnerImpl::Run() {
-  DCHECK(initialization_started_);
-  DCHECK(!is_shutdown_);
-  main_loop_->RunMainMessageLoopParts();
-  return main_loop_->GetResultCode();
-}
+  int Run() override {
+    DCHECK(initialization_started_);
+    DCHECK(!is_shutdown_);
+    main_loop_->RunMainMessageLoopParts();
+    return main_loop_->GetResultCode();
+  }
 
-void BrowserMainRunnerImpl::Shutdown() {
-  DCHECK(initialization_started_);
-  DCHECK(!is_shutdown_);
+  void Shutdown() override {
+    DCHECK(initialization_started_);
+    DCHECK(!is_shutdown_);
 
 #ifdef LEAK_SANITIZER
-  // Invoke leak detection now, to avoid dealing with shutdown-only leaks.
-  // Normally this will have already happened in
-  // BroserProcessImpl::ReleaseModule(), so this call has no effect. This is
-  // only for processes which do not instantiate a BrowserProcess.
-  // If leaks are found, the process will exit here.
-  __lsan_do_leak_check();
+    // Invoke leak detection now, to avoid dealing with shutdown-only leaks.
+    // Normally this will have already happened in
+    // BroserProcessImpl::ReleaseModule(), so this call has no effect. This is
+    // only for processes which do not instantiate a BrowserProcess.
+    // If leaks are found, the process will exit here.
+    __lsan_do_leak_check();
 #endif
 
-  main_loop_->PreShutdown();
+    main_loop_->PreShutdown();
 
-  // If startup tracing has not been finished yet, replace it's dumper
-  // with special version, which would save trace file on exit (i.e.
-  // startup tracing becomes a version of shutdown tracing).
-  // There are two cases:
-  // 1. Startup duration is not reached.
-  // 2. Or startup duration is not specified for --trace-config-file flag.
-  std::unique_ptr<BrowserShutdownProfileDumper> startup_profiler;
-  if (tracing::TraceStartupConfig::GetInstance()
-          ->IsTracingStartupForDuration()) {
-    main_loop_->StopStartupTracingTimer();
-    if (main_loop_->startup_trace_file() !=
-        base::FilePath().AppendASCII("none")) {
-      startup_profiler.reset(
-          new BrowserShutdownProfileDumper(main_loop_->startup_trace_file()));
+    // If startup tracing has not been finished yet, replace it's dumper
+    // with special version, which would save trace file on exit (i.e.
+    // startup tracing becomes a version of shutdown tracing).
+    // There are two cases:
+    // 1. Startup duration is not reached.
+    // 2. Or startup duration is not specified for --trace-config-file flag.
+    std::unique_ptr<BrowserShutdownProfileDumper> startup_profiler;
+    if (tracing::TraceStartupConfig::GetInstance()
+            ->IsTracingStartupForDuration()) {
+      main_loop_->StopStartupTracingTimer();
+      if (main_loop_->startup_trace_file() !=
+          base::FilePath().AppendASCII("none")) {
+        startup_profiler.reset(
+            new BrowserShutdownProfileDumper(main_loop_->startup_trace_file()));
+      }
+    } else if (tracing::TraceStartupConfig::GetInstance()->IsEnabled()) {
+      base::FilePath result_file = main_loop_->GetStartupTraceFileName();
+      startup_profiler.reset(new BrowserShutdownProfileDumper(result_file));
     }
-  } else if (tracing::TraceStartupConfig::GetInstance()->IsEnabled()) {
-    base::FilePath result_file = main_loop_->GetStartupTraceFileName();
-    startup_profiler.reset(new BrowserShutdownProfileDumper(result_file));
-  }
 
-  // The shutdown tracing got enabled in AttemptUserExit earlier, but someone
-  // needs to write the result to disc. For that a dumper needs to get created
-  // which will dump the traces to disc when it gets destroyed.
-  const base::CommandLine& command_line =
-      *base::CommandLine::ForCurrentProcess();
-  std::unique_ptr<BrowserShutdownProfileDumper> shutdown_profiler;
-  if (command_line.HasSwitch(switches::kTraceShutdown)) {
-    shutdown_profiler.reset(new BrowserShutdownProfileDumper(
-        BrowserShutdownProfileDumper::GetShutdownProfileFileName()));
-  }
+    // The shutdown tracing got enabled in AttemptUserExit earlier, but someone
+    // needs to write the result to disc. For that a dumper needs to get created
+    // which will dump the traces to disc when it gets destroyed.
+    const base::CommandLine& command_line =
+        *base::CommandLine::ForCurrentProcess();
+    std::unique_ptr<BrowserShutdownProfileDumper> shutdown_profiler;
+    if (command_line.HasSwitch(switches::kTraceShutdown)) {
+      shutdown_profiler.reset(new BrowserShutdownProfileDumper(
+          BrowserShutdownProfileDumper::GetShutdownProfileFileName()));
+    }
 
-  {
-    // The trace event has to stay between profiler creation and destruction.
-    TRACE_EVENT0("shutdown", "BrowserMainRunner");
-    g_exited_main_message_loop.Get().Set();
+    {
+      // The trace event has to stay between profiler creation and destruction.
+      TRACE_EVENT0("shutdown", "BrowserMainRunner");
+      g_exited_main_message_loop.Get().Set();
 
-    main_loop_->ShutdownThreadsAndCleanUp();
+      main_loop_->ShutdownThreadsAndCleanUp();
 
-    ui::ShutdownInputMethod();
+      ui::ShutdownInputMethod();
 #if defined(OS_WIN)
-    ole_initializer_.reset(NULL);
+      ole_initializer_.reset(NULL);
 #endif
 #if defined(OS_ANDROID)
-    // Forcefully terminates the RunLoop inside MessagePumpForUI, ensuring
-    // proper shutdown for content_browsertests. Shutdown() is not used by
-    // the actual browser.
-    if (base::RunLoop::IsRunningOnCurrentThread())
-      base::RunLoop::QuitCurrentDeprecated();
+      // Forcefully terminates the RunLoop inside MessagePumpForUI, ensuring
+      // proper shutdown for content_browsertests. Shutdown() is not used by
+      // the actual browser.
+      if (base::RunLoop::IsRunningOnCurrentThread())
+        base::RunLoop::QuitCurrentDeprecated();
 #endif
-    main_loop_.reset(nullptr);
+      main_loop_.reset(nullptr);
 
-    notification_service_.reset(nullptr);
+      notification_service_.reset(nullptr);
 
-    is_shutdown_ = true;
+      is_shutdown_ = true;
+    }
   }
-}
+
+ protected:
+  // True if we have started to initialize the runner.
+  bool initialization_started_;
+
+  // True if the runner has been shut down.
+  bool is_shutdown_;
+
+  std::unique_ptr<NotificationServiceImpl> notification_service_;
+  std::unique_ptr<BrowserMainLoop> main_loop_;
+#if defined(OS_WIN)
+  std::unique_ptr<ui::ScopedOleInitializer> ole_initializer_;
+#endif
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(BrowserMainRunnerImpl);
+};
 
 // static
 BrowserMainRunner* BrowserMainRunner::Create() {
-  return BrowserMainRunnerImpl::Create();
+  return new BrowserMainRunnerImpl();
 }
 
 // static
diff --git a/content/browser/browser_main_runner_impl.h b/content/browser/browser_main_runner_impl.h
deleted file mode 100644
index adb084f..0000000
--- a/content/browser/browser_main_runner_impl.h
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_BROWSER_BROWSER_MAIN_RUNNER_IMPL_H_
-#define CONTENT_BROWSER_BROWSER_MAIN_RUNNER_IMPL_H_
-
-#include <memory>
-
-#include "base/macros.h"
-#include "build/build_config.h"
-#include "content/public/browser/browser_main_runner.h"
-
-#if defined(OS_WIN)
-namespace ui {
-class ScopedOleInitializer;
-}
-#endif
-
-namespace content {
-
-class BrowserProcessSubThread;
-class BrowserMainLoop;
-class NotificationServiceImpl;
-
-class BrowserMainRunnerImpl : public BrowserMainRunner {
- public:
-  static BrowserMainRunnerImpl* Create();
-
-  BrowserMainRunnerImpl();
-  ~BrowserMainRunnerImpl() override;
-
-  // BrowserMainRunner:
-  int Initialize(const MainFunctionParams& parameters) override;
-#if defined(OS_ANDROID)
-  void SynchronouslyFlushStartupTasks() override;
-#endif
-  int Run() override;
-  void Shutdown() override;
-
-  // Initialize all necessary browser state with a |service_manager_thread|
-  // on which ServiceManager is currently running.
-  int Initialize(
-      const MainFunctionParams& parameters,
-      std::unique_ptr<BrowserProcessSubThread> service_manager_thread);
-
- private:
-  // True if we have started to initialize the runner.
-  bool initialization_started_;
-
-  // True if the runner has been shut down.
-  bool is_shutdown_;
-
-  std::unique_ptr<NotificationServiceImpl> notification_service_;
-  std::unique_ptr<BrowserMainLoop> main_loop_;
-#if defined(OS_WIN)
-  std::unique_ptr<ui::ScopedOleInitializer> ole_initializer_;
-#endif
-
-  DISALLOW_COPY_AND_ASSIGN(BrowserMainRunnerImpl);
-};
-
-}  // namespace content
-
-#endif  // CONTENT_BROWSER_BROWSER_MAIN_RUNNER_IMPL_H_
diff --git a/content/browser/browser_process_sub_thread.cc b/content/browser/browser_process_sub_thread.cc
index edc608a..0902fb6 100644
--- a/content/browser/browser_process_sub_thread.cc
+++ b/content/browser/browser_process_sub_thread.cc
@@ -70,24 +70,6 @@
   is_blocking_allowed_for_testing_ = true;
 }
 
-// static
-std::unique_ptr<BrowserProcessSubThread>
-BrowserProcessSubThread::CreateIOThread() {
-  TRACE_EVENT0("startup", "BrowserProcessSubThread::CreateIOThread");
-  base::Thread::Options options;
-  options.message_loop_type = base::MessageLoop::TYPE_IO;
-#if defined(OS_ANDROID) || defined(OS_CHROMEOS)
-  // Up the priority of the |io_thread_| as some of its IPCs relate to
-  // display tasks.
-  options.priority = base::ThreadPriority::DISPLAY;
-#endif
-  std::unique_ptr<BrowserProcessSubThread> io_thread(
-      new BrowserProcessSubThread(BrowserThread::IO));
-  if (!io_thread->StartWithOptions(options))
-    LOG(FATAL) << "Failed to start BrowserThread:IO";
-  return io_thread;
-}
-
 void BrowserProcessSubThread::Init() {
   DCHECK_CALLED_ON_VALID_THREAD(browser_thread_checker_);
 
diff --git a/content/browser/browser_process_sub_thread.h b/content/browser/browser_process_sub_thread.h
index edc2238..f4b8799 100644
--- a/content/browser/browser_process_sub_thread.h
+++ b/content/browser/browser_process_sub_thread.h
@@ -53,10 +53,6 @@
   // starting this BrowserProcessSubThread.
   void AllowBlockingForTesting();
 
-  // Creates and starts the IO thread. It should not be promoted to
-  // BrowserThread::IO until BrowserMainLoop::CreateThreads().
-  static std::unique_ptr<BrowserProcessSubThread> CreateIOThread();
-
  protected:
   void Init() override;
   void Run(base::RunLoop* run_loop) override;
diff --git a/content/browser/cookie_store/BUILD.gn b/content/browser/cookie_store/BUILD.gn
new file mode 100644
index 0000000..d5dbdd7
--- /dev/null
+++ b/content/browser/cookie_store/BUILD.gn
@@ -0,0 +1,11 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/protobuf/proto_library.gni")
+
+proto_library("cookie_store_proto") {
+  sources = [
+    "cookie_change_subscriptions.proto",
+  ]
+}
diff --git a/content/browser/cookie_store/OWNERS b/content/browser/cookie_store/OWNERS
new file mode 100644
index 0000000..b23c10fd
--- /dev/null
+++ b/content/browser/cookie_store/OWNERS
@@ -0,0 +1,8 @@
+# Primary
+pwnall@chromium.org
+
+# Secondary
+jsbell@chromium.org
+
+# TEAM: storage-dev@chromium.org
+# COMPONENT: Blink>Storage>CookiesAPI
diff --git a/content/browser/cookie_store/cookie_change_subscription.cc b/content/browser/cookie_store/cookie_change_subscription.cc
new file mode 100644
index 0000000..78c4101
--- /dev/null
+++ b/content/browser/cookie_store/cookie_change_subscription.cc
@@ -0,0 +1,182 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/cookie_store/cookie_change_subscription.h"
+
+#include <utility>
+
+#include "content/browser/cookie_store/cookie_change_subscriptions.pb.h"
+
+namespace content {
+
+namespace {
+
+#define STATIC_ASSERT_ENUM(a, b)                            \
+  static_assert(static_cast<int>(a) == static_cast<int>(b), \
+                "mismatching enums: " #a)
+
+STATIC_ASSERT_ENUM(network::mojom::CookieMatchType::EQUALS,
+                   proto::CookieMatchType::EQUALS);
+STATIC_ASSERT_ENUM(network::mojom::CookieMatchType::STARTS_WITH,
+                   proto::CookieMatchType::STARTS_WITH);
+
+proto::CookieMatchType CookieMatchTypeToProto(
+    network::mojom::CookieMatchType match_type) {
+  switch (match_type) {
+    case network::mojom::CookieMatchType::EQUALS:
+      return proto::CookieMatchType::EQUALS;
+    case ::network::mojom::CookieMatchType::STARTS_WITH:
+      return proto::CookieMatchType::STARTS_WITH;
+  }
+  NOTREACHED();
+  return proto::CookieMatchType::EQUALS;
+}
+
+network::mojom::CookieMatchType CookieMatchTypeFromProto(
+    proto::CookieMatchType match_type_proto) {
+  switch (match_type_proto) {
+    case proto::CookieMatchType::EQUALS:
+      return network::mojom::CookieMatchType::EQUALS;
+    case proto::CookieMatchType::STARTS_WITH:
+      return ::network::mojom::CookieMatchType::STARTS_WITH;
+  }
+  NOTREACHED();
+  return network::mojom::CookieMatchType::EQUALS;
+}
+
+}  // namespace
+
+// static
+base::Optional<std::vector<CookieChangeSubscription>>
+CookieChangeSubscription::DeserializeVector(
+    const std::string& proto_string,
+    int64_t service_worker_registration_id) {
+  proto::CookieChangeSubscriptionsProto subscriptions_proto;
+  if (!subscriptions_proto.ParseFromString(proto_string))
+    return base::nullopt;
+
+  std::vector<CookieChangeSubscription> subscriptions;
+  int subscription_count = subscriptions_proto.subscriptions_size();
+  subscriptions.reserve(subscription_count);
+  for (int i = 0; i < subscription_count; ++i) {
+    base::Optional<CookieChangeSubscription> subscription_opt =
+        CookieChangeSubscription::Create(subscriptions_proto.subscriptions(i),
+                                         service_worker_registration_id);
+    if (!subscription_opt.has_value())
+      continue;
+    subscriptions.emplace_back(std::move(subscription_opt).value());
+  }
+
+  return base::make_optional(
+      std::vector<CookieChangeSubscription>(std::move(subscriptions)));
+}
+
+// static
+std::vector<CookieChangeSubscription> CookieChangeSubscription::FromMojoVector(
+    std::vector<blink::mojom::CookieChangeSubscriptionPtr> mojo_subscriptions,
+    int64_t service_worker_registration_id) {
+  std::vector<CookieChangeSubscription> subscriptions;
+  subscriptions.reserve(mojo_subscriptions.size());
+  for (const auto& mojo_subscription : mojo_subscriptions) {
+    subscriptions.emplace_back(
+        std::move(mojo_subscription->url), std::move(mojo_subscription->name),
+        mojo_subscription->match_type, service_worker_registration_id);
+  }
+  return subscriptions;
+}
+
+// static
+std::string CookieChangeSubscription::SerializeVector(
+    const std::vector<CookieChangeSubscription>& subscriptions) {
+  proto::CookieChangeSubscriptionsProto subscriptions_proto;
+  for (const auto& subscription : subscriptions)
+    subscription.Serialize(subscriptions_proto.add_subscriptions());
+  return subscriptions_proto.SerializeAsString();
+}
+
+// static
+std::vector<blink::mojom::CookieChangeSubscriptionPtr>
+CookieChangeSubscription::ToMojoVector(
+    const std::vector<CookieChangeSubscription>& subscriptions) {
+  std::vector<blink::mojom::CookieChangeSubscriptionPtr> mojo_subscriptions;
+  mojo_subscriptions.reserve(subscriptions.size());
+  for (const auto& subscription : subscriptions) {
+    auto mojo_subscription = blink::mojom::CookieChangeSubscription::New();
+    subscription.Serialize(mojo_subscription.get());
+    mojo_subscriptions.emplace_back(std::move(mojo_subscription));
+  }
+  return mojo_subscriptions;
+}
+
+// static
+base::Optional<CookieChangeSubscription> CookieChangeSubscription::Create(
+    proto::CookieChangeSubscriptionProto proto,
+    int64_t service_worker_registration_id) {
+  if (!proto.has_url())
+    return base::nullopt;
+  GURL url = GURL(proto.url());
+  if (!url.is_valid())
+    return base::nullopt;
+
+  std::string name = proto.has_name() ? proto.name() : "";
+  ::network::mojom::CookieMatchType match_type =
+      proto.has_match_type() ? CookieMatchTypeFromProto(proto.match_type())
+                             : ::network::mojom::CookieMatchType::EQUALS;
+
+  return CookieChangeSubscription(std::move(url), std::move(name), match_type,
+                                  service_worker_registration_id);
+}
+
+CookieChangeSubscription::CookieChangeSubscription(CookieChangeSubscription&&) =
+    default;
+
+CookieChangeSubscription::~CookieChangeSubscription() = default;
+
+CookieChangeSubscription::CookieChangeSubscription(
+    GURL url,
+    std::string name,
+    ::network::mojom::CookieMatchType match_type,
+    int64_t service_worker_registration_id)
+    : url_(std::move(url)),
+      name_(std::move(name)),
+      match_type_(match_type),
+      service_worker_registration_id_(service_worker_registration_id) {}
+
+void CookieChangeSubscription::Serialize(
+    proto::CookieChangeSubscriptionProto* proto) const {
+  proto->set_match_type(CookieMatchTypeToProto(match_type_));
+  proto->set_name(name_);
+  proto->set_url(url_.spec());
+}
+
+void CookieChangeSubscription::Serialize(
+    blink::mojom::CookieChangeSubscription* mojo_subscription) const {
+  mojo_subscription->url = url_;
+  mojo_subscription->name = name_;
+  mojo_subscription->match_type = match_type_;
+}
+
+bool CookieChangeSubscription::ShouldObserveChangeTo(
+    const net::CanonicalCookie& cookie) const {
+  switch (match_type_) {
+    case ::network::mojom::CookieMatchType::EQUALS:
+      if (cookie.Name() != name_)
+        return false;
+      break;
+    case ::network::mojom::CookieMatchType::STARTS_WITH:
+      if (!base::StartsWith(cookie.Name(), name_, base::CompareCase::SENSITIVE))
+        return false;
+      break;
+  }
+
+  net::CookieOptions net_options;
+  net_options.set_same_site_cookie_mode(
+      net::CookieOptions::SameSiteCookieMode::INCLUDE_STRICT_AND_LAX);
+  if (!cookie.IncludeForRequestURL(url_, net_options))
+    return false;
+
+  return true;
+}
+
+}  // namespace content
diff --git a/content/browser/cookie_store/cookie_change_subscription.h b/content/browser/cookie_store/cookie_change_subscription.h
new file mode 100644
index 0000000..c2c4839
--- /dev/null
+++ b/content/browser/cookie_store/cookie_change_subscription.h
@@ -0,0 +1,114 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_COOKIE_STORE_COOKIE_CHANGE_SUBSCRIPTION_H_
+#define CONTENT_BROWSER_COOKIE_STORE_COOKIE_CHANGE_SUBSCRIPTION_H_
+
+#include <memory>
+#include <string>
+
+#include "base/containers/linked_list.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "third_party/blink/public/mojom/cookie_store/cookie_store.mojom.h"
+#include "url/gurl.h"
+
+namespace content {
+
+namespace proto {
+
+class CookieChangeSubscriptionProto;
+
+}  // namespace proto
+
+// Represents a single subscription to the list of cookies sent to a URL.
+//
+// The included linked list node and service worker registration ID are used by
+// CookieStoreManager.
+class CookieChangeSubscription
+    : public base::LinkNode<CookieChangeSubscription> {
+ public:
+  // Used to read a service worker's subscriptions from the persistent store.
+  static base::Optional<std::vector<CookieChangeSubscription>>
+  DeserializeVector(const std::string& proto_string,
+                    int64_t service_worker_registration_id);
+
+  // Converts subscriptions from a Mojo API call.
+  static std::vector<CookieChangeSubscription> FromMojoVector(
+      std::vector<blink::mojom::CookieChangeSubscriptionPtr> mojo_subscriptions,
+      int64_t service_worker_registration_id);
+
+  // Used to write a service worker's subscriptions to the service worker store.
+  //
+  // Returns the empty string in case of a serialization error.
+  static std::string SerializeVector(
+      const std::vector<CookieChangeSubscription>&);
+
+  // Converts a service worker's subscriptions to a Mojo API call result.
+  static std::vector<blink::mojom::CookieChangeSubscriptionPtr> ToMojoVector(
+      const std::vector<CookieChangeSubscription>&);
+
+  // Public for testing.
+  //
+  // Production code should use the vector-based factory methods above.
+  static base::Optional<CookieChangeSubscription> Create(
+      proto::CookieChangeSubscriptionProto proto,
+      int64_t service_worker_registration_id);
+
+  // Public for testing.
+  //
+  // Production code should use the vector-based factory methods above.
+  CookieChangeSubscription(GURL url,
+                           std::string name,
+                           ::network::mojom::CookieMatchType match_type,
+                           int64_t service_worker_registration_id);
+
+  // LinkNode supports move-construction, but not move assignment.
+  CookieChangeSubscription(CookieChangeSubscription&&);
+  CookieChangeSubscription& operator=(CookieChangeSubscription&&) = delete;
+
+  ~CookieChangeSubscription();
+
+  // The URL whose cookie list is watched for changes.
+  const GURL& url() const { return url_; }
+
+  // Operator for name-based matching.
+  //
+  // This is used to implement both equality and prefix-based name matching.
+  // Supporting the latter helps avoid wasting battery by waking up service
+  // workers unnecessarily.
+  ::network::mojom::CookieMatchType match_type() const { return match_type_; }
+
+  // Operand for the name-based matching operator above.
+  //
+  // For EQUAL matching, the cookie name must precisely match name(). For
+  // STARTS_WITH matching, the cookie name must be prefixed by name().
+  const std::string& name() const { return name_; }
+
+  // The service worker registration that this subscription belongs to.
+  int64_t service_worker_registration_id() const {
+    return service_worker_registration_id_;
+  }
+
+  // Writes the subscription to the given protobuf.
+  void Serialize(proto::CookieChangeSubscriptionProto* proto) const;
+  // Writes the subscription to the given Mojo object.
+  void Serialize(
+      blink::mojom::CookieChangeSubscription* mojo_subscription) const;
+
+  // True if the subscription covers a change to the given cookie.
+  bool ShouldObserveChangeTo(const net::CanonicalCookie& cookie) const;
+
+ private:
+  const GURL url_;
+  const std::string name_;
+  const ::network::mojom::CookieMatchType match_type_;
+  const int64_t service_worker_registration_id_;
+
+  DISALLOW_COPY_AND_ASSIGN(CookieChangeSubscription);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_COOKIE_STORE_COOKIE_CHANGE_SUBSCRIPTION_H_
diff --git a/content/browser/cookie_store/cookie_change_subscriptions.proto b/content/browser/cookie_store/cookie_change_subscriptions.proto
new file mode 100644
index 0000000..4ee027f
--- /dev/null
+++ b/content/browser/cookie_store/cookie_change_subscriptions.proto
@@ -0,0 +1,27 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package content.proto;
+
+// Proto equivalent of network::mojom::CookieMatchType. Values must match.
+enum CookieMatchType {
+  EQUALS = 0;
+  STARTS_WITH = 1;
+}
+
+// A single cookie change subscription.
+message CookieChangeSubscriptionProto {
+  required string url = 1;
+  optional string name = 2;
+  optional CookieMatchType match_type = 3;
+}
+
+// All cookie change subscriptions belonging to a service worker registration.
+message CookieChangeSubscriptionsProto {
+  repeated CookieChangeSubscriptionProto subscriptions = 1;
+}
diff --git a/content/browser/cookie_store/cookie_store_context.cc b/content/browser/cookie_store/cookie_store_context.cc
new file mode 100644
index 0000000..eeea0d0
--- /dev/null
+++ b/content/browser/cookie_store/cookie_store_context.cc
@@ -0,0 +1,117 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/cookie_store/cookie_store_context.h"
+
+#include "content/browser/service_worker/service_worker_context_wrapper.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/storage_partition.h"
+
+namespace content {
+
+CookieStoreContext::CookieStoreContext()
+    : base::RefCountedDeleteOnSequence<CookieStoreContext>(
+          BrowserThread::GetTaskRunnerForThread(BrowserThread::IO)) {}
+
+CookieStoreContext::~CookieStoreContext() {
+  // The destructor must be called on the IO thread, because it runs
+  // cookie_store_manager_'s destructor, and the latter is only accessed on the
+  // IO thread.
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+}
+
+void CookieStoreContext::Initialize(
+    scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
+    base::OnceCallback<void(bool)> callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+#if DCHECK_IS_ON()
+  DCHECK(!initialize_called_) << __func__ << " called twice";
+  initialize_called_ = true;
+#endif  // DCHECK_IS_ON()
+
+  BrowserThread::PostTask(
+      BrowserThread::IO, FROM_HERE,
+      base::BindOnce(
+          &CookieStoreContext::InitializeOnIOThread, this,
+          std::move(service_worker_context),
+          base::BindOnce(
+              [](scoped_refptr<base::SequencedTaskRunner> task_runner,
+                 base::OnceCallback<void(bool)> callback, bool result) {
+                task_runner->PostTask(
+                    FROM_HERE, base::BindOnce(std::move(callback), result));
+              },
+              base::SequencedTaskRunnerHandle::Get(), std::move(callback))));
+}
+
+void CookieStoreContext::ListenToCookieChanges(
+    ::network::mojom::NetworkContext* network_context,
+    base::OnceCallback<void(bool)> callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+#if DCHECK_IS_ON()
+  DCHECK(initialize_called_) << __func__ << " called before Initialize()";
+#endif  // DCHECK_IS_ON()
+
+  ::network::mojom::CookieManagerPtrInfo cookie_manager_ptr_info;
+  network_context->GetCookieManager(
+      mojo::MakeRequest(&cookie_manager_ptr_info));
+
+  BrowserThread::PostTask(
+      BrowserThread::IO, FROM_HERE,
+      base::BindOnce(
+          &CookieStoreContext::ListenToCookieChangesOnIOThread, this,
+          std::move(cookie_manager_ptr_info),
+          base::BindOnce(
+              [](scoped_refptr<base::SequencedTaskRunner> task_runner,
+                 base::OnceCallback<void(bool)> callback, bool result) {
+                task_runner->PostTask(
+                    FROM_HERE, base::BindOnce(std::move(callback), result));
+              },
+              base::SequencedTaskRunnerHandle::Get(), std::move(callback))));
+}
+
+void CookieStoreContext::CreateService(blink::mojom::CookieStoreRequest request,
+                                       const url::Origin& origin) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+#if DCHECK_IS_ON()
+  DCHECK(initialize_called_) << __func__ << " called before Initialize()";
+#endif  // DCHECK_IS_ON()
+
+  BrowserThread::PostTask(
+      BrowserThread::IO, FROM_HERE,
+      base::BindOnce(&CookieStoreContext::CreateServiceOnIOThread, this,
+                     std::move(request), origin));
+}
+
+void CookieStoreContext::InitializeOnIOThread(
+    scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
+    base::OnceCallback<void(bool)> callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK(!cookie_store_manager_) << __func__ << " called more than once";
+
+  cookie_store_manager_ =
+      std::make_unique<CookieStoreManager>(std::move(service_worker_context));
+  cookie_store_manager_->LoadAllSubscriptions(std::move(callback));
+}
+
+void CookieStoreContext::ListenToCookieChangesOnIOThread(
+    ::network::mojom::CookieManagerPtrInfo cookie_manager_ptr_info,
+    base::OnceCallback<void(bool)> callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK(cookie_store_manager_);
+
+  cookie_store_manager_->ListenToCookieChanges(
+      ::network::mojom::CookieManagerPtr(std::move(cookie_manager_ptr_info)),
+      std::move(callback));
+}
+
+void CookieStoreContext::CreateServiceOnIOThread(
+    blink::mojom::CookieStoreRequest request,
+    const url::Origin& origin) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK(cookie_store_manager_);
+
+  cookie_store_manager_->CreateService(std::move(request), origin);
+}
+
+}  // namespace content
diff --git a/content/browser/cookie_store/cookie_store_context.h b/content/browser/cookie_store/cookie_store_context.h
new file mode 100644
index 0000000..0317b9f0
--- /dev/null
+++ b/content/browser/cookie_store/cookie_store_context.h
@@ -0,0 +1,98 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_COOKIE_STORE_COOKIE_STORE_CONTEXT_H_
+#define CONTENT_BROWSER_COOKIE_STORE_COOKIE_STORE_CONTEXT_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted_delete_on_sequence.h"
+#include "base/memory/scoped_refptr.h"
+#include "content/browser/cookie_store/cookie_store_manager.h"
+#include "content/common/content_export.h"
+#include "services/network/public/mojom/network_service.mojom.h"
+#include "third_party/blink/public/mojom/cookie_store/cookie_store.mojom.h"
+#include "url/origin.h"
+
+namespace content {
+
+class CookieStoreManager;
+class ServiceWorkerContextWrapper;
+
+// UI thread handle to a CookieStoreManager.
+//
+// This class is RefCountedDeleteOnSequence because it has members that must be
+// accessed on the IO thread, and therefore must be destroyed on the IO thread.
+// Conceptually, CookieStoreContext instances are owned by StoragePartitionImpl.
+class CONTENT_EXPORT CookieStoreContext
+    : public base::RefCountedDeleteOnSequence<CookieStoreContext> {
+ public:
+  REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE();
+
+  // Creates an empty CookieStoreContext shell.
+  //
+  // Newly created instances must be initialized via Initialize() before any
+  // other methods are used.
+  CookieStoreContext();
+
+  // Creates the underlying CookieStoreManager.
+  //
+  // This must be called before any other CookieStoreContext method.
+  //
+  // The newly created CookieStoreManager starts loading any persisted cookie
+  // change subscriptions from ServiceWorkerStorage. When the loading completes,
+  // the given callback is called with a boolean indicating whether the loading
+  // succeeded.
+  //
+  // It is safe to call all the other methods during the loading operation. This
+  // includes creating and using CookieStore mojo services. The
+  // CookieStoreManager has well-defined semantics if loading from
+  // ServiceWorkerStorage fails, so the caller does not need to handle loading
+  // errors.
+  void Initialize(
+      scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
+      base::OnceCallback<void(bool)> callback);
+
+  // Starts listening to cookie changes from a network service instance.
+  //
+  // The callback is called with the (success / failure) result of subscribing.
+  void ListenToCookieChanges(::network::mojom::NetworkContext* network_context,
+                             base::OnceCallback<void(bool)> callback);
+
+  // Routes a mojo request to the CookieStoreManager on the IO thread.
+  void CreateService(blink::mojom::CookieStoreRequest request,
+                     const url::Origin& origin);
+
+ private:
+  friend class base::RefCountedDeleteOnSequence<CookieStoreContext>;
+  friend class base::DeleteHelper<CookieStoreContext>;
+  ~CookieStoreContext();
+
+  void InitializeOnIOThread(
+      scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
+      base::OnceCallback<void(bool)> callback);
+
+  void ListenToCookieChangesOnIOThread(
+      ::network::mojom::CookieManagerPtrInfo cookie_manager_ptr_info,
+      base::OnceCallback<void(bool)> callback);
+
+  void CreateServiceOnIOThread(blink::mojom::CookieStoreRequest request,
+                               const url::Origin& origin);
+
+  // Only accessed on the IO thread.
+  std::unique_ptr<CookieStoreManager> cookie_store_manager_;
+
+#if DCHECK_IS_ON()
+  // Only accesssed on the UI thread.
+  bool initialize_called_ = false;
+#endif  // DCHECK_IS_ON()
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  DISALLOW_COPY_AND_ASSIGN(CookieStoreContext);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_COOKIE_STORE_COOKIE_STORE_CONTEXT_H_
diff --git a/content/browser/cookie_store/cookie_store_host.cc b/content/browser/cookie_store/cookie_store_host.cc
new file mode 100644
index 0000000..69e85466
--- /dev/null
+++ b/content/browser/cookie_store/cookie_store_host.cc
@@ -0,0 +1,38 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/cookie_store/cookie_store_host.h"
+
+#include <utility>
+
+#include "content/browser/cookie_store/cookie_store_manager.h"
+#include "url/origin.h"
+
+namespace content {
+
+CookieStoreHost::CookieStoreHost(CookieStoreManager* manager,
+                                 const url::Origin& origin)
+    : manager_(manager), origin_(origin.GetURL()) {}
+
+CookieStoreHost::~CookieStoreHost() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void CookieStoreHost::AppendSubscriptions(
+    int64_t service_worker_registration_id,
+    std::vector<blink::mojom::CookieChangeSubscriptionPtr> subscriptions,
+    AppendSubscriptionsCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  manager_->AppendSubscriptions(service_worker_registration_id, origin_,
+                                std::move(subscriptions), std::move(callback));
+}
+
+void CookieStoreHost::GetSubscriptions(int64_t service_worker_registration_id,
+                                       GetSubscriptionsCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  manager_->GetSubscriptions(service_worker_registration_id, origin_,
+                             std::move(callback));
+}
+
+}  // namespace content
diff --git a/content/browser/cookie_store/cookie_store_host.h b/content/browser/cookie_store/cookie_store_host.h
new file mode 100644
index 0000000..e4e04f6
--- /dev/null
+++ b/content/browser/cookie_store/cookie_store_host.h
@@ -0,0 +1,65 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_COOKIE_STORE_COOKIE_STORE_HOST_H_
+#define CONTENT_BROWSER_COOKIE_STORE_COOKIE_STORE_HOST_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/sequence_checker.h"
+#include "third_party/blink/public/mojom/cookie_store/cookie_store.mojom.h"
+#include "url/gurl.h"
+
+namespace url {
+
+class Origin;
+
+}  // namespace url
+
+namespace content {
+
+class CookieStoreManager;
+
+// Stores the state associated with each CookieStore mojo connection.
+//
+// The bulk of the CookieStore implementation is in the CookieStoreManager
+// class. Each StoragePartition has a single associated CookieStoreManager
+// instance. By contrast, each CookieStore mojo connection has an associated
+// CoookieStoreHost instance, which stores the per-connection state.
+//
+// Instances of this class must be accessed exclusively on the IO thread,
+// because they call into CookieStoreManager directly.
+class CookieStoreHost : public blink::mojom::CookieStore {
+ public:
+  CookieStoreHost(CookieStoreManager* manager, const url::Origin& origin);
+  ~CookieStoreHost() override;
+
+  // content::mojom::CookieStore
+  void AppendSubscriptions(
+      int64_t service_worker_registration_id,
+      std::vector<blink::mojom::CookieChangeSubscriptionPtr>,
+      AppendSubscriptionsCallback callback) override;
+  void GetSubscriptions(int64_t service_worker_registration_id,
+                        GetSubscriptionsCallback callback) override;
+
+ private:
+  // The raw pointer is safe because CookieStoreManager owns this instance via a
+  // mojo::BindingSet.
+  CookieStoreManager* const manager_;
+
+  const GURL origin_;
+
+  // Instances of this class are currently bound to the IO thread, because they
+  // call ServiceWorkerContextWrapper methods that are restricted to the IO
+  // thread. However, the class implementation itself is thread-friendly, so it
+  // only checks that methods are called on the same sequence.
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  DISALLOW_COPY_AND_ASSIGN(CookieStoreHost);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_COOKIE_STORE_COOKIE_STORE_HOST_H_
diff --git a/content/browser/cookie_store/cookie_store_manager.cc b/content/browser/cookie_store/cookie_store_manager.cc
new file mode 100644
index 0000000..b1153cf
--- /dev/null
+++ b/content/browser/cookie_store/cookie_store_manager.cc
@@ -0,0 +1,505 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/cookie_store/cookie_store_manager.h"
+
+#include <utility>
+
+#include "base/optional.h"
+#include "content/browser/cookie_store/cookie_change_subscriptions.pb.h"
+#include "content/browser/service_worker/embedded_worker_status.h"
+#include "content/browser/service_worker/service_worker_context_wrapper.h"
+#include "content/browser/service_worker/service_worker_metrics.h"
+#include "content/browser/service_worker/service_worker_registration.h"
+#include "content/browser/service_worker/service_worker_version.h"
+#include "content/common/service_worker/service_worker_status_code.h"
+#include "content/public/browser/browser_context.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "third_party/blink/public/mojom/service_worker/service_worker_event_status.mojom.h"
+#include "url/gurl.h"
+
+namespace content {
+
+namespace {
+
+// ServiceWorkerStorage user data key for cookie change subscriptions.
+const char kSubscriptionsUserKey[] = "cookie_store_subscriptions";
+
+// Handles the result of ServiceWorkerContextWrapper::StoreRegistrationUserData.
+void HandleStoreRegistrationUserDataStatus(ServiceWorkerStatusCode status) {
+  // The current implementation does not have a good way to handle errors in
+  // StoreRegistrationUserData. Cookie change subscriptions have been added to
+  // the registration during the install event, so it's too late to surface the
+  // error to the renderer. The registration has already been persisted, and the
+  // Service Worker is likely active by now.
+  DLOG_IF(ERROR, status != SERVICE_WORKER_OK)
+      << "StoreRegistrationUserData failed";
+}
+
+}  // namespace
+
+CookieStoreManager::CookieStoreManager(
+    scoped_refptr<ServiceWorkerContextWrapper> service_worker_context)
+    : service_worker_context_(std::move(service_worker_context)),
+      cookie_change_listener_binding_(this),
+      registration_user_data_key_(kSubscriptionsUserKey),
+      weak_factory_(this) {
+  service_worker_context_->AddObserver(this);
+}
+
+CookieStoreManager::~CookieStoreManager() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  service_worker_context_->RemoveObserver(this);
+}
+
+void CookieStoreManager::CreateService(blink::mojom::CookieStoreRequest request,
+                                       const url::Origin& origin) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  bindings_.AddBinding(std::make_unique<CookieStoreHost>(this, origin),
+                       std::move(request));
+}
+
+void CookieStoreManager::LoadAllSubscriptions(
+    base::OnceCallback<void(bool)> callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  DCHECK(!done_loading_subscriptions_) << __func__ << " already called";
+
+  service_worker_context_->GetUserDataForAllRegistrations(
+      registration_user_data_key_,
+      base::BindOnce(&CookieStoreManager::ProcessOnDiskSubscriptions,
+                     weak_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void CookieStoreManager::ListenToCookieChanges(
+    ::network::mojom::CookieManagerPtr cookie_manager,
+    base::OnceCallback<void(bool)> callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  DCHECK(!cookie_manager_) << __func__ << " already called";
+  cookie_manager_ = std::move(cookie_manager);
+
+  DCHECK(!cookie_change_listener_binding_.is_bound());
+  ::network::mojom::CookieChangeListenerPtr cookie_change_listener;
+  cookie_change_listener_binding_.Bind(
+      mojo::MakeRequest(&cookie_change_listener));
+
+  // TODO(pwnall): Switch to an API with subscription confirmation.
+  cookie_manager_->AddGlobalChangeListener(std::move(cookie_change_listener));
+  std::move(callback).Run(true);
+}
+
+void CookieStoreManager::ProcessOnDiskSubscriptions(
+    base::OnceCallback<void(bool)> load_callback,
+    const std::vector<std::pair<int64_t, std::string>>& user_data,
+    ServiceWorkerStatusCode status) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  DCHECK(!done_loading_subscriptions_) << __func__ << " already called";
+  done_loading_subscriptions_ = true;
+
+  if (status != SERVICE_WORKER_OK) {
+    DidLoadAllSubscriptions(false, std::move(load_callback));
+    return;
+  }
+
+  DCHECK(subscriptions_by_registration_.empty());
+  subscriptions_by_registration_.reserve(user_data.size());
+  bool load_success = true;
+  for (const auto& pair : user_data) {
+    int64_t service_worker_registration_id = pair.first;
+    const std::string& proto_string = pair.second;
+
+    base::Optional<std::vector<CookieChangeSubscription>> subscriptions_opt =
+        CookieChangeSubscription::DeserializeVector(
+            proto_string, service_worker_registration_id);
+    if (!subscriptions_opt.has_value()) {
+      load_success = false;
+      continue;
+    }
+
+    ActivateSubscriptions(&subscriptions_opt.value());
+    DCHECK(
+        !subscriptions_by_registration_.count(service_worker_registration_id));
+    subscriptions_by_registration_.emplace(
+        std::move(service_worker_registration_id),
+        std::move(subscriptions_opt).value());
+  }
+
+  DidLoadAllSubscriptions(load_success, std::move(load_callback));
+}
+
+void CookieStoreManager::DidLoadAllSubscriptions(
+    bool succeeded,
+    base::OnceCallback<void(bool)> load_callback) {
+  DCHECK(done_loading_subscriptions_);
+  succeeded_loading_subscriptions_ = succeeded;
+
+  for (auto& callback : subscriptions_loaded_callbacks_)
+    std::move(callback).Run();
+  subscriptions_loaded_callbacks_.clear();
+
+  std::move(load_callback).Run(succeeded);
+}
+
+void CookieStoreManager::AppendSubscriptions(
+    int64_t service_worker_registration_id,
+    const GURL& origin,
+    std::vector<blink::mojom::CookieChangeSubscriptionPtr> mojo_subscriptions,
+    blink::mojom::CookieStore::AppendSubscriptionsCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (!done_loading_subscriptions_) {
+    subscriptions_loaded_callbacks_.emplace_back(base::BindOnce(
+        &CookieStoreManager::AppendSubscriptions, weak_factory_.GetWeakPtr(),
+        service_worker_registration_id, origin, std::move(mojo_subscriptions),
+        std::move(callback)));
+    return;
+  }
+
+  if (!succeeded_loading_subscriptions_) {
+    std::move(callback).Run(false);
+    return;
+  }
+
+  // GetLiveRegistration() is sufficient here (as opposed to a flavor of
+  // FindRegistration()) because AppendSubscriptions is only called from the
+  // implementation of the Cookie Store API, which is exposed to
+  // ServiceWorkerGlobalScope. ServiceWorkerGlobalScope references the
+  // service worker's registration via a ServiceWorkerRegistration JavaScript
+  // object, so the registration is guaranteed to be live while the service
+  // worker is executing.
+  //
+  // It is possible for the service worker to get killed while this API call is
+  // in progress, for example, if the service worker code exceeds an event
+  // handling time limit. In that case, the return value will not be observed,
+  // so a false negative is acceptable.
+  ServiceWorkerRegistration* service_worker_registration =
+      service_worker_context_->GetLiveRegistration(
+          service_worker_registration_id);
+  if (!service_worker_registration) {
+    // This error case is a good fit for mojo::ReportBadMessage(), because the
+    // renderer has passed an invalid registration ID. However, the code here
+    // might run without a mojo call context, if the original call was delayed
+    // while loading on-disk subscription data.
+    //
+    // While it would be possible to have two code paths for the two situations,
+    // the extra complexity doesn't seem warranted for the limited debuggig
+    // benefits provided by mojo::ReportBadMessage.
+    std::move(callback).Run(false);
+    return;
+  }
+
+  // TODO(crbug.com/843079): This check incorrectly allows an active service
+  //                         worker version to call the API, if another version
+  //                         is installing at the same time.
+  if (!service_worker_registration->installing_version()) {
+    // A service worker's cookie change subscriptions can only be modified while
+    // the service worker's install event is handled.
+    std::move(callback).Run(false);
+    return;
+  }
+
+  if (mojo_subscriptions.empty()) {
+    // Empty subscriptions are special-cased so we never have to serialize an
+    // empty array of subscriptions. This is advantageous because the protobuf
+    // serialization of an empty array is the empty string, which is also used
+    // by the convenience protobuf serialization API to signal serialization
+    // failure. So, supporting serializing an empty array would mean we can't
+    // use the convenience serialization API.
+    std::move(callback).Run(true);
+    return;
+  }
+
+  std::vector<CookieChangeSubscription> new_subscriptions =
+      CookieChangeSubscription::FromMojoVector(
+          std::move(mojo_subscriptions), service_worker_registration->id());
+  DCHECK(!new_subscriptions.empty());
+
+  auto old_subscriptions_it =
+      subscriptions_by_registration_.find(service_worker_registration_id);
+  if (old_subscriptions_it == subscriptions_by_registration_.end()) {
+    subscriptions_by_registration_.emplace(service_worker_registration_id,
+                                           std::move(new_subscriptions));
+    std::move(callback).Run(true);
+    return;
+  }
+
+  std::vector<CookieChangeSubscription>& old_subscriptions =
+      old_subscriptions_it->second;
+  old_subscriptions.reserve(old_subscriptions.size() +
+                            new_subscriptions.size());
+  for (auto& new_subscription : new_subscriptions)
+    old_subscriptions.emplace_back(std::move(new_subscription));
+
+  std::move(callback).Run(true);
+}
+
+void CookieStoreManager::GetSubscriptions(
+    int64_t service_worker_registration_id,
+    const GURL& origin,
+    blink::mojom::CookieStore::GetSubscriptionsCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (!done_loading_subscriptions_) {
+    subscriptions_loaded_callbacks_.emplace_back(base::BindOnce(
+        &CookieStoreManager::GetSubscriptions, weak_factory_.GetWeakPtr(),
+        service_worker_registration_id, origin, std::move(callback)));
+    return;
+  }
+
+  if (!succeeded_loading_subscriptions_) {
+    std::move(callback).Run(
+        std::vector<blink::mojom::CookieChangeSubscriptionPtr>(), false);
+    return;
+  }
+
+  auto it = subscriptions_by_registration_.find(service_worker_registration_id);
+  if (it == subscriptions_by_registration_.end()) {
+    std::move(callback).Run(
+        std::vector<blink::mojom::CookieChangeSubscriptionPtr>(), true);
+    return;
+  }
+
+  std::move(callback).Run(CookieChangeSubscription::ToMojoVector(it->second),
+                          true);
+}
+
+void CookieStoreManager::OnNewLiveRegistration(
+    int64_t service_worker_registration_id,
+    const GURL& pattern) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void CookieStoreManager::OnRegistrationStored(
+    int64_t service_worker_registration_id,
+    const GURL& pattern) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Waiting for the on-disk subscriptions to be loaded ensures that the
+  // registration's subscriptions aren't activated twice. Without waiting,
+  // there's a risk that LoadAllSubscriptions() sees the result of the
+  // StoreRegistrationUserData() call below.
+  if (!done_loading_subscriptions_) {
+    subscriptions_loaded_callbacks_.emplace_back(base::BindOnce(
+        &CookieStoreManager::OnRegistrationStored, weak_factory_.GetWeakPtr(),
+        service_worker_registration_id, pattern));
+    return;
+  }
+
+  auto it = subscriptions_by_registration_.find(service_worker_registration_id);
+  if (it == subscriptions_by_registration_.end())
+    return;
+
+  ActivateSubscriptions(&it->second);
+
+  std::string subscriptions_data =
+      CookieChangeSubscription::SerializeVector(it->second);
+  DCHECK(!subscriptions_data.empty())
+      << "Failed to create cookie change subscriptions protobuf";
+
+  service_worker_context_->StoreRegistrationUserData(
+      service_worker_registration_id, pattern.GetOrigin(),
+      std::vector<std::pair<std::string, std::string>>(
+          {{registration_user_data_key_, subscriptions_data}}),
+      base::BindOnce(&HandleStoreRegistrationUserDataStatus));
+}
+
+void CookieStoreManager::OnRegistrationDeleted(
+    int64_t service_worker_registration_id,
+    const GURL& pattern) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Waiting for the on-disk subscriptions to be loaded ensures that the
+  // registration's subscriptions are removed. Without waiting, there's a risk
+  // that a registration's subscriptions will finish loading (and thus remain
+  // active) right after this function runs.
+  if (!done_loading_subscriptions_) {
+    subscriptions_loaded_callbacks_.emplace_back(base::BindOnce(
+        &CookieStoreManager::OnRegistrationDeleted, weak_factory_.GetWeakPtr(),
+        service_worker_registration_id, pattern));
+    return;
+  }
+
+  auto it = subscriptions_by_registration_.find(service_worker_registration_id);
+  if (it == subscriptions_by_registration_.end())
+    return;
+
+  DeactivateSubscriptions(&it->second);
+  subscriptions_by_registration_.erase(it);
+}
+
+void CookieStoreManager::ActivateSubscriptions(
+    std::vector<CookieChangeSubscription>* subscriptions) {
+  if (subscriptions->empty())
+    return;
+
+  // Service workers can only observe changes to cookies for URLs under their
+  // scope. This means all the URLs that the worker is observing must map to the
+  // same domain key (eTLD+1).
+  //
+  // TODO(pwnall): This is the same as implementation as
+  //               net::CookieMonsterChangeDispatcher::DomainKey. Extract that
+  //               implementation into net/cookies.cookie_util.h and call it.
+  std::string url_key = net::registry_controlled_domains::GetDomainAndRegistry(
+
+      (*subscriptions)[0].url(),
+      net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
+  base::LinkedList<CookieChangeSubscription>& url_key_subscriptions_list =
+      subscriptions_by_url_key_[url_key];
+
+  for (auto& subscription : *subscriptions) {
+    DCHECK(!subscription.next() && !subscription.previous())
+        << "Subscription passed to " << __func__ << " already activated";
+    DCHECK_EQ(url_key,
+              net::registry_controlled_domains::GetDomainAndRegistry(
+                  subscription.url(),
+                  net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES))
+        << __func__ << " subscriptions belong to different registrations";
+    url_key_subscriptions_list.Append(&subscription);
+  }
+}
+
+void CookieStoreManager::DeactivateSubscriptions(
+    std::vector<CookieChangeSubscription>* subscriptions) {
+  if (subscriptions->empty())
+    return;
+
+  // Service workers can only observe changes to cookies for URLs under their
+  // scope. This means all the URLs that the worker is observing must map to the
+  // same domain key (eTLD+1).
+  //
+  // TODO(pwnall): This has the same implementation as
+  //               net::CookieMonsterChangeDispatcher::DomainKey. Extract that
+  //               implementation into net/cookies.cookie_util.h and call it.
+  std::string url_key = net::registry_controlled_domains::GetDomainAndRegistry(
+      (*subscriptions)[0].url(),
+      net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
+  for (auto& subscription : *subscriptions) {
+    DCHECK(subscription.next() && subscription.previous())
+        << "Subscription passed to " << __func__ << " not previously activated";
+    DCHECK_EQ(url_key,
+              net::registry_controlled_domains::GetDomainAndRegistry(
+                  subscription.url(),
+                  net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES))
+        << __func__ << " subscriptions belong to different registrations";
+    subscription.RemoveFromList();
+  }
+  auto it = subscriptions_by_url_key_.find(url_key);
+  DCHECK(it != subscriptions_by_url_key_.end());
+  if (it->second.empty())
+    subscriptions_by_url_key_.erase(it);
+}
+
+void CookieStoreManager::OnStorageWiped() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Waiting for the on-disk subscriptions to be loaded ensures that all
+  // subscriptions are removed. Without waiting, there's a risk that some
+  // subscriptions will finish loading (and thus remain active) after this
+  // function runs.
+  if (!done_loading_subscriptions_) {
+    subscriptions_loaded_callbacks_.emplace_back(base::BindOnce(
+        &CookieStoreManager::OnStorageWiped, weak_factory_.GetWeakPtr()));
+    return;
+  }
+
+  subscriptions_by_url_key_.clear();
+  subscriptions_by_registration_.clear();
+}
+
+void CookieStoreManager::OnCookieChange(
+    const net::CanonicalCookie& cookie,
+    ::network::mojom::CookieChangeCause cause) {
+  // Waiting for on-disk subscriptions to be loaded ensures that changes are
+  // delivered to all service workers that subscribed to them in previous
+  // browser sessions. Without waiting, workers might miss cookie changes.
+  if (!done_loading_subscriptions_) {
+    subscriptions_loaded_callbacks_.emplace_back(
+        base::BindOnce(&CookieStoreManager::OnCookieChange,
+                       weak_factory_.GetWeakPtr(), cookie, cause));
+    return;
+  }
+
+  // Compute the list of service workers interested in this change. A worker
+  // might have multiple subscriptions that cover this change, but should still
+  // receive a single change event.
+  // TODO(pwnall): This has same as implementation as
+  //               net::CookieMonsterChangeDispatcher::DomainKey. Extract that
+  //               implementation into net/cookies.cookie_util.h and call it.
+  std::string url_key = net::registry_controlled_domains::GetDomainAndRegistry(
+      cookie.Domain(),
+      net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
+  auto it = subscriptions_by_url_key_.find(url_key);
+  if (it == subscriptions_by_url_key_.end())
+    return;
+  std::set<int64_t> interested_registration_ids;
+  const base::LinkedList<CookieChangeSubscription>& subscriptions = it->second;
+  for (const base::LinkNode<CookieChangeSubscription>* node =
+           subscriptions.head();
+       node != subscriptions.end(); node = node->next()) {
+    const CookieChangeSubscription* subscription = node->value();
+    if (subscription->ShouldObserveChangeTo(cookie)) {
+      interested_registration_ids.insert(
+          subscription->service_worker_registration_id());
+    }
+  }
+
+  // Dispatch the change to interested workers.
+  for (int64_t registration_id : interested_registration_ids) {
+    service_worker_context_->FindReadyRegistrationForIdOnly(
+        registration_id,
+        base::BindOnce(
+            [](base::WeakPtr<CookieStoreManager> manager,
+               const net::CanonicalCookie& cookie,
+               ::network::mojom::CookieChangeCause cause,
+               ServiceWorkerStatusCode find_status,
+               scoped_refptr<ServiceWorkerRegistration> registration) {
+              if (find_status != SERVICE_WORKER_OK)
+                return;
+
+              DCHECK(registration);
+              if (!manager)
+                return;
+              manager->DispatchChangeEvent(std::move(registration), cookie,
+                                           cause);
+            },
+            weak_factory_.GetWeakPtr(), cookie, cause));
+  }
+}
+
+void CookieStoreManager::DispatchChangeEvent(
+    scoped_refptr<ServiceWorkerRegistration> registration,
+    const net::CanonicalCookie& cookie,
+    ::network::mojom::CookieChangeCause cause) {
+  scoped_refptr<ServiceWorkerVersion> active_version =
+      registration->active_version();
+  if (active_version->running_status() != EmbeddedWorkerStatus::RUNNING) {
+    active_version->RunAfterStartWorker(
+        ServiceWorkerMetrics::EventType::COOKIE_CHANGE,
+        base::BindOnce(&CookieStoreManager::DidStartWorkerForChangeEvent,
+                       weak_factory_.GetWeakPtr(), std::move(registration),
+                       cookie, cause));
+    return;
+  }
+
+  int request_id = active_version->StartRequest(
+      ServiceWorkerMetrics::EventType::COOKIE_CHANGE, base::DoNothing());
+
+  active_version->event_dispatcher()->DispatchCookieChangeEvent(
+      cookie, cause, active_version->CreateSimpleEventCallback(request_id));
+}
+
+void CookieStoreManager::DidStartWorkerForChangeEvent(
+    scoped_refptr<ServiceWorkerRegistration> registration,
+    const net::CanonicalCookie& cookie,
+    ::network::mojom::CookieChangeCause cause,
+    ServiceWorkerStatusCode start_worker_status) {
+  if (start_worker_status != SERVICE_WORKER_OK)
+    return;
+  DispatchChangeEvent(std::move(registration), cookie, cause);
+}
+
+}  // namespace content
diff --git a/content/browser/cookie_store/cookie_store_manager.h b/content/browser/cookie_store/cookie_store_manager.h
new file mode 100644
index 0000000..2eac6ccc
--- /dev/null
+++ b/content/browser/cookie_store/cookie_store_manager.h
@@ -0,0 +1,228 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_COOKIE_STORE_COOKIE_STORE_MANAGER_H_
+#define CONTENT_BROWSER_COOKIE_STORE_COOKIE_STORE_MANAGER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/containers/linked_list.h"
+#include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "content/browser/cookie_store/cookie_change_subscription.h"
+#include "content/browser/cookie_store/cookie_store_host.h"
+#include "content/browser/service_worker/service_worker_context_core_observer.h"
+#include "mojo/public/cpp/bindings/strong_binding_set.h"
+#include "services/network/public/mojom/network_service.mojom.h"
+#include "third_party/blink/public/mojom/cookie_store/cookie_store.mojom.h"
+#include "url/origin.h"
+
+class GURL;
+
+namespace content {
+
+class ServiceWorkerContextWrapper;
+class ServiceWorkerRegistration;
+
+// Manages cookie change subscriptions for a StoragePartition's service workers.
+//
+// Subscriptions are stored along with their associated service worker
+// registrations in ServiceWorkerStorage, as user data. When a service worker is
+// unregistered, its cookie change subscriptions are removed. The storage method
+// (user data) is an implementation detail. Callers should not rely on it, as
+// the storage method may change in the future.
+//
+// Instances of this class must be accessed exclusively on the IO thread,
+// because they call into ServiceWorkerContextWrapper methods that are
+// restricted to the IO thread.
+class CookieStoreManager : public ServiceWorkerContextCoreObserver,
+                           public ::network::mojom::CookieChangeListener {
+ public:
+  // Creates a CookieStoreManager with an empty in-memory subscription database.
+  //
+  // The in-memory subscription database must be populated with data from disk,
+  // by calling ReadAllSubscriptions().
+  CookieStoreManager(
+      scoped_refptr<ServiceWorkerContextWrapper> service_worker_context);
+
+  ~CookieStoreManager() override;
+
+  // Creates a mojo connection to a service worker.
+  //
+  // This is called when service workers use the Cookie Store API to subscribe
+  // to cookie changes or obtain the list of cookie changes.
+  void CreateService(blink::mojom::CookieStoreRequest request,
+                     const url::Origin& origin);
+
+  // Starts loading the on-disk subscription data.
+  //
+  // Returns after scheduling the work. The callback is called with a boolean
+  // that indicates if the load operation succeeded.
+  //
+  // It is safe to call all the other CookieStoreManager methods during the
+  // loading operation. The CookieStoreManager has well-defined semantics if
+  // loading fails, so it is not necessary to handle loading errors.
+  void LoadAllSubscriptions(base::OnceCallback<void(bool)> callback);
+
+  // Processes cookie changes from a network service instance.
+  void ListenToCookieChanges(::network::mojom::CookieManagerPtr cookie_manager,
+                             base::OnceCallback<void(bool)> callback);
+
+  // content::mojom::CookieStore implementation
+  void AppendSubscriptions(
+      int64_t service_worker_registration_id,
+      const GURL& origin,
+      std::vector<blink::mojom::CookieChangeSubscriptionPtr> mojo_subscriptions,
+      blink::mojom::CookieStore::AppendSubscriptionsCallback callback);
+  void GetSubscriptions(
+      int64_t service_worker_registration_id,
+      const GURL& origin,
+      blink::mojom::CookieStore::GetSubscriptionsCallback callback);
+
+  // ServiceWorkerContextCoreObserver
+  void OnRegistrationStored(int64_t service_worker_registration_id,
+                            const GURL& pattern) override;
+  void OnRegistrationDeleted(int64_t service_worker_registration_id,
+                             const GURL& pattern) override;
+  void OnNewLiveRegistration(int64_t service_worker_registration_id,
+                             const GURL& pattern) override;
+  void OnStorageWiped() override;
+
+  // ::network::mojom::CookieChangeListener
+  void OnCookieChange(const net::CanonicalCookie& cookie,
+                      ::network::mojom::CookieChangeCause cause) override;
+
+ private:
+  // Updates internal state with the result of loading disk subscription data.
+  //
+  // Called exactly once.
+  void ProcessOnDiskSubscriptions(
+      base::OnceCallback<void(bool)> load_callback,
+      const std::vector<std::pair<int64_t, std::string>>& user_data,
+      ServiceWorkerStatusCode status);
+
+  // Runs all the callbacks waiting for on-disk subscription data.
+  //
+  // Called exactly once, after on-disk subcriptions have been loaded.
+  void DidLoadAllSubscriptions(bool succeeded,
+                               base::OnceCallback<void(bool)> load_callback);
+
+  // Starts sending cookie change events to a service worker.
+  //
+  // All subscriptions must belong to the same service worker registration. This
+  // method is not idempotent.
+  void ActivateSubscriptions(
+      std::vector<CookieChangeSubscription>* subscriptions);
+
+  // Stops sending cookie change events to a service worker.
+  //
+  // All subscriptions must belong to the same service worker registration. This
+  // method is not idempotent.
+  void DeactivateSubscriptions(
+      std::vector<CookieChangeSubscription>* subscriptions);
+
+  // Sends a cookie change to interested service workers.
+  //
+  // Must only be called after the on-disk subscription data is successfully
+  // loaded.
+  void DispatchCookieChange(const net::CanonicalCookie& cookie,
+                            ::network::mojom::CookieChangeCause cause);
+
+  // Sends a cookie change event to one service worker.
+  void DispatchChangeEvent(
+      scoped_refptr<ServiceWorkerRegistration> registration,
+      const net::CanonicalCookie& cookie,
+      ::network::mojom::CookieChangeCause cause);
+
+  // Called after a service worker was started so it can get a cookie change.
+  void DidStartWorkerForChangeEvent(
+      scoped_refptr<ServiceWorkerRegistration> registration,
+      const net::CanonicalCookie& cookie,
+      ::network::mojom::CookieChangeCause cause,
+      ServiceWorkerStatusCode start_worker_status);
+
+  // Used to efficiently implement OnRegistrationDeleted().
+  //
+  // When a service worker registration is removed from the system, the
+  // CookieStoreManager needs to remove all the cookie change subscriptions
+  // associated with the registration. Looking up the registration ID in the
+  // |subscriptions_by_registration_| map is done in O(1) time, and then each
+  // subscription is removed from a LinkedList in |subscription_by_url_key_| in
+  // O(1) time.
+  std::unordered_map<int64_t, std::vector<CookieChangeSubscription>>
+      subscriptions_by_registration_;
+
+  // Used to efficiently implement DispatchCookieChange().
+  //
+  // When a cookie change notification comes from the network service, the
+  // CookieStoreManager needs to dispatch events to the workers with relevant
+  // subscriptions. |subscriptions_by_url_key_| indexes change subscriptions
+  // according to the eTLD+1 of the subscription's scope URL, so each cookie
+  // change only needs to be checked against the subscriptions of the service
+  // workers in the same eTLD+1. The reduction in work is signficant, given that
+  // checking whether a subscription matches a cookie isn't very cheap.
+  //
+  // The current implementation's performance profile could have been achieved
+  // with a map from eTLD+1 to registration IDs, which would not have required
+  // linked lists. However, the current approach is more amenable to future
+  // optimizations, such as partitioning by (eTLD+1, cookie name).
+  std::map<std::string, base::LinkedList<CookieChangeSubscription>>
+      subscriptions_by_url_key_;
+
+  // Used to look up and modify service worker registration data.
+  scoped_refptr<ServiceWorkerContextWrapper> service_worker_context_;
+
+  // Tracks the open mojo pipes created by CreateService().
+  //
+  // Each pipe is associated with the CookieStoreHost instance that it is
+  // connected to. When the pipe is closed, the StrongBindingSet automatically
+  // deletes the CookieStoreHost.
+  mojo::StrongBindingSet<blink::mojom::CookieStore> bindings_;
+
+  // Used to receive cookie changes from the network service.
+  ::network::mojom::CookieManagerPtr cookie_manager_;
+  mojo::Binding<::network::mojom::CookieChangeListener>
+      cookie_change_listener_binding_;
+
+  // The service worker registration user data key for subscription data.
+  //
+  // All the subscriptions associated with a registration are stored in a single
+  // user data entry whose key is |registration_user_data_key_|, and whose value
+  // is a serialized CookieChangeSubscriptionsProto.
+  const std::string registration_user_data_key_;
+
+  // Called after all subscriptions have been loaded.
+  //
+  // Callbacks can assume that |done_loading_subscriptions_| is true
+  // and |succeeded_loading_subscriptions_| is set. If the latter is true,
+  // |subscriptions_by_registration_| and |subscriptions_by_url_key_| will also
+  // be populated.
+  std::vector<base::OnceClosure> subscriptions_loaded_callbacks_;
+
+  // Set to true once all subscriptions have been loaded.
+  bool done_loading_subscriptions_ = false;
+
+  // Only defined when |done_loading_subscriptions_| is true.
+  bool succeeded_loading_subscriptions_ = false;
+
+  // Instances of this class are currently bound to the IO thread, because they
+  // call ServiceWorkerContextWrapper methods that are restricted to the IO
+  // thread. However, the class implementation itself is thread-friendly, so it
+  // only checks that methods are called on the same sequence.
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  // Supports having the manager destroyed while waiting for disk I/O.
+  base::WeakPtrFactory<CookieStoreManager> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(CookieStoreManager);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_COOKIE_STORE_COOKIE_STORE_MANAGER_H_
diff --git a/content/browser/cookie_store/cookie_store_manager_unittest.cc b/content/browser/cookie_store/cookie_store_manager_unittest.cc
new file mode 100644
index 0000000..d548c209
--- /dev/null
+++ b/content/browser/cookie_store/cookie_store_manager_unittest.cc
@@ -0,0 +1,784 @@
+// 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 <memory>
+
+#include "base/bind.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
+#include "content/browser/cookie_store/cookie_store_context.h"
+#include "content/browser/cookie_store/cookie_store_manager.h"
+#include "content/browser/service_worker/embedded_worker_test_helper.h"
+#include "content/browser/service_worker/service_worker_context_wrapper.h"
+#include "content/browser/storage_partition_impl.h"
+#include "content/common/service_worker/service_worker_event_dispatcher.mojom.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/mojom/service_worker/service_worker_event_status.mojom.h"
+#include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace content {
+
+namespace {
+
+// Synchronous proxies to a wrapped CookieStore service's methods.
+class CookieStoreSync {
+ public:
+  using Subscriptions = std::vector<blink::mojom::CookieChangeSubscriptionPtr>;
+
+  // The caller must ensure that the CookieStore service outlives this.
+  explicit CookieStoreSync(blink::mojom::CookieStore* cookie_store_service)
+      : cookie_store_service_(cookie_store_service) {}
+  ~CookieStoreSync() = default;
+
+  bool AppendSubscriptions(int64_t service_worker_registration_id,
+                           Subscriptions subscriptions) {
+    bool success;
+    base::RunLoop run_loop;
+    cookie_store_service_->AppendSubscriptions(
+        service_worker_registration_id, std::move(subscriptions),
+        base::BindOnce(
+            [](base::RunLoop* run_loop, bool* success, bool service_success) {
+              *success = service_success;
+              run_loop->Quit();
+            },
+            &run_loop, &success));
+    run_loop.Run();
+    return success;
+  }
+
+  Subscriptions GetSubscriptions(int64_t service_worker_registration_id) {
+    Subscriptions result;
+    base::RunLoop run_loop;
+    cookie_store_service_->GetSubscriptions(
+        service_worker_registration_id,
+        base::BindOnce(
+            [](base::RunLoop* run_loop, Subscriptions* result,
+               Subscriptions service_result, bool success) {
+              *result = std::move(service_result);
+              run_loop->Quit();
+              EXPECT_TRUE(success) << "GetSubscriptions failed";
+            },
+            &run_loop, &result));
+    run_loop.Run();
+    return result;
+  }
+
+ private:
+  blink::mojom::CookieStore* cookie_store_service_;
+
+  DISALLOW_COPY_AND_ASSIGN(CookieStoreSync);
+};
+
+const char kExampleScope[] = "https://example.com/a";
+const char kExampleWorkerScript[] = "https://example.com/a/script.js";
+const char kGoogleScope[] = "https://google.com/a";
+const char kGoogleWorkerScript[] = "https://google.com/a/script.js";
+
+// Mocks a service worker that uses the cookieStore API.
+class CookieStoreWorkerTestHelper : public EmbeddedWorkerTestHelper {
+ public:
+  using EmbeddedWorkerTestHelper::EmbeddedWorkerTestHelper;
+
+  // Sets the cookie change subscriptions requested in the next install event.
+  void SetOnInstallSubscriptions(
+      std::vector<CookieStoreSync::Subscriptions> subscription_batches,
+      blink::mojom::CookieStore* cookie_store_service) {
+    install_subscription_batches_ = std::move(subscription_batches);
+    cookie_store_service_ = cookie_store_service;
+  }
+
+  // Spins inside a run loop until a service worker activate event is received.
+  void WaitForActivateEvent() {
+    base::RunLoop run_loop;
+    quit_on_activate_ = &run_loop;
+    run_loop.Run();
+  }
+
+  // The data in the CookieChangeEvents received by the worker.
+  std::vector<
+      std::pair<net::CanonicalCookie, ::network::mojom::CookieChangeCause>>&
+  changes() {
+    return changes_;
+  }
+
+ protected:
+  // Collects the worker's registration ID for OnInstallEvent().
+  void OnStartWorker(
+      int embedded_worker_id,
+      int64_t service_worker_version_id,
+      const GURL& scope,
+      const GURL& script_url,
+      bool pause_after_download,
+      mojom::ServiceWorkerEventDispatcherRequest dispatcher_request,
+      mojom::ControllerServiceWorkerRequest controller_request,
+      mojom::EmbeddedWorkerInstanceHostAssociatedPtrInfo instance_host,
+      mojom::ServiceWorkerProviderInfoForStartWorkerPtr provider_info,
+      blink::mojom::ServiceWorkerInstalledScriptsInfoPtr installed_scripts_info)
+      override {
+    ServiceWorkerVersion* service_worker_version =
+        context()->GetLiveVersion(service_worker_version_id);
+    DCHECK(service_worker_version);
+    service_worker_registration_id_ = service_worker_version->registration_id();
+
+    EmbeddedWorkerTestHelper::OnStartWorker(
+        embedded_worker_id, service_worker_version_id, scope, script_url,
+        pause_after_download, std::move(dispatcher_request),
+        std::move(controller_request), std::move(instance_host),
+        std::move(provider_info), std::move(installed_scripts_info));
+  }
+
+  // Cookie change subscriptions can only be created in this event handler.
+  void OnInstallEvent(
+      mojom::ServiceWorkerEventDispatcher::DispatchInstallEventCallback
+          callback) override {
+    for (auto& subscriptions : install_subscription_batches_) {
+      cookie_store_service_->AppendSubscriptions(
+          service_worker_registration_id_, std::move(subscriptions),
+          base::BindOnce([](bool success) {
+            CHECK(success) << "AppendSubscriptions failed";
+          }));
+    }
+    install_subscription_batches_.clear();
+
+    EmbeddedWorkerTestHelper::OnInstallEvent(std::move(callback));
+  }
+
+  // Used to implement WaitForActivateEvent().
+  void OnActivateEvent(
+      mojom::ServiceWorkerEventDispatcher::DispatchActivateEventCallback
+          callback) override {
+    if (quit_on_activate_) {
+      quit_on_activate_->Quit();
+      quit_on_activate_ = nullptr;
+    }
+
+    EmbeddedWorkerTestHelper::OnActivateEvent(std::move(callback));
+  }
+
+  void OnCookieChangeEvent(
+      const net::CanonicalCookie& cookie,
+      ::network::mojom::CookieChangeCause cause,
+      mojom::ServiceWorkerEventDispatcher::DispatchCookieChangeEventCallback
+          callback) override {
+    changes_.emplace_back(cookie, cause);
+    std::move(callback).Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED,
+                            base::Time::Now());
+  }
+
+ private:
+  // Used to add cookie change subscriptions during OnInstallEvent().
+  blink::mojom::CookieStore* cookie_store_service_ = nullptr;
+  std::vector<CookieStoreSync::Subscriptions> install_subscription_batches_;
+  int64_t service_worker_registration_id_;
+
+  // Set by WaitForActivateEvent(), used in OnActivateEvent().
+  base::RunLoop* quit_on_activate_ = nullptr;
+
+  // Collects the changes reported to OnCookieChangeEvent().
+  std::vector<
+      std::pair<net::CanonicalCookie, ::network::mojom::CookieChangeCause>>
+      changes_;
+};
+
+}  // namespace
+
+// This class cannot be in an anonymous namespace because it needs to be a
+// friend of StoragePartitionImpl, to access its constructor.
+class CookieStoreManagerTest
+    : public testing::Test,
+      public testing::WithParamInterface<bool /* reset_context */> {
+ public:
+  CookieStoreManagerTest()
+      : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {}
+
+  void SetUp() override {
+    // Use an on-disk service worker storage to test saving and loading.
+    ASSERT_TRUE(user_data_directory_.CreateUniqueTempDir());
+
+    ResetServiceWorkerContext();
+  }
+
+  void TearDown() override {
+    thread_bundle_.RunUntilIdle();
+
+    // Smart pointers are reset manually in destruction order because this is
+    // called by ResetServiceWorkerContext().
+    example_service_.reset();
+    google_service_.reset();
+    example_service_ptr_.reset();
+    google_service_ptr_.reset();
+    cookie_manager_.reset();
+    cookie_store_context_ = nullptr;
+    storage_partition_impl_.reset();
+    worker_test_helper_.reset();
+  }
+
+  void ResetServiceWorkerContext() {
+    if (cookie_store_context_)
+      TearDown();
+
+    worker_test_helper_ = std::make_unique<CookieStoreWorkerTestHelper>(
+        user_data_directory_.GetPath());
+    cookie_store_context_ = base::MakeRefCounted<CookieStoreContext>();
+    cookie_store_context_->Initialize(worker_test_helper_->context_wrapper(),
+                                      base::BindOnce([](bool success) {
+                                        CHECK(success) << "Initialize failed";
+                                      }));
+    storage_partition_impl_ = base::WrapUnique(
+        new StoragePartitionImpl(worker_test_helper_->browser_context(),
+                                 user_data_directory_.GetPath(), nullptr));
+    storage_partition_impl_->SetURLRequestContext(
+        worker_test_helper_->browser_context()
+            ->CreateRequestContextForStoragePartition(
+                user_data_directory_.GetPath(), false, nullptr,
+                URLRequestInterceptorScopedVector()));
+    ::network::mojom::NetworkContext* network_context =
+        storage_partition_impl_->GetNetworkContext();
+    cookie_store_context_->ListenToCookieChanges(
+        network_context, base::BindOnce([](bool success) {
+          CHECK(success) << "ListenToCookieChanges failed";
+        }));
+    network_context->GetCookieManager(mojo::MakeRequest(&cookie_manager_));
+
+    cookie_store_context_->CreateService(
+        mojo::MakeRequest(&example_service_ptr_),
+        url::Origin::Create(GURL(kExampleScope)));
+    example_service_ =
+        std::make_unique<CookieStoreSync>(example_service_ptr_.get());
+
+    cookie_store_context_->CreateService(
+        mojo::MakeRequest(&google_service_ptr_),
+        url::Origin::Create(GURL(kGoogleScope)));
+    google_service_ =
+        std::make_unique<CookieStoreSync>(google_service_ptr_.get());
+  }
+
+  int64_t RegisterServiceWorker(const char* scope, const char* script_url) {
+    bool success = false;
+    int64_t registration_id;
+    blink::mojom::ServiceWorkerRegistrationOptions options;
+    options.scope = GURL(scope);
+    base::RunLoop run_loop;
+    worker_test_helper_->context()->RegisterServiceWorker(
+        GURL(script_url), options,
+        base::BindOnce(
+            [](base::RunLoop* run_loop, bool* success, int64_t* registration_id,
+               ServiceWorkerStatusCode status,
+               const std::string& status_message,
+               int64_t service_worker_registration_id) {
+              *success = (status == SERVICE_WORKER_OK);
+              *registration_id = service_worker_registration_id;
+              EXPECT_EQ(SERVICE_WORKER_OK, status)
+                  << ServiceWorkerStatusToString(status);
+              run_loop->Quit();
+            },
+            &run_loop, &success, &registration_id));
+    run_loop.Run();
+    if (!success)
+      return kInvalidRegistrationId;
+
+    worker_test_helper_->WaitForActivateEvent();
+    return registration_id;
+  }
+
+  // Simplified helper for SetCanonicalCookie.
+  //
+  // Creates a CanonicalCookie that is not secure, not http-only,
+  // and not restricted to first parties. Returns false if creation fails.
+  bool SetSessionCookie(const char* name,
+                        const char* value,
+                        const char* domain,
+                        const char* path) {
+    net::CanonicalCookie cookie(
+        name, value, domain, path, base::Time(), base::Time(), base::Time(),
+        /* secure = */ false,
+        /* httponly = */ false, net::CookieSameSite::NO_RESTRICTION,
+        net::COOKIE_PRIORITY_DEFAULT);
+    base::RunLoop run_loop;
+    bool success = false;
+    cookie_manager_->SetCanonicalCookie(
+        cookie, /* secure_source = */ true, /* can_modify_httponly = */ true,
+        base::BindOnce(
+            [](base::RunLoop* run_loop, bool* success, bool service_success) {
+              *success = success;
+              run_loop->Quit();
+            },
+            &run_loop, &success));
+    run_loop.Run();
+    return success;
+  }
+
+  bool reset_context_during_test() const { return GetParam(); }
+
+  static constexpr const int64_t kInvalidRegistrationId = -1;
+
+ protected:
+  TestBrowserThreadBundle thread_bundle_;
+  base::ScopedTempDir user_data_directory_;
+  std::unique_ptr<CookieStoreWorkerTestHelper> worker_test_helper_;
+  std::unique_ptr<StoragePartitionImpl> storage_partition_impl_;
+  scoped_refptr<CookieStoreContext> cookie_store_context_;
+  ::network::mojom::CookieManagerPtr cookie_manager_;
+
+  blink::mojom::CookieStorePtr example_service_ptr_, google_service_ptr_;
+  std::unique_ptr<CookieStoreSync> example_service_, google_service_;
+};
+
+const int64_t CookieStoreManagerTest::kInvalidRegistrationId;
+
+namespace {
+
+// Useful for sorting a vector of cookie change subscriptions.
+bool CookieChangeSubscriptionLessThan(
+    const blink::mojom::CookieChangeSubscriptionPtr& lhs,
+    const blink::mojom::CookieChangeSubscriptionPtr& rhs) {
+  return std::tie(lhs->name, lhs->match_type, lhs->url) <
+         std::tie(rhs->name, rhs->match_type, rhs->url);
+}
+
+TEST_P(CookieStoreManagerTest, NoSubscriptions) {
+  worker_test_helper_->SetOnInstallSubscriptions(
+      std::vector<CookieStoreSync::Subscriptions>(),
+      example_service_ptr_.get());
+  int64_t registration_id =
+      RegisterServiceWorker(kExampleScope, kExampleWorkerScript);
+  ASSERT_NE(registration_id, kInvalidRegistrationId);
+
+  if (reset_context_during_test())
+    ResetServiceWorkerContext();
+
+  std::vector<blink::mojom::CookieChangeSubscriptionPtr> all_subscriptions =
+      example_service_->GetSubscriptions(registration_id);
+  EXPECT_EQ(0u, all_subscriptions.size());
+}
+
+TEST_P(CookieStoreManagerTest, EmptySubscriptions) {
+  std::vector<CookieStoreSync::Subscriptions> batches;
+  batches.emplace_back();
+  worker_test_helper_->SetOnInstallSubscriptions(std::move(batches),
+                                                 example_service_ptr_.get());
+  int64_t registration_id =
+      RegisterServiceWorker(kExampleScope, kExampleWorkerScript);
+  ASSERT_NE(registration_id, kInvalidRegistrationId);
+
+  if (reset_context_during_test())
+    ResetServiceWorkerContext();
+
+  std::vector<blink::mojom::CookieChangeSubscriptionPtr> all_subscriptions =
+      example_service_->GetSubscriptions(registration_id);
+  EXPECT_EQ(0u, all_subscriptions.size());
+}
+
+TEST_P(CookieStoreManagerTest, OneSubscription) {
+  std::vector<CookieStoreSync::Subscriptions> batches;
+  batches.emplace_back();
+
+  CookieStoreSync::Subscriptions& subscriptions = batches.back();
+  subscriptions.emplace_back(blink::mojom::CookieChangeSubscription::New());
+  subscriptions.back()->name = "cookie_name_prefix";
+  subscriptions.back()->match_type =
+      ::network::mojom::CookieMatchType::STARTS_WITH;
+  subscriptions.back()->url = GURL(kExampleScope);
+
+  worker_test_helper_->SetOnInstallSubscriptions(std::move(batches),
+                                                 example_service_ptr_.get());
+  int64_t registration_id =
+      RegisterServiceWorker(kExampleScope, kExampleWorkerScript);
+  ASSERT_NE(registration_id, kInvalidRegistrationId);
+
+  if (reset_context_during_test())
+    ResetServiceWorkerContext();
+
+  std::vector<blink::mojom::CookieChangeSubscriptionPtr> all_subscriptions =
+      example_service_->GetSubscriptions(registration_id);
+  EXPECT_EQ(1u, all_subscriptions.size());
+  EXPECT_EQ("cookie_name_prefix", all_subscriptions[0]->name);
+  EXPECT_EQ(::network::mojom::CookieMatchType::STARTS_WITH,
+            all_subscriptions[0]->match_type);
+  EXPECT_EQ(GURL(kExampleScope), all_subscriptions[0]->url);
+}
+
+TEST_P(CookieStoreManagerTest, AppendSubscriptionsAfterEmptyInstall) {
+  worker_test_helper_->SetOnInstallSubscriptions(
+      std::vector<CookieStoreSync::Subscriptions>(),
+      example_service_ptr_.get());
+  int64_t registration_id =
+      RegisterServiceWorker(kExampleScope, kExampleWorkerScript);
+  ASSERT_NE(registration_id, kInvalidRegistrationId);
+
+  CookieStoreSync::Subscriptions subscriptions;
+  subscriptions.emplace_back(blink::mojom::CookieChangeSubscription::New());
+  subscriptions.back()->name = "cookie_name_prefix";
+  subscriptions.back()->match_type =
+      ::network::mojom::CookieMatchType::STARTS_WITH;
+  subscriptions.back()->url = GURL(kExampleScope);
+
+  EXPECT_FALSE(example_service_->AppendSubscriptions(registration_id,
+                                                     std::move(subscriptions)));
+
+  if (reset_context_during_test())
+    ResetServiceWorkerContext();
+
+  std::vector<blink::mojom::CookieChangeSubscriptionPtr> all_subscriptions =
+      example_service_->GetSubscriptions(registration_id);
+  EXPECT_EQ(0u, all_subscriptions.size());
+}
+
+TEST_P(CookieStoreManagerTest, AppendSubscriptionsAfterInstall) {
+  {
+    std::vector<CookieStoreSync::Subscriptions> batches;
+    batches.emplace_back();
+
+    CookieStoreSync::Subscriptions& subscriptions = batches.back();
+    subscriptions.emplace_back(blink::mojom::CookieChangeSubscription::New());
+    subscriptions.back()->name = "cookie_name_prefix";
+    subscriptions.back()->match_type =
+        ::network::mojom::CookieMatchType::STARTS_WITH;
+    subscriptions.back()->url = GURL(kExampleScope);
+
+    worker_test_helper_->SetOnInstallSubscriptions(std::move(batches),
+                                                   example_service_ptr_.get());
+  }
+  int64_t registration_id =
+      RegisterServiceWorker(kExampleScope, kExampleWorkerScript);
+  ASSERT_NE(registration_id, kInvalidRegistrationId);
+
+  {
+    CookieStoreSync::Subscriptions subscriptions;
+    subscriptions.emplace_back(blink::mojom::CookieChangeSubscription::New());
+    subscriptions.back()->name = "cookie_name";
+    subscriptions.back()->match_type =
+        ::network::mojom::CookieMatchType::EQUALS;
+    subscriptions.back()->url = GURL(kExampleScope);
+
+    EXPECT_FALSE(example_service_->AppendSubscriptions(
+        registration_id, std::move(subscriptions)));
+  }
+
+  if (reset_context_during_test())
+    ResetServiceWorkerContext();
+
+  std::vector<blink::mojom::CookieChangeSubscriptionPtr> all_subscriptions =
+      example_service_->GetSubscriptions(registration_id);
+  EXPECT_EQ(1u, all_subscriptions.size());
+  EXPECT_EQ("cookie_name_prefix", all_subscriptions[0]->name);
+  EXPECT_EQ(::network::mojom::CookieMatchType::STARTS_WITH,
+            all_subscriptions[0]->match_type);
+  EXPECT_EQ(GURL(kExampleScope), all_subscriptions[0]->url);
+}
+
+TEST_P(CookieStoreManagerTest, AppendSubscriptionsInvalidRegistrationId) {
+  worker_test_helper_->SetOnInstallSubscriptions(
+      std::vector<CookieStoreSync::Subscriptions>(),
+      example_service_ptr_.get());
+  int64_t registration_id =
+      RegisterServiceWorker(kExampleScope, kExampleWorkerScript);
+  ASSERT_NE(registration_id, kInvalidRegistrationId);
+
+  if (reset_context_during_test())
+    ResetServiceWorkerContext();
+
+  CookieStoreSync::Subscriptions subscriptions;
+  subscriptions.emplace_back(blink::mojom::CookieChangeSubscription::New());
+  subscriptions.back()->name = "cookie_name_prefix";
+  subscriptions.back()->match_type =
+      ::network::mojom::CookieMatchType::STARTS_WITH;
+  subscriptions.back()->url = GURL(kExampleScope);
+
+  EXPECT_FALSE(example_service_->AppendSubscriptions(registration_id + 100,
+                                                     std::move(subscriptions)));
+
+  std::vector<blink::mojom::CookieChangeSubscriptionPtr> all_subscriptions =
+      example_service_->GetSubscriptions(registration_id);
+  EXPECT_EQ(0u, all_subscriptions.size());
+}
+
+TEST_P(CookieStoreManagerTest, MultiWorkerSubscriptions) {
+  {
+    std::vector<CookieStoreSync::Subscriptions> batches;
+    batches.emplace_back();
+
+    CookieStoreSync::Subscriptions& subscriptions = batches.back();
+    subscriptions.emplace_back(blink::mojom::CookieChangeSubscription::New());
+    subscriptions.back()->name = "cookie_name_prefix";
+    subscriptions.back()->match_type =
+        ::network::mojom::CookieMatchType::STARTS_WITH;
+    subscriptions.back()->url = GURL(kExampleScope);
+
+    worker_test_helper_->SetOnInstallSubscriptions(std::move(batches),
+                                                   example_service_ptr_.get());
+  }
+  int64_t example_registration_id =
+      RegisterServiceWorker(kExampleScope, kExampleWorkerScript);
+  ASSERT_NE(example_registration_id, kInvalidRegistrationId);
+
+  {
+    std::vector<CookieStoreSync::Subscriptions> batches;
+    batches.emplace_back();
+
+    CookieStoreSync::Subscriptions& subscriptions = batches.back();
+    subscriptions.emplace_back(blink::mojom::CookieChangeSubscription::New());
+    subscriptions.back()->name = "cookie_name";
+    subscriptions.back()->match_type =
+        ::network::mojom::CookieMatchType::EQUALS;
+    subscriptions.back()->url = GURL(kGoogleScope);
+
+    worker_test_helper_->SetOnInstallSubscriptions(std::move(batches),
+                                                   google_service_ptr_.get());
+  }
+  int64_t google_registration_id =
+      RegisterServiceWorker(kGoogleScope, kGoogleWorkerScript);
+  ASSERT_NE(google_registration_id, kInvalidRegistrationId);
+  EXPECT_NE(example_registration_id, google_registration_id);
+
+  if (reset_context_during_test())
+    ResetServiceWorkerContext();
+
+  std::vector<blink::mojom::CookieChangeSubscriptionPtr> example_subscriptions =
+      example_service_->GetSubscriptions(example_registration_id);
+  EXPECT_EQ(1u, example_subscriptions.size());
+  EXPECT_EQ("cookie_name_prefix", example_subscriptions[0]->name);
+  EXPECT_EQ(::network::mojom::CookieMatchType::STARTS_WITH,
+            example_subscriptions[0]->match_type);
+  EXPECT_EQ(GURL(kExampleScope), example_subscriptions[0]->url);
+
+  std::vector<blink::mojom::CookieChangeSubscriptionPtr> google_subscriptions =
+      google_service_->GetSubscriptions(google_registration_id);
+  EXPECT_EQ(1u, google_subscriptions.size());
+  EXPECT_EQ("cookie_name", google_subscriptions[0]->name);
+  EXPECT_EQ(::network::mojom::CookieMatchType::EQUALS,
+            google_subscriptions[0]->match_type);
+  EXPECT_EQ(GURL(kGoogleScope), google_subscriptions[0]->url);
+}
+
+TEST_P(CookieStoreManagerTest, MultipleSubscriptions) {
+  std::vector<CookieStoreSync::Subscriptions> batches;
+
+  {
+    batches.emplace_back();
+    CookieStoreSync::Subscriptions& subscriptions = batches.back();
+
+    subscriptions.emplace_back(blink::mojom::CookieChangeSubscription::New());
+    subscriptions.back()->name = "name1";
+    subscriptions.back()->match_type =
+        ::network::mojom::CookieMatchType::STARTS_WITH;
+    subscriptions.back()->url = GURL("https://example.com/a/1");
+    subscriptions.emplace_back(blink::mojom::CookieChangeSubscription::New());
+    subscriptions.back()->name = "name2";
+    subscriptions.back()->match_type =
+        ::network::mojom::CookieMatchType::EQUALS;
+    subscriptions.back()->url = GURL("https://example.com/a/2");
+  }
+
+  batches.emplace_back();
+
+  {
+    batches.emplace_back();
+    CookieStoreSync::Subscriptions& subscriptions = batches.back();
+
+    subscriptions.emplace_back(blink::mojom::CookieChangeSubscription::New());
+    subscriptions.back()->name = "name3";
+    subscriptions.back()->match_type =
+        ::network::mojom::CookieMatchType::STARTS_WITH;
+    subscriptions.back()->url = GURL("https://example.com/a/3");
+  }
+
+  worker_test_helper_->SetOnInstallSubscriptions(std::move(batches),
+                                                 example_service_ptr_.get());
+  int64_t registration_id =
+      RegisterServiceWorker(kExampleScope, kExampleWorkerScript);
+  ASSERT_NE(registration_id, kInvalidRegistrationId);
+
+  if (reset_context_during_test())
+    ResetServiceWorkerContext();
+
+  std::vector<blink::mojom::CookieChangeSubscriptionPtr> all_subscriptions =
+      example_service_->GetSubscriptions(registration_id);
+
+  std::sort(all_subscriptions.begin(), all_subscriptions.end(),
+            CookieChangeSubscriptionLessThan);
+
+  EXPECT_EQ(3u, all_subscriptions.size());
+  EXPECT_EQ("name1", all_subscriptions[0]->name);
+  EXPECT_EQ(::network::mojom::CookieMatchType::STARTS_WITH,
+            all_subscriptions[0]->match_type);
+  EXPECT_EQ(GURL("https://example.com/a/1"), all_subscriptions[0]->url);
+  EXPECT_EQ("name2", all_subscriptions[1]->name);
+  EXPECT_EQ(::network::mojom::CookieMatchType::EQUALS,
+            all_subscriptions[1]->match_type);
+  EXPECT_EQ(GURL("https://example.com/a/2"), all_subscriptions[1]->url);
+  EXPECT_EQ("name3", all_subscriptions[2]->name);
+  EXPECT_EQ(::network::mojom::CookieMatchType::STARTS_WITH,
+            all_subscriptions[2]->match_type);
+  EXPECT_EQ(GURL("https://example.com/a/3"), all_subscriptions[2]->url);
+}
+
+TEST_P(CookieStoreManagerTest, OneCookieChange) {
+  std::vector<CookieStoreSync::Subscriptions> batches;
+  batches.emplace_back();
+
+  CookieStoreSync::Subscriptions& subscriptions = batches.back();
+  subscriptions.emplace_back(blink::mojom::CookieChangeSubscription::New());
+  subscriptions.back()->name = "";
+  subscriptions.back()->match_type =
+      ::network::mojom::CookieMatchType::STARTS_WITH;
+  subscriptions.back()->url = GURL(kExampleScope);
+
+  worker_test_helper_->SetOnInstallSubscriptions(std::move(batches),
+                                                 example_service_ptr_.get());
+  int64_t registration_id =
+      RegisterServiceWorker(kExampleScope, kExampleWorkerScript);
+  ASSERT_NE(registration_id, kInvalidRegistrationId);
+
+  std::vector<blink::mojom::CookieChangeSubscriptionPtr> all_subscriptions =
+      example_service_->GetSubscriptions(registration_id);
+  ASSERT_EQ(1u, all_subscriptions.size());
+
+  if (reset_context_during_test())
+    ResetServiceWorkerContext();
+
+  ASSERT_TRUE(
+      SetSessionCookie("cookie-name", "cookie-value", "example.com", "/"));
+  thread_bundle_.RunUntilIdle();
+
+  ASSERT_EQ(1u, worker_test_helper_->changes().size());
+  EXPECT_EQ("cookie-name", worker_test_helper_->changes()[0].first.Name());
+  EXPECT_EQ("cookie-value", worker_test_helper_->changes()[0].first.Value());
+  EXPECT_EQ("example.com", worker_test_helper_->changes()[0].first.Domain());
+  EXPECT_EQ("/", worker_test_helper_->changes()[0].first.Path());
+  EXPECT_EQ(::network::mojom::CookieChangeCause::INSERTED,
+            worker_test_helper_->changes()[0].second);
+}
+
+TEST_P(CookieStoreManagerTest, CookieChangeNameStartsWith) {
+  std::vector<CookieStoreSync::Subscriptions> batches;
+  batches.emplace_back();
+
+  CookieStoreSync::Subscriptions& subscriptions = batches.back();
+  subscriptions.emplace_back(blink::mojom::CookieChangeSubscription::New());
+  subscriptions.back()->name = "cookie-name-2";
+  subscriptions.back()->match_type =
+      ::network::mojom::CookieMatchType::STARTS_WITH;
+  subscriptions.back()->url = GURL(kExampleScope);
+
+  worker_test_helper_->SetOnInstallSubscriptions(std::move(batches),
+                                                 example_service_ptr_.get());
+  int64_t registration_id =
+      RegisterServiceWorker(kExampleScope, kExampleWorkerScript);
+  ASSERT_NE(registration_id, kInvalidRegistrationId);
+
+  std::vector<blink::mojom::CookieChangeSubscriptionPtr> all_subscriptions =
+      example_service_->GetSubscriptions(registration_id);
+  ASSERT_EQ(1u, all_subscriptions.size());
+
+  if (reset_context_during_test())
+    ResetServiceWorkerContext();
+
+  ASSERT_TRUE(
+      SetSessionCookie("cookie-name-1", "cookie-value-1", "example.com", "/"));
+  thread_bundle_.RunUntilIdle();
+  EXPECT_EQ(0u, worker_test_helper_->changes().size());
+
+  worker_test_helper_->changes().clear();
+  ASSERT_TRUE(
+      SetSessionCookie("cookie-name-2", "cookie-value-2", "example.com", "/"));
+  thread_bundle_.RunUntilIdle();
+
+  ASSERT_EQ(1u, worker_test_helper_->changes().size());
+  EXPECT_EQ("cookie-name-2", worker_test_helper_->changes()[0].first.Name());
+  EXPECT_EQ("cookie-value-2", worker_test_helper_->changes()[0].first.Value());
+  EXPECT_EQ("example.com", worker_test_helper_->changes()[0].first.Domain());
+  EXPECT_EQ("/", worker_test_helper_->changes()[0].first.Path());
+  EXPECT_EQ(::network::mojom::CookieChangeCause::INSERTED,
+            worker_test_helper_->changes()[0].second);
+
+  worker_test_helper_->changes().clear();
+  ASSERT_TRUE(SetSessionCookie("cookie-name-22", "cookie-value-22",
+                               "example.com", "/"));
+  thread_bundle_.RunUntilIdle();
+
+  ASSERT_EQ(1u, worker_test_helper_->changes().size());
+  EXPECT_EQ("cookie-name-22", worker_test_helper_->changes()[0].first.Name());
+  EXPECT_EQ("cookie-value-22", worker_test_helper_->changes()[0].first.Value());
+  EXPECT_EQ("example.com", worker_test_helper_->changes()[0].first.Domain());
+  EXPECT_EQ("/", worker_test_helper_->changes()[0].first.Path());
+  EXPECT_EQ(::network::mojom::CookieChangeCause::INSERTED,
+            worker_test_helper_->changes()[0].second);
+}
+
+TEST_P(CookieStoreManagerTest, CookieChangeUrl) {
+  std::vector<CookieStoreSync::Subscriptions> batches;
+  batches.emplace_back();
+
+  CookieStoreSync::Subscriptions& subscriptions = batches.back();
+  subscriptions.emplace_back(blink::mojom::CookieChangeSubscription::New());
+  subscriptions.back()->name = "";
+  subscriptions.back()->match_type =
+      ::network::mojom::CookieMatchType::STARTS_WITH;
+  subscriptions.back()->url = GURL(kExampleScope);
+
+  worker_test_helper_->SetOnInstallSubscriptions(std::move(batches),
+                                                 example_service_ptr_.get());
+  int64_t registration_id =
+      RegisterServiceWorker(kExampleScope, kExampleWorkerScript);
+  ASSERT_NE(registration_id, kInvalidRegistrationId);
+
+  std::vector<blink::mojom::CookieChangeSubscriptionPtr> all_subscriptions =
+      example_service_->GetSubscriptions(registration_id);
+  ASSERT_EQ(1u, all_subscriptions.size());
+
+  if (reset_context_during_test())
+    ResetServiceWorkerContext();
+
+  ASSERT_TRUE(
+      SetSessionCookie("cookie-name-1", "cookie-value-1", "google.com", "/"));
+  thread_bundle_.RunUntilIdle();
+  ASSERT_EQ(0u, worker_test_helper_->changes().size());
+
+  worker_test_helper_->changes().clear();
+  ASSERT_TRUE(SetSessionCookie("cookie-name-2", "cookie-value-2", "example.com",
+                               "/a/subpath"));
+  thread_bundle_.RunUntilIdle();
+  EXPECT_EQ(0u, worker_test_helper_->changes().size());
+
+  worker_test_helper_->changes().clear();
+  ASSERT_TRUE(
+      SetSessionCookie("cookie-name-3", "cookie-value-3", "example.com", "/"));
+  thread_bundle_.RunUntilIdle();
+
+  ASSERT_EQ(1u, worker_test_helper_->changes().size());
+  EXPECT_EQ("cookie-name-3", worker_test_helper_->changes()[0].first.Name());
+  EXPECT_EQ("cookie-value-3", worker_test_helper_->changes()[0].first.Value());
+  EXPECT_EQ("example.com", worker_test_helper_->changes()[0].first.Domain());
+  EXPECT_EQ("/", worker_test_helper_->changes()[0].first.Path());
+  EXPECT_EQ(::network::mojom::CookieChangeCause::INSERTED,
+            worker_test_helper_->changes()[0].second);
+
+  worker_test_helper_->changes().clear();
+  ASSERT_TRUE(
+      SetSessionCookie("cookie-name-4", "cookie-value-4", "example.com", "/a"));
+  thread_bundle_.RunUntilIdle();
+
+  ASSERT_EQ(1u, worker_test_helper_->changes().size());
+  EXPECT_EQ("cookie-name-4", worker_test_helper_->changes()[0].first.Name());
+  EXPECT_EQ("cookie-value-4", worker_test_helper_->changes()[0].first.Value());
+  EXPECT_EQ("example.com", worker_test_helper_->changes()[0].first.Domain());
+  EXPECT_EQ("/a", worker_test_helper_->changes()[0].first.Path());
+  EXPECT_EQ(::network::mojom::CookieChangeCause::INSERTED,
+            worker_test_helper_->changes()[0].second);
+}
+
+INSTANTIATE_TEST_CASE_P(CookieStoreManagerTest,
+                        CookieStoreManagerTest,
+                        testing::Bool() /* reset_storage_during_test */);
+
+}  // namespace
+
+}  // namespace content
diff --git a/content/browser/media/audio_input_stream_broker_unittest.cc b/content/browser/media/audio_input_stream_broker_unittest.cc
index 956f5f04..f0e1ce4 100644
--- a/content/browser/media/audio_input_stream_broker_unittest.cc
+++ b/content/browser/media/audio_input_stream_broker_unittest.cc
@@ -68,6 +68,7 @@
   mojo::Binding<mojom::RendererAudioInputStreamFactoryClient> binding_;
   media::mojom::AudioInputStreamPtr input_stream_;
   media::mojom::AudioInputStreamClientRequest client_request_;
+  DISALLOW_COPY_AND_ASSIGN(MockRendererAudioInputStreamFactoryClient);
 };
 
 class MockStreamFactory : public audio::FakeStreamFactory {
@@ -128,6 +129,7 @@
   }
 
   StreamRequestData* stream_request_data_;
+  DISALLOW_COPY_AND_ASSIGN(MockStreamFactory);
 };
 
 struct TestEnvironment {
diff --git a/content/browser/media/audio_loopback_stream_broker.cc b/content/browser/media/audio_loopback_stream_broker.cc
new file mode 100644
index 0000000..6d1b8af
--- /dev/null
+++ b/content/browser/media/audio_loopback_stream_broker.cc
@@ -0,0 +1,163 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/media/audio_loopback_stream_broker.h"
+
+#include <utility>
+
+#include "base/unguessable_token.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+
+namespace content {
+
+AudioStreamBrokerFactory::LoopbackSource::LoopbackSource() = default;
+
+AudioStreamBrokerFactory::LoopbackSource::LoopbackSource(
+    WebContents* source_contents)
+    : WebContentsObserver(source_contents) {
+  DCHECK(source_contents);
+}
+
+AudioStreamBrokerFactory::LoopbackSource::~LoopbackSource() = default;
+
+base::UnguessableToken AudioStreamBrokerFactory::LoopbackSource::GetGroupID() {
+  if (WebContentsImpl* source_contents =
+          static_cast<WebContentsImpl*>(web_contents())) {
+    return source_contents->GetAudioStreamFactory()->group_id();
+  }
+  return base::UnguessableToken();
+}
+
+void AudioStreamBrokerFactory::LoopbackSource::OnStartCapturing() {
+  if (WebContentsImpl* source_contents =
+          static_cast<WebContentsImpl*>(web_contents())) {
+    source_contents->IncrementCapturerCount(gfx::Size());
+  }
+}
+
+void AudioStreamBrokerFactory::LoopbackSource::OnStopCapturing() {
+  if (WebContentsImpl* source_contents =
+          static_cast<WebContentsImpl*>(web_contents())) {
+    source_contents->DecrementCapturerCount();
+  }
+}
+
+void AudioStreamBrokerFactory::LoopbackSource::WebContentsDestroyed() {
+  if (on_gone_closure_)
+    std::move(on_gone_closure_).Run();
+}
+
+AudioLoopbackStreamBroker::AudioLoopbackStreamBroker(
+    int render_process_id,
+    int render_frame_id,
+    std::unique_ptr<AudioStreamBrokerFactory::LoopbackSource> source,
+    const media::AudioParameters& params,
+    uint32_t shared_memory_count,
+    bool mute_source,
+    AudioStreamBroker::DeleterCallback deleter,
+    mojom::RendererAudioInputStreamFactoryClientPtr renderer_factory_client)
+    : AudioStreamBroker(render_process_id, render_frame_id),
+      source_(std::move(source)),
+      params_(params),
+      shared_memory_count_(shared_memory_count),
+      deleter_(std::move(deleter)),
+      renderer_factory_client_(std::move(renderer_factory_client)),
+      observer_binding_(this),
+      weak_ptr_factory_(this) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK(source_);
+  DCHECK(source_->GetGroupID());
+  DCHECK(renderer_factory_client_);
+  DCHECK(deleter_);
+
+  // Unretained is safe because |this| owns |source_|.
+  source_->set_on_gone_closure(base::BindOnce(
+      &AudioLoopbackStreamBroker::Cleanup, base::Unretained(this)));
+
+  if (mute_source) {
+    muter_.emplace(source_->GetGroupID());
+  }
+
+  // Unretained is safe because |this| owns |renderer_factory_client_|.
+  renderer_factory_client_.set_connection_error_handler(base::BindOnce(
+      &AudioLoopbackStreamBroker::Cleanup, base::Unretained(this)));
+
+  // Notify the source that we are capturing from it, to prevent its
+  // backgrounding.
+  source_->OnStartCapturing();
+
+  // Notify RenderProcessHost about the input stream, so that the destination
+  // renderer does not get background.
+  if (auto* process_host = RenderProcessHost::FromID(render_process_id))
+    process_host->OnMediaStreamAdded();
+}
+
+AudioLoopbackStreamBroker::~AudioLoopbackStreamBroker() {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  source_->OnStopCapturing();
+
+  if (auto* process_host = RenderProcessHost::FromID(render_process_id()))
+    process_host->OnMediaStreamRemoved();
+}
+
+void AudioLoopbackStreamBroker::CreateStream(
+    audio::mojom::StreamFactory* factory) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK(!observer_binding_.is_bound());
+  DCHECK(!client_request_);
+  DCHECK(source_->GetGroupID());
+
+  if (muter_)  // Mute the source.
+    muter_->Connect(factory);
+
+  media::mojom::AudioInputStreamClientPtr client;
+  client_request_ = mojo::MakeRequest(&client);
+
+  media::mojom::AudioInputStreamPtr stream;
+  media::mojom::AudioInputStreamRequest stream_request =
+      mojo::MakeRequest(&stream);
+
+  media::mojom::AudioInputStreamObserverPtr observer_ptr;
+  observer_binding_.Bind(mojo::MakeRequest(&observer_ptr));
+
+  // Unretained is safe because |this| owns |observer_binding_|.
+  observer_binding_.set_connection_error_handler(base::BindOnce(
+      &AudioLoopbackStreamBroker::Cleanup, base::Unretained(this)));
+
+  factory->CreateLoopbackStream(
+      std::move(stream_request), std::move(client), std::move(observer_ptr),
+      params_, shared_memory_count_, source_->GetGroupID(),
+      base::BindOnce(&AudioLoopbackStreamBroker::StreamCreated,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(stream)));
+}
+
+void AudioLoopbackStreamBroker::DidStartRecording() {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void AudioLoopbackStreamBroker::StreamCreated(
+    media::mojom::AudioInputStreamPtr stream,
+    media::mojom::AudioDataPipePtr data_pipe) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  if (!data_pipe) {
+    Cleanup();
+    return;
+  }
+
+  DCHECK(renderer_factory_client_);
+  renderer_factory_client_->StreamCreated(
+      std::move(stream), std::move(client_request_), std::move(data_pipe),
+      false /* |initially_muted|: Loopback streams are never muted. */);
+}
+
+void AudioLoopbackStreamBroker::Cleanup() {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  std::move(deleter_).Run(this);
+}
+
+}  // namespace content
diff --git a/content/browser/media/audio_loopback_stream_broker.h b/content/browser/media/audio_loopback_stream_broker.h
new file mode 100644
index 0000000..71778564
--- /dev/null
+++ b/content/browser/media/audio_loopback_stream_broker.h
@@ -0,0 +1,74 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_MEDIA_AUDIO_LOOPBACK_STREAM_BROKER_H_
+#define CONTENT_BROWSER_MEDIA_AUDIO_LOOPBACK_STREAM_BROKER_H_
+
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/sequence_checker.h"
+#include "content/browser/media/audio_muting_session.h"
+#include "content/browser/media/audio_stream_broker.h"
+#include "content/common/content_export.h"
+#include "content/common/media/renderer_audio_input_stream_factory.mojom.h"
+#include "media/base/audio_parameters.h"
+#include "media/mojo/interfaces/audio_input_stream.mojom.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "services/audio/public/mojom/stream_factory.mojom.h"
+
+namespace content {
+
+// AudioLoopbackStreamBroker is used to broker a connection between a client
+// (typically renderer) and the audio service. It is operated on the UI thread.
+class CONTENT_EXPORT AudioLoopbackStreamBroker final
+    : public AudioStreamBroker,
+      public media::mojom::AudioInputStreamObserver {
+ public:
+  AudioLoopbackStreamBroker(
+      int render_process_id,
+      int render_frame_id,
+      std::unique_ptr<AudioStreamBrokerFactory::LoopbackSource> source,
+      const media::AudioParameters& params,
+      uint32_t shared_memory_count,
+      bool mute_source,
+      AudioStreamBroker::DeleterCallback deleter,
+      mojom::RendererAudioInputStreamFactoryClientPtr renderer_factory_client);
+
+  ~AudioLoopbackStreamBroker() final;
+
+  // Creates the stream.
+  void CreateStream(audio::mojom::StreamFactory* factory) final;
+
+  // media::AudioInputStreamObserver implementation.
+  void DidStartRecording() final;
+
+ private:
+  void StreamCreated(media::mojom::AudioInputStreamPtr stream,
+                     media::mojom::AudioDataPipePtr data_pipe);
+  void Cleanup();
+
+  const std::unique_ptr<AudioStreamBrokerFactory::LoopbackSource> source_;
+  const media::AudioParameters params_;
+  const uint32_t shared_memory_count_;
+
+  DeleterCallback deleter_;
+
+  // Constructed only if the loopback source playback should be muted while the
+  // loopback stream is running.
+  base::Optional<AudioMutingSession> muter_;
+
+  mojom::RendererAudioInputStreamFactoryClientPtr renderer_factory_client_;
+  mojo::Binding<AudioInputStreamObserver> observer_binding_;
+  media::mojom::AudioInputStreamClientRequest client_request_;
+
+  base::WeakPtrFactory<AudioLoopbackStreamBroker> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(AudioLoopbackStreamBroker);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_MEDIA_AUDIO_LOOPBACK_STREAM_BROKER_H_
diff --git a/content/browser/media/audio_loopback_stream_broker_unittest.cc b/content/browser/media/audio_loopback_stream_broker_unittest.cc
new file mode 100644
index 0000000..0507f990
--- /dev/null
+++ b/content/browser/media/audio_loopback_stream_broker_unittest.cc
@@ -0,0 +1,370 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/media/audio_loopback_stream_broker.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/sync_socket.h"
+#include "base/test/mock_callback.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "media/mojo/interfaces/audio_input_stream.mojom.h"
+#include "mojo/public/cpp/system/platform_handle.h"
+#include "services/audio/public/cpp/fake_stream_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::Test;
+using ::testing::Mock;
+using ::testing::NiceMock;
+using ::testing::StrictMock;
+using ::testing::InSequence;
+
+namespace content {
+
+namespace {
+
+const int kRenderProcessId = 123;
+const int kRenderFrameId = 234;
+const uint32_t kShMemCount = 10;
+
+media::AudioParameters TestParams() {
+  return media::AudioParameters::UnavailableDeviceParams();
+}
+
+class MockSource : public AudioStreamBrokerFactory::LoopbackSource {
+ public:
+  explicit MockSource(const base::UnguessableToken& group_id)
+      : group_id_(group_id) {}
+  ~MockSource() override {}
+
+  // AudioStreamBrokerFactory::LoopbackSource mocking.
+  base::UnguessableToken GetGroupID() override { return group_id_; }
+  MOCK_METHOD0(OnStartCapturing, void(void));
+  MOCK_METHOD0(OnStopCapturing, void(void));
+
+ private:
+  base::UnguessableToken group_id_;
+  DISALLOW_COPY_AND_ASSIGN(MockSource);
+};
+
+using MockDeleterCallback = StrictMock<
+    base::MockCallback<base::OnceCallback<void(AudioStreamBroker*)>>>;
+
+class MockRendererAudioInputStreamFactoryClient
+    : public mojom::RendererAudioInputStreamFactoryClient {
+ public:
+  MockRendererAudioInputStreamFactoryClient() : binding_(this) {}
+  ~MockRendererAudioInputStreamFactoryClient() override {}
+
+  mojom::RendererAudioInputStreamFactoryClientPtr MakePtr() {
+    mojom::RendererAudioInputStreamFactoryClientPtr ret;
+    binding_.Bind(mojo::MakeRequest(&ret));
+    return ret;
+  }
+
+  MOCK_METHOD0(OnStreamCreated, void());
+
+  void StreamCreated(media::mojom::AudioInputStreamPtr input_stream,
+                     media::mojom::AudioInputStreamClientRequest client_request,
+                     media::mojom::AudioDataPipePtr data_pipe,
+                     bool initially_muted) override {
+    input_stream_ = std::move(input_stream);
+    client_request_ = std::move(client_request);
+    OnStreamCreated();
+  }
+
+  void CloseBinding() { binding_.Close(); }
+
+ private:
+  mojo::Binding<mojom::RendererAudioInputStreamFactoryClient> binding_;
+  media::mojom::AudioInputStreamPtr input_stream_;
+  media::mojom::AudioInputStreamClientRequest client_request_;
+};
+
+class MockStreamFactory : public audio::FakeStreamFactory {
+ public:
+  MockStreamFactory() {}
+  ~MockStreamFactory() final {}
+
+  // State of an expected stream creation. |device_id| and |params| are set
+  // ahead of time and verified during request. The other fields are filled in
+  // when the request is received.
+  struct StreamRequestData {
+    StreamRequestData(const base::UnguessableToken& group_id,
+                      const media::AudioParameters& params)
+        : params(params), group_id(group_id) {}
+
+    bool requested = false;
+    media::mojom::AudioInputStreamRequest stream_request;
+    media::mojom::AudioInputStreamClientPtr client;
+    media::mojom::AudioInputStreamObserverPtr observer;
+    const media::AudioParameters params;
+    uint32_t shared_memory_count;
+    base::UnguessableToken group_id;
+    mojo::ScopedSharedBufferHandle key_press_count_buffer;
+    CreateLoopbackStreamCallback created_callback;
+    audio::mojom::LocalMuterAssociatedRequest muter_request;
+  };
+
+  void ExpectStreamCreation(StreamRequestData* ex) {
+    stream_request_data_ = ex;
+  }
+
+  MOCK_METHOD1(IsMuting, void(const base::UnguessableToken&));
+
+ private:
+  void CreateLoopbackStream(
+      media::mojom::AudioInputStreamRequest stream_request,
+      media::mojom::AudioInputStreamClientPtr client,
+      media::mojom::AudioInputStreamObserverPtr observer,
+      const media::AudioParameters& params,
+      uint32_t shared_memory_count,
+      const base::UnguessableToken& group_id,
+      CreateLoopbackStreamCallback created_callback) final {
+    // No way to cleanly exit the test here in case of failure, so use CHECK.
+    CHECK(stream_request_data_);
+    EXPECT_EQ(stream_request_data_->group_id, group_id);
+    EXPECT_TRUE(stream_request_data_->params.Equals(params));
+    stream_request_data_->requested = true;
+    stream_request_data_->stream_request = std::move(stream_request);
+    stream_request_data_->client = std::move(client);
+    stream_request_data_->observer = std::move(observer);
+    stream_request_data_->shared_memory_count = shared_memory_count;
+    stream_request_data_->created_callback = std::move(created_callback);
+  }
+
+  void BindMuter(audio::mojom::LocalMuterAssociatedRequest request,
+                 const base::UnguessableToken& group_id) final {
+    stream_request_data_->muter_request = std::move(request);
+    IsMuting(group_id);
+  }
+
+  StreamRequestData* stream_request_data_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockStreamFactory);
+};
+
+const bool kMuteSource = true;
+
+struct TestEnvironment {
+  TestEnvironment(const base::UnguessableToken& group_id, bool mute_source) {
+    // Muting should not start until CreateStream() is called.
+    EXPECT_CALL(stream_factory, IsMuting(_)).Times(0);
+    auto mock_source = std::make_unique<NiceMock<MockSource>>(group_id);
+    source = mock_source.get();
+    broker = std::make_unique<AudioLoopbackStreamBroker>(
+        kRenderProcessId, kRenderFrameId, std::move(mock_source), TestParams(),
+        kShMemCount, mute_source, deleter.Get(),
+        renderer_factory_client.MakePtr());
+  }
+
+  void RunUntilIdle() { thread_bundle.RunUntilIdle(); }
+
+  TestBrowserThreadBundle thread_bundle;
+  MockDeleterCallback deleter;
+  MockSource* source;
+  StrictMock<MockRendererAudioInputStreamFactoryClient> renderer_factory_client;
+  std::unique_ptr<AudioLoopbackStreamBroker> broker;
+  MockStreamFactory stream_factory;
+  audio::mojom::StreamFactoryPtr factory_ptr = stream_factory.MakePtr();
+};
+
+}  // namespace
+
+TEST(AudioLoopbackStreamBrokerTest, StoresProcessAndFrameId) {
+  InSequence seq;
+  TestBrowserThreadBundle thread_bundle;
+  MockDeleterCallback deleter;
+  StrictMock<MockRendererAudioInputStreamFactoryClient> renderer_factory_client;
+  auto source = std::make_unique<StrictMock<MockSource>>(
+      base::UnguessableToken::Create());
+  MockSource* mock_source = source.get();
+
+  EXPECT_CALL(*mock_source, OnStartCapturing());
+
+  AudioLoopbackStreamBroker broker(kRenderProcessId, kRenderFrameId,
+                                   std::move(source), TestParams(), kShMemCount,
+                                   !kMuteSource, deleter.Get(),
+                                   renderer_factory_client.MakePtr());
+
+  EXPECT_EQ(kRenderProcessId, broker.render_process_id());
+  EXPECT_EQ(kRenderFrameId, broker.render_frame_id());
+
+  EXPECT_CALL(*mock_source, OnStopCapturing());
+}
+
+TEST(AudioLoopbackStreamBrokerTest, StreamCreationSuccess_Propagates) {
+  TestEnvironment env(base::UnguessableToken::Create(), !kMuteSource);
+  MockStreamFactory::StreamRequestData stream_request_data(
+      env.source->GetGroupID(), TestParams());
+  env.stream_factory.ExpectStreamCreation(&stream_request_data);
+
+  EXPECT_CALL(env.stream_factory, IsMuting(_)).Times(0);
+
+  env.broker->CreateStream(env.factory_ptr.get());
+  env.RunUntilIdle();
+
+  EXPECT_TRUE(stream_request_data.requested);
+
+  // Set up test IPC primitives.
+  const size_t shmem_size = 456;
+  base::SyncSocket socket1, socket2;
+  base::SyncSocket::CreatePair(&socket1, &socket2);
+  std::move(stream_request_data.created_callback)
+      .Run({base::in_place, mojo::SharedBufferHandle::Create(shmem_size),
+            mojo::WrapPlatformFile(socket1.Release())});
+
+  EXPECT_CALL(env.renderer_factory_client, OnStreamCreated());
+
+  env.RunUntilIdle();
+
+  Mock::VerifyAndClear(&env.renderer_factory_client);
+  env.broker.reset();
+}
+
+TEST(AudioLoopbackStreamBrokerTest, MutedStreamCreation_Mutes) {
+  TestEnvironment env(base::UnguessableToken::Create(), kMuteSource);
+  MockStreamFactory::StreamRequestData stream_request_data(
+      env.source->GetGroupID(), TestParams());
+  env.stream_factory.ExpectStreamCreation(&stream_request_data);
+
+  EXPECT_CALL(env.stream_factory, IsMuting(env.source->GetGroupID()));
+
+  env.broker->CreateStream(env.factory_ptr.get());
+  env.RunUntilIdle();
+
+  EXPECT_TRUE(stream_request_data.requested);
+
+  // Set up test IPC primitives.
+  const size_t shmem_size = 456;
+  base::SyncSocket socket1, socket2;
+  base::SyncSocket::CreatePair(&socket1, &socket2);
+  std::move(stream_request_data.created_callback)
+      .Run({base::in_place, mojo::SharedBufferHandle::Create(shmem_size),
+            mojo::WrapPlatformFile(socket1.Release())});
+
+  EXPECT_CALL(env.renderer_factory_client, OnStreamCreated());
+
+  env.RunUntilIdle();
+
+  Mock::VerifyAndClear(&env.renderer_factory_client);
+  env.broker.reset();
+}
+
+TEST(AudioLoopbackStreamBrokerTest, SourceGone_CallsDeleter) {
+  TestEnvironment env(base::UnguessableToken::Create(), kMuteSource);
+  MockStreamFactory::StreamRequestData stream_request_data(
+      env.source->GetGroupID(), TestParams());
+  env.stream_factory.ExpectStreamCreation(&stream_request_data);
+
+  EXPECT_CALL(env.stream_factory, IsMuting(env.source->GetGroupID()));
+
+  env.broker->CreateStream(env.factory_ptr.get());
+  env.RunUntilIdle();
+
+  EXPECT_TRUE(stream_request_data.requested);
+
+  // Set up test IPC primitives.
+  const size_t shmem_size = 456;
+  base::SyncSocket socket1, socket2;
+  base::SyncSocket::CreatePair(&socket1, &socket2);
+  std::move(stream_request_data.created_callback)
+      .Run({base::in_place, mojo::SharedBufferHandle::Create(shmem_size),
+            mojo::WrapPlatformFile(socket1.Release())});
+
+  EXPECT_CALL(env.renderer_factory_client, OnStreamCreated());
+
+  env.RunUntilIdle();
+
+  Mock::VerifyAndClear(&env.renderer_factory_client);
+
+  EXPECT_CALL(env.deleter, Run(env.broker.release()))
+      .WillOnce(testing::DeleteArg<0>());
+
+  env.source->WebContentsDestroyed();
+
+  env.RunUntilIdle();
+}
+
+TEST(AudioLoopbackStreamBrokerTest, StreamCreationFailure_CallsDeleter) {
+  TestEnvironment env(base::UnguessableToken::Create(), !kMuteSource);
+  MockStreamFactory::StreamRequestData stream_request_data(
+      env.source->GetGroupID(), TestParams());
+  env.stream_factory.ExpectStreamCreation(&stream_request_data);
+
+  EXPECT_CALL(env.stream_factory, IsMuting(_)).Times(0);
+
+  env.broker->CreateStream(env.factory_ptr.get());
+  env.RunUntilIdle();
+
+  EXPECT_TRUE(stream_request_data.requested);
+  EXPECT_CALL(env.deleter, Run(env.broker.release()))
+      .WillOnce(testing::DeleteArg<0>());
+
+  std::move(stream_request_data.created_callback).Run(nullptr);
+
+  env.RunUntilIdle();
+}
+
+TEST(AudioLoopbackStreamBrokerTest,
+     RendererFactoryClientDisconnect_CallsDeleter) {
+  TestEnvironment env(base::UnguessableToken::Create(), !kMuteSource);
+  MockStreamFactory::StreamRequestData stream_request_data(
+      env.source->GetGroupID(), TestParams());
+  env.stream_factory.ExpectStreamCreation(&stream_request_data);
+
+  EXPECT_CALL(env.stream_factory, IsMuting(_)).Times(0);
+
+  env.broker->CreateStream(env.factory_ptr.get());
+  env.RunUntilIdle();
+  EXPECT_TRUE(stream_request_data.requested);
+
+  EXPECT_CALL(env.deleter, Run(env.broker.release()))
+      .WillOnce(testing::DeleteArg<0>());
+  env.renderer_factory_client.CloseBinding();
+  env.RunUntilIdle();
+  Mock::VerifyAndClear(&env.deleter);
+
+  env.stream_factory.CloseBinding();
+  env.RunUntilIdle();
+}
+
+TEST(AudioLoopbackStreamBrokerTest, ObserverDisconnect_CallsDeleter) {
+  TestEnvironment env(base::UnguessableToken::Create(), !kMuteSource);
+  MockStreamFactory::StreamRequestData stream_request_data(
+      env.source->GetGroupID(), TestParams());
+  env.stream_factory.ExpectStreamCreation(&stream_request_data);
+
+  EXPECT_CALL(env.stream_factory, IsMuting(_)).Times(0);
+
+  env.broker->CreateStream(env.factory_ptr.get());
+  env.RunUntilIdle();
+  EXPECT_TRUE(stream_request_data.requested);
+
+  EXPECT_CALL(env.deleter, Run(env.broker.release()))
+      .WillOnce(testing::DeleteArg<0>());
+  stream_request_data.observer.reset();
+  env.RunUntilIdle();
+  Mock::VerifyAndClear(&env.deleter);
+
+  env.stream_factory.CloseBinding();
+  env.RunUntilIdle();
+}
+
+TEST(AudioLoopbackStreamBrokerTest,
+     FactoryDisconnectDuringConstruction_CallsDeleter) {
+  TestEnvironment env(base::UnguessableToken::Create(), !kMuteSource);
+  env.broker->CreateStream(env.factory_ptr.get());
+  env.stream_factory.CloseBinding();
+
+  EXPECT_CALL(env.deleter, Run(env.broker.release()))
+      .WillOnce(testing::DeleteArg<0>());
+
+  env.RunUntilIdle();
+}
+
+}  // namespace content
diff --git a/content/browser/media/audio_muting_session.cc b/content/browser/media/audio_muting_session.cc
new file mode 100644
index 0000000..6e8c13d3
--- /dev/null
+++ b/content/browser/media/audio_muting_session.cc
@@ -0,0 +1,22 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/media/audio_muting_session.h"
+
+namespace content {
+
+AudioMutingSession::AudioMutingSession(const base::UnguessableToken& group_id)
+    : group_id_(group_id) {}
+
+AudioMutingSession::~AudioMutingSession(){};
+
+void AudioMutingSession::Connect(audio::mojom::StreamFactory* factory) {
+  if (muter_)
+    muter_.reset();
+
+  DCHECK(factory);
+  factory->BindMuter(mojo::MakeRequest(&muter_), group_id_);
+}
+
+}  // namespace content
diff --git a/content/browser/media/audio_muting_session.h b/content/browser/media/audio_muting_session.h
new file mode 100644
index 0000000..a13c13c
--- /dev/null
+++ b/content/browser/media/audio_muting_session.h
@@ -0,0 +1,32 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_MEDIA_AUDIO_MUTING_SESSION_H_
+#define CONTENT_BROWSER_MEDIA_AUDIO_MUTING_SESSION_H_
+
+#include <utility>
+
+#include "base/unguessable_token.h"
+#include "content/common/content_export.h"
+#include "services/audio/public/mojom/stream_factory.mojom.h"
+
+namespace content {
+
+class CONTENT_EXPORT AudioMutingSession {
+ public:
+  explicit AudioMutingSession(const base::UnguessableToken& group_id);
+  ~AudioMutingSession();
+
+  void Connect(audio::mojom::StreamFactory* factory);
+
+ private:
+  const base::UnguessableToken group_id_;
+  audio::mojom::LocalMuterAssociatedPtr muter_;
+
+  DISALLOW_COPY_AND_ASSIGN(AudioMutingSession);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_MEDIA_AUDIO_MUTING_SESSION_H_
diff --git a/content/browser/media/audio_output_stream_broker_unittest.cc b/content/browser/media/audio_output_stream_broker_unittest.cc
index 2a634904..1c9213a 100644
--- a/content/browser/media/audio_output_stream_broker_unittest.cc
+++ b/content/browser/media/audio_output_stream_broker_unittest.cc
@@ -74,6 +74,7 @@
 
  private:
   mojo::Binding<media::mojom::AudioOutputStreamProviderClient> binding_;
+  DISALLOW_COPY_AND_ASSIGN(MockAudioOutputStreamProviderClient);
 };
 
 class MockStreamFactory : public audio::FakeStreamFactory {
@@ -128,6 +129,7 @@
   }
 
   StreamRequestData* stream_request_data_;
+  DISALLOW_COPY_AND_ASSIGN(MockStreamFactory);
 };
 
 // This struct collects test state we need without doing anything fancy.
diff --git a/content/browser/media/audio_stream_broker.cc b/content/browser/media/audio_stream_broker.cc
index abe535a..6ff668c 100644
--- a/content/browser/media/audio_stream_broker.cc
+++ b/content/browser/media/audio_stream_broker.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "content/browser/media/audio_input_stream_broker.h"
+#include "content/browser/media/audio_loopback_stream_broker.h"
 #include "content/browser/media/audio_output_stream_broker.h"
 
 namespace content {
@@ -34,6 +35,22 @@
         std::move(renderer_factory_client));
   }
 
+  std::unique_ptr<AudioStreamBroker> CreateAudioLoopbackStreamBroker(
+      int render_process_id,
+      int render_frame_id,
+      std::unique_ptr<LoopbackSource> source,
+      const media::AudioParameters& params,
+      uint32_t shared_memory_count,
+      bool mute_source,
+      AudioStreamBroker::DeleterCallback deleter,
+      mojom::RendererAudioInputStreamFactoryClientPtr renderer_factory_client)
+      final {
+    return std::make_unique<AudioLoopbackStreamBroker>(
+        render_process_id, render_frame_id, std::move(source), params,
+        shared_memory_count, mute_source, std::move(deleter),
+        std::move(renderer_factory_client));
+  }
+
   std::unique_ptr<AudioStreamBroker> CreateAudioOutputStreamBroker(
       int render_process_id,
       int render_frame_id,
diff --git a/content/browser/media/audio_stream_broker.h b/content/browser/media/audio_stream_broker.h
index 29e3079..40af0ea 100644
--- a/content/browser/media/audio_stream_broker.h
+++ b/content/browser/media/audio_stream_broker.h
@@ -12,6 +12,7 @@
 #include "base/macros.h"
 #include "content/common/content_export.h"
 #include "content/common/media/renderer_audio_input_stream_factory.mojom.h"
+#include "content/public/browser/web_contents_observer.h"
 #include "media/mojo/interfaces/audio_input_stream.mojom.h"
 #include "media/mojo/interfaces/audio_output_stream.mojom.h"
 
@@ -30,6 +31,7 @@
 }
 
 namespace content {
+class WebContents;
 
 // An AudioStreamBroker is used to broker a connection between a client
 // (typically renderer) and the audio service. It also sets up all objects
@@ -57,6 +59,38 @@
 // Used for dependency injection into ForwardingAudioStreamFactory.
 class CONTENT_EXPORT AudioStreamBrokerFactory {
  public:
+  class CONTENT_EXPORT LoopbackSource : public WebContentsObserver {
+   public:
+    explicit LoopbackSource(WebContents* source_contents);
+    ~LoopbackSource() override;
+
+    // Virtual for mocking in tests.
+    // Will return an empty token if the source is not present.
+
+    virtual base::UnguessableToken GetGroupID();
+
+    // Signals the source WebContents that capturing started.
+    virtual void OnStartCapturing();
+
+    // Signals the source WebContents that capturing stopped.
+    virtual void OnStopCapturing();
+
+    // Sets the closure to run when the source WebContents is gone.
+    void set_on_gone_closure(base::OnceClosure on_gone_closure) {
+      on_gone_closure_ = std::move(on_gone_closure);
+    }
+
+    // WebContentsObserver implementation.
+    void WebContentsDestroyed() override;
+
+   protected:
+    LoopbackSource();
+
+   private:
+    base::OnceClosure on_gone_closure_;
+    DISALLOW_COPY_AND_ASSIGN(LoopbackSource);
+  };
+
   static std::unique_ptr<AudioStreamBrokerFactory> CreateImpl();
 
   AudioStreamBrokerFactory();
@@ -73,6 +107,17 @@
       mojom::RendererAudioInputStreamFactoryClientPtr
           renderer_factory_client) = 0;
 
+  virtual std::unique_ptr<AudioStreamBroker> CreateAudioLoopbackStreamBroker(
+      int render_process_id,
+      int render_frame_id,
+      std::unique_ptr<LoopbackSource> source,
+      const media::AudioParameters& params,
+      uint32_t shared_memory_count,
+      bool mute_source,
+      AudioStreamBroker::DeleterCallback deleter,
+      mojom::RendererAudioInputStreamFactoryClientPtr
+          renderer_factory_client) = 0;
+
   virtual std::unique_ptr<AudioStreamBroker> CreateAudioOutputStreamBroker(
       int render_process_id,
       int render_frame_id,
diff --git a/content/browser/media/forwarding_audio_stream_factory.cc b/content/browser/media/forwarding_audio_stream_factory.cc
index ad71db9..63c426ab 100644
--- a/content/browser/media/forwarding_audio_stream_factory.cc
+++ b/content/browser/media/forwarding_audio_stream_factory.cc
@@ -79,6 +79,7 @@
 
   const int process_id = frame->GetProcess()->GetID();
   const int frame_id = frame->GetRoutingID();
+
   outputs_
       .insert(broker_factory_->CreateAudioOutputStreamBroker(
           process_id, frame_id, ++stream_id_counter_, device_id, params,
@@ -90,18 +91,55 @@
       ->CreateStream(GetFactory());
 }
 
+void ForwardingAudioStreamFactory::CreateLoopbackStream(
+    RenderFrameHost* frame,
+    WebContents* source_contents,
+    const media::AudioParameters& params,
+    uint32_t shared_memory_count,
+    bool mute_source,
+    mojom::RendererAudioInputStreamFactoryClientPtr renderer_factory_client) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  const int process_id = frame->GetProcess()->GetID();
+  const int frame_id = frame->GetRoutingID();
+  inputs_
+      .insert(broker_factory_->CreateAudioLoopbackStreamBroker(
+          process_id, frame_id,
+          std::make_unique<AudioStreamBrokerFactory::LoopbackSource>(
+              source_contents),
+          params, shared_memory_count, mute_source,
+          base::BindOnce(&ForwardingAudioStreamFactory::RemoveInput,
+                         base::Unretained(this)),
+          std::move(renderer_factory_client)))
+      .first->get()
+      ->CreateStream(GetFactory());
+}
+
+void ForwardingAudioStreamFactory::SetMuted(bool muted) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK_NE(muted, IsMuted());
+
+  if (!muted) {
+    muter_.reset();
+    return;
+  }
+
+  muter_.emplace(group_id_);
+  if (remote_factory_)
+    muter_->Connect(remote_factory_.get());
+}
+
+bool ForwardingAudioStreamFactory::IsMuted() const {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  return !!muter_;
+}
+
 void ForwardingAudioStreamFactory::FrameDeleted(
     RenderFrameHost* render_frame_host) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   CleanupStreamsBelongingTo(render_frame_host);
 }
 
-void ForwardingAudioStreamFactory::WebContentsDestroyed() {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  DCHECK(outputs_.empty());
-  DCHECK(inputs_.empty());
-}
-
 void ForwardingAudioStreamFactory::CleanupStreamsBelongingTo(
     RenderFrameHost* render_frame_host) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
@@ -145,6 +183,10 @@
     remote_factory_.set_connection_error_handler(
         base::BindOnce(&ForwardingAudioStreamFactory::ResetRemoteFactoryPtr,
                        base::Unretained(this)));
+
+    // Restore the muting session on reconnect.
+    if (muter_)
+      muter_->Connect(remote_factory_.get());
   }
 
   return remote_factory_.get();
@@ -161,6 +203,8 @@
   remote_factory_.reset();
   // The stream brokers will call a callback to be deleted soon, give them a
   // chance to signal an error to the client first.
+  inputs_.clear();
+  outputs_.clear();
 }
 
 }  // namespace content
diff --git a/content/browser/media/forwarding_audio_stream_factory.h b/content/browser/media/forwarding_audio_stream_factory.h
index 535a604e..12667079 100644
--- a/content/browser/media/forwarding_audio_stream_factory.h
+++ b/content/browser/media/forwarding_audio_stream_factory.h
@@ -11,7 +11,9 @@
 #include "base/containers/flat_set.h"
 #include "base/containers/unique_ptr_adapters.h"
 #include "base/macros.h"
+#include "base/optional.h"
 #include "base/unguessable_token.h"
+#include "content/browser/media/audio_muting_session.h"
 #include "content/browser/media/audio_stream_broker.h"
 #include "content/common/content_export.h"
 #include "content/common/media/renderer_audio_input_stream_factory.mojom.h"
@@ -30,6 +32,7 @@
 
 class AudioStreamBroker;
 class RenderFrameHost;
+class WebContents;
 
 // This class handles stream creation operations for a WebContents.
 // This class is operated on the UI thread.
@@ -50,7 +53,6 @@
 
   const base::UnguessableToken& group_id() { return group_id_; }
 
-  // TODO(https://crbug.com/803102): Add loopback and muting streams.
   // TODO(https://crbug.com/787806): Automatically restore streams on audio
   // service restart.
   void CreateInputStream(
@@ -67,10 +69,23 @@
       const media::AudioParameters& params,
       media::mojom::AudioOutputStreamProviderClientPtr client);
 
+  void CreateLoopbackStream(
+      RenderFrameHost* frame,
+      WebContents* source_contents,
+      const media::AudioParameters& params,
+      uint32_t shared_memory_count,
+      bool mute_source,
+      mojom::RendererAudioInputStreamFactoryClientPtr renderer_factory_client);
+
+  // Sets the muting state for all output streams created through this factory.
+  void SetMuted(bool muted);
+
+  // Returns the current muting state.
+  bool IsMuted() const;
+
   // WebContentsObserver implementation. We observe these events so that we can
   // clean up streams belonging to a frame when that frame is destroyed.
   void FrameDeleted(RenderFrameHost* render_frame_host) final;
-  void WebContentsDestroyed() final;
 
   // E.g. to override binder.
   service_manager::Connector* get_connector_for_testing() {
@@ -111,6 +126,9 @@
   // remove it.
   int stream_id_counter_ = 0;
 
+  // Instantiated when |outputs_| should be muted, empty otherwise.
+  base::Optional<AudioMutingSession> muter_;
+
   StreamBrokerSet inputs_;
   StreamBrokerSet outputs_;
 
diff --git a/content/browser/media/forwarding_audio_stream_factory_unittest.cc b/content/browser/media/forwarding_audio_stream_factory_unittest.cc
index b96e500..d3c1e688 100644
--- a/content/browser/media/forwarding_audio_stream_factory_unittest.cc
+++ b/content/browser/media/forwarding_audio_stream_factory_unittest.cc
@@ -14,11 +14,14 @@
 #include "base/unguessable_token.h"
 #include "content/common/media/renderer_audio_input_stream_factory.mojom.h"
 #include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
 #include "content/public/test/test_renderer_host.h"
 #include "media/base/audio_parameters.h"
 #include "media/mojo/interfaces/audio_output_stream.mojom.h"
+#include "mojo/public/cpp/bindings/associated_binding.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"
 #include "services/audio/public/mojom/constants.mojom.h"
 #include "services/audio/public/mojom/stream_factory.mojom.h"
 #include "services/service_manager/public/cpp/connector.h"
@@ -35,6 +38,30 @@
 
 namespace {
 
+class MockStreamFactory : public audio::FakeStreamFactory,
+                          public audio::mojom::LocalMuter {
+ public:
+  MockStreamFactory() : muter_binding_(this) {}
+  ~MockStreamFactory() final {}
+
+  bool IsConnected() {
+    return binding_ && !binding_.handle().QuerySignalsState().peer_closed();
+  }
+  bool IsMuterConnected() { return muter_binding_.is_bound(); }
+
+ private:
+  void BindMuter(audio::mojom::LocalMuterAssociatedRequest request,
+                 const base::UnguessableToken& group_id) final {
+    muter_binding_.Bind(std::move(request));
+    muter_binding_.set_connection_error_handler(base::BindOnce(
+        &MockStreamFactory::MuterUnbound, base::Unretained(this)));
+  }
+  void MuterUnbound() { muter_binding_.Close(); }
+
+  mojo::AssociatedBinding<audio::mojom::LocalMuter> muter_binding_;
+  DISALLOW_COPY_AND_ASSIGN(MockStreamFactory);
+};
+
 class MockBroker : public AudioStreamBroker {
  public:
   explicit MockBroker(RenderFrameHost* rfh)
@@ -52,6 +79,7 @@
 
  private:
   base::WeakPtrFactory<MockBroker> weak_factory_;
+  DISALLOW_COPY_AND_ASSIGN(MockBroker);
 };
 
 class MockBrokerFactory : public AudioStreamBrokerFactory {
@@ -72,6 +100,10 @@
     prepared_output_stream_brokers_.push(std::move(broker));
   }
 
+  void ExpectLoopbackStreamBrokerCreation(std::unique_ptr<MockBroker> broker) {
+    prepared_loopback_stream_brokers_.push(std::move(broker));
+  }
+
   std::unique_ptr<AudioStreamBroker> CreateAudioInputStreamBroker(
       int render_process_id,
       int render_frame_id,
@@ -111,9 +143,31 @@
     return std::move(prepared_broker);
   }
 
+  std::unique_ptr<AudioStreamBroker> CreateAudioLoopbackStreamBroker(
+      int render_process_id,
+      int render_frame_id,
+      std::unique_ptr<LoopbackSource> source,
+      const media::AudioParameters& params,
+      uint32_t shared_memory_count,
+      bool mute_source,
+      AudioStreamBroker::DeleterCallback deleter,
+      mojom::RendererAudioInputStreamFactoryClientPtr renderer_factory_client)
+      final {
+    std::unique_ptr<MockBroker> prepared_broker =
+        std::move(prepared_loopback_stream_brokers_.front());
+    prepared_loopback_stream_brokers_.pop();
+    CHECK_NE(nullptr, prepared_broker.get());
+    EXPECT_EQ(render_process_id, prepared_broker->render_process_id());
+    EXPECT_EQ(render_frame_id, prepared_broker->render_frame_id());
+    prepared_broker->deleter = std::move(deleter);
+    return std::move(prepared_broker);
+  }
+
  private:
+  base::queue<std::unique_ptr<MockBroker>> prepared_loopback_stream_brokers_;
   base::queue<std::unique_ptr<MockBroker>> prepared_input_stream_brokers_;
   base::queue<std::unique_ptr<MockBroker>> prepared_output_stream_brokers_;
+  DISALLOW_COPY_AND_ASSIGN(MockBrokerFactory);
 };
 
 class ForwardingAudioStreamFactoryTest : public RenderViewHostTestHarness {
@@ -125,9 +179,8 @@
     connector_test_api.OverrideBinderForTesting(
         service_manager::Identity(audio::mojom::kServiceName),
         audio::mojom::StreamFactory::Name_,
-        base::BindRepeating(
-            &ForwardingAudioStreamFactoryTest::SetPendingFactoryRequest,
-            base::Unretained(this)));
+        base::BindRepeating(&ForwardingAudioStreamFactoryTest::BindFactory,
+                            base::Unretained(this)));
   }
 
   ~ForwardingAudioStreamFactoryTest() override {}
@@ -139,8 +192,17 @@
         RenderFrameHostTester::For(main_rfh())->AppendChild("other_rfh");
   }
 
-  void SetPendingFactoryRequest(mojo::ScopedMessagePipeHandle factory_request) {
-    pending_factory_request_ = std::move(factory_request);
+  void BindFactory(mojo::ScopedMessagePipeHandle factory_request) {
+    stream_factory_.binding_.Bind(
+        audio::mojom::StreamFactoryRequest(std::move(factory_request)));
+  }
+
+  base::WeakPtr<MockBroker> ExpectLoopbackBrokerConstruction(
+      RenderFrameHost* rfh) {
+    auto broker = std::make_unique<StrictMock<MockBroker>>(rfh);
+    auto weak_broker = broker->GetWeakPtr();
+    broker_factory_->ExpectLoopbackStreamBrokerCreation(std::move(broker));
+    return weak_broker;
   }
 
   base::WeakPtr<MockBroker> ExpectInputBrokerConstruction(
@@ -166,9 +228,9 @@
   static const media::AudioParameters kParams;
   static const uint32_t kSharedMemoryCount;
   static const bool kEnableAgc;
+  MockStreamFactory stream_factory_;
   service_manager::mojom::ConnectorRequest connector_request_;
   std::unique_ptr<service_manager::Connector> connector_;
-  mojo::ScopedMessagePipeHandle pending_factory_request_;
   RenderFrameHost* other_rfh_;
   std::unique_ptr<MockBrokerFactory> broker_factory_;
 };
@@ -181,6 +243,7 @@
     media::AudioParameters::UnavailableDeviceParams();
 const uint32_t ForwardingAudioStreamFactoryTest::kSharedMemoryCount = 10;
 const bool ForwardingAudioStreamFactoryTest::kEnableAgc = false;
+const bool kMuteSource = true;
 
 }  // namespace
 
@@ -198,6 +261,23 @@
 }
 
 TEST_F(ForwardingAudioStreamFactoryTest,
+       CreateLoopbackStream_CreatesLoopbackStream) {
+  std::unique_ptr<WebContents> source_contents = CreateTestWebContents();
+  mojom::RendererAudioInputStreamFactoryClientPtr client;
+  base::WeakPtr<MockBroker> broker =
+      ExpectLoopbackBrokerConstruction(main_rfh());
+
+  ForwardingAudioStreamFactory factory(web_contents(), std::move(connector_),
+                                       std::move(broker_factory_));
+
+  EXPECT_CALL(*broker, CreateStream(NotNull()));
+  mojo::MakeRequest(&client);
+  factory.CreateLoopbackStream(main_rfh(), source_contents.get(), kParams,
+                               kSharedMemoryCount, kMuteSource,
+                               std::move(client));
+}
+
+TEST_F(ForwardingAudioStreamFactoryTest,
        CreateOutputStream_CreatesOutputStream) {
   media::mojom::AudioOutputStreamProviderClientPtr client;
   base::WeakPtr<MockBroker> broker = ExpectOutputBrokerConstruction(main_rfh());
@@ -246,6 +326,41 @@
 }
 
 TEST_F(ForwardingAudioStreamFactoryTest,
+       LoopbackBrokerDeleterCalled_DestroysInputStream) {
+  std::unique_ptr<WebContents> source_contents = CreateTestWebContents();
+  mojom::RendererAudioInputStreamFactoryClientPtr client;
+  base::WeakPtr<MockBroker> main_rfh_broker =
+      ExpectLoopbackBrokerConstruction(main_rfh());
+  base::WeakPtr<MockBroker> other_rfh_broker =
+      ExpectLoopbackBrokerConstruction(other_rfh());
+
+  ForwardingAudioStreamFactory factory(web_contents(), std::move(connector_),
+                                       std::move(broker_factory_));
+
+  {
+    EXPECT_CALL(*main_rfh_broker, CreateStream(NotNull()));
+    mojo::MakeRequest(&client);
+    factory.CreateLoopbackStream(main_rfh(), source_contents.get(), kParams,
+                                 kSharedMemoryCount, kMuteSource,
+                                 std::move(client));
+    testing::Mock::VerifyAndClear(&*main_rfh_broker);
+  }
+  {
+    EXPECT_CALL(*other_rfh_broker, CreateStream(NotNull()));
+    mojo::MakeRequest(&client);
+    factory.CreateLoopbackStream(other_rfh(), source_contents.get(), kParams,
+                                 kSharedMemoryCount, kMuteSource,
+                                 std::move(client));
+    testing::Mock::VerifyAndClear(&*other_rfh_broker);
+  }
+
+  std::move(other_rfh_broker->deleter).Run(&*other_rfh_broker);
+  EXPECT_FALSE(other_rfh_broker)
+      << "Loopback broker should be destructed when deleter is called.";
+  EXPECT_TRUE(main_rfh_broker);
+}
+
+TEST_F(ForwardingAudioStreamFactoryTest,
        OutputBrokerDeleterCalled_DestroysOutputStream) {
   media::mojom::AudioOutputStreamProviderClientPtr client;
   base::WeakPtr<MockBroker> main_rfh_broker =
@@ -278,12 +393,19 @@
 }
 
 TEST_F(ForwardingAudioStreamFactoryTest, DestroyFrame_DestroysRelatedStreams) {
+  std::unique_ptr<WebContents> source_contents = CreateTestWebContents();
+
   mojom::RendererAudioInputStreamFactoryClientPtr input_client;
   base::WeakPtr<MockBroker> main_rfh_input_broker =
       ExpectInputBrokerConstruction(main_rfh());
   base::WeakPtr<MockBroker> other_rfh_input_broker =
       ExpectInputBrokerConstruction(other_rfh());
 
+  base::WeakPtr<MockBroker> main_rfh_loopback_broker =
+      ExpectLoopbackBrokerConstruction(main_rfh());
+  base::WeakPtr<MockBroker> other_rfh_loopback_broker =
+      ExpectLoopbackBrokerConstruction(other_rfh());
+
   media::mojom::AudioOutputStreamProviderClientPtr output_client;
   base::WeakPtr<MockBroker> main_rfh_output_broker =
       ExpectOutputBrokerConstruction(main_rfh());
@@ -311,6 +433,23 @@
   }
 
   {
+    EXPECT_CALL(*main_rfh_loopback_broker, CreateStream(NotNull()));
+    mojo::MakeRequest(&input_client);
+    factory.CreateLoopbackStream(main_rfh(), source_contents.get(), kParams,
+                                 kSharedMemoryCount, kMuteSource,
+                                 std::move(input_client));
+    testing::Mock::VerifyAndClear(&*main_rfh_loopback_broker);
+  }
+  {
+    EXPECT_CALL(*other_rfh_loopback_broker, CreateStream(NotNull()));
+    mojo::MakeRequest(&input_client);
+    factory.CreateLoopbackStream(other_rfh(), source_contents.get(), kParams,
+                                 kSharedMemoryCount, kMuteSource,
+                                 std::move(input_client));
+    testing::Mock::VerifyAndClear(&*other_rfh_loopback_broker);
+  }
+
+  {
     EXPECT_CALL(*main_rfh_output_broker, CreateStream(NotNull()));
     mojo::MakeRequest(&output_client);
     factory.CreateOutputStream(main_rfh(), kOutputDeviceId, kParams,
@@ -330,6 +469,10 @@
   EXPECT_FALSE(other_rfh_input_broker)
       << "Input broker should be destructed when owning frame is destructed.";
   EXPECT_TRUE(main_rfh_input_broker);
+  EXPECT_FALSE(other_rfh_loopback_broker) << "Loopback broker should be "
+                                             "destructed when owning frame is "
+                                             "destructed.";
+  EXPECT_TRUE(main_rfh_loopback_broker);
   EXPECT_FALSE(other_rfh_output_broker)
       << "Output broker should be destructed when owning frame is destructed.";
   EXPECT_TRUE(main_rfh_output_broker);
@@ -416,23 +559,153 @@
   }
 
   base::RunLoop().RunUntilIdle();
-  EXPECT_FALSE(pending_factory_request_->QuerySignalsState().peer_closed());
+  EXPECT_TRUE(stream_factory_.IsConnected());
   std::move(other_rfh_input_broker->deleter).Run(&*other_rfh_input_broker);
   base::RunLoop().RunUntilIdle();
   // Connection should still be open, since there are still streams left.
-  EXPECT_FALSE(pending_factory_request_->QuerySignalsState().peer_closed());
+  EXPECT_TRUE(stream_factory_.IsConnected());
   std::move(main_rfh_input_broker->deleter).Run(&*main_rfh_input_broker);
   base::RunLoop().RunUntilIdle();
   // Connection should still be open, since there are still streams left.
-  EXPECT_FALSE(pending_factory_request_->QuerySignalsState().peer_closed());
+  EXPECT_TRUE(stream_factory_.IsConnected());
   std::move(other_rfh_output_broker->deleter).Run(&*other_rfh_output_broker);
   base::RunLoop().RunUntilIdle();
   // Connection should still be open, since there's still a stream left.
-  EXPECT_FALSE(pending_factory_request_->QuerySignalsState().peer_closed());
+  EXPECT_TRUE(stream_factory_.IsConnected());
   std::move(main_rfh_output_broker->deleter).Run(&*main_rfh_output_broker);
   base::RunLoop().RunUntilIdle();
   // Now there are no streams left, connection should be broken.
-  EXPECT_TRUE(pending_factory_request_->QuerySignalsState().peer_closed());
+  EXPECT_FALSE(stream_factory_.IsConnected());
+}
+
+TEST_F(ForwardingAudioStreamFactoryTest,
+       MuteNoOutputStreams_DoesNotConnectMuter) {
+  ForwardingAudioStreamFactory factory(web_contents(), std::move(connector_),
+                                       std::move(broker_factory_));
+  EXPECT_FALSE(factory.IsMuted());
+
+  factory.SetMuted(true);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(factory.IsMuted());
+  EXPECT_FALSE(stream_factory_.IsConnected());
+  EXPECT_FALSE(stream_factory_.IsMuterConnected());
+
+  factory.SetMuted(false);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(factory.IsMuted());
+  EXPECT_FALSE(stream_factory_.IsConnected());
+  EXPECT_FALSE(stream_factory_.IsMuterConnected());
+}
+
+TEST_F(ForwardingAudioStreamFactoryTest, MuteWithOutputStream_ConnectsMuter) {
+  media::mojom::AudioOutputStreamProviderClientPtr client;
+  base::WeakPtr<MockBroker> broker = ExpectOutputBrokerConstruction(main_rfh());
+  ForwardingAudioStreamFactory factory(web_contents(), std::move(connector_),
+                                       std::move(broker_factory_));
+
+  EXPECT_CALL(*broker, CreateStream(NotNull()));
+  mojo::MakeRequest(&client);
+  factory.CreateOutputStream(main_rfh(), kOutputDeviceId, kParams,
+                             std::move(client));
+  base::RunLoop().RunUntilIdle();
+  testing::Mock::VerifyAndClear(&*broker);
+
+  EXPECT_TRUE(stream_factory_.IsConnected());
+  EXPECT_FALSE(factory.IsMuted());
+
+  factory.SetMuted(true);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(factory.IsMuted());
+  EXPECT_TRUE(stream_factory_.IsConnected());
+  EXPECT_TRUE(stream_factory_.IsMuterConnected());
+
+  factory.SetMuted(false);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(factory.IsMuted());
+  EXPECT_TRUE(stream_factory_.IsConnected());
+  EXPECT_FALSE(stream_factory_.IsMuterConnected());
+}
+
+TEST_F(ForwardingAudioStreamFactoryTest,
+       WhenMuting_ConnectedWhenOutputStreamExists) {
+  media::mojom::AudioOutputStreamProviderClientPtr client;
+  base::WeakPtr<MockBroker> broker = ExpectOutputBrokerConstruction(main_rfh());
+  ForwardingAudioStreamFactory factory(web_contents(), std::move(connector_),
+                                       std::move(broker_factory_));
+
+  EXPECT_FALSE(stream_factory_.IsConnected());
+  EXPECT_FALSE(factory.IsMuted());
+
+  factory.SetMuted(true);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(factory.IsMuted());
+  EXPECT_FALSE(stream_factory_.IsConnected());
+  EXPECT_FALSE(stream_factory_.IsMuterConnected());
+
+  EXPECT_CALL(*broker, CreateStream(NotNull()));
+  mojo::MakeRequest(&client);
+  factory.CreateOutputStream(main_rfh(), kOutputDeviceId, kParams,
+                             std::move(client));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(factory.IsMuted());
+  EXPECT_TRUE(stream_factory_.IsConnected());
+  EXPECT_TRUE(stream_factory_.IsMuterConnected());
+  testing::Mock::VerifyAndClear(&*broker);
+
+  std::move(broker->deleter).Run(&*broker);
+  EXPECT_FALSE(broker);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(factory.IsMuted());
+  EXPECT_FALSE(stream_factory_.IsConnected());
+  EXPECT_FALSE(stream_factory_.IsMuterConnected());
+}
+
+TEST_F(ForwardingAudioStreamFactoryTest,
+       WhenMuting_AddRemoveSecondStream_DoesNotChangeMuting) {
+  media::mojom::AudioOutputStreamProviderClientPtr client;
+  base::WeakPtr<MockBroker> broker = ExpectOutputBrokerConstruction(main_rfh());
+  base::WeakPtr<MockBroker> another_broker =
+      ExpectOutputBrokerConstruction(main_rfh());
+  ForwardingAudioStreamFactory factory(web_contents(), std::move(connector_),
+                                       std::move(broker_factory_));
+
+  {
+    EXPECT_CALL(*broker, CreateStream(NotNull()));
+    mojo::MakeRequest(&client);
+    factory.CreateOutputStream(main_rfh(), kOutputDeviceId, kParams,
+                               std::move(client));
+    base::RunLoop().RunUntilIdle();
+    testing::Mock::VerifyAndClear(&*broker);
+  }
+  EXPECT_TRUE(stream_factory_.IsConnected());
+  EXPECT_FALSE(factory.IsMuted());
+
+  factory.SetMuted(true);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(factory.IsMuted());
+  EXPECT_TRUE(stream_factory_.IsConnected());
+  EXPECT_TRUE(stream_factory_.IsMuterConnected());
+
+  {
+    EXPECT_CALL(*another_broker, CreateStream(NotNull()));
+    mojo::MakeRequest(&client);
+    factory.CreateOutputStream(main_rfh(), kOutputDeviceId, kParams,
+                               std::move(client));
+    base::RunLoop().RunUntilIdle();
+    testing::Mock::VerifyAndClear(&*another_broker);
+  }
+
+  EXPECT_TRUE(factory.IsMuted());
+  EXPECT_TRUE(stream_factory_.IsConnected());
+  EXPECT_TRUE(stream_factory_.IsMuterConnected());
+
+  std::move(another_broker->deleter).Run(&*another_broker);
+  EXPECT_FALSE(another_broker);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(factory.IsMuted());
+  EXPECT_TRUE(stream_factory_.IsConnected());
+  EXPECT_TRUE(stream_factory_.IsMuterConnected());
 }
 
 }  // namespace content
diff --git a/content/browser/renderer_interface_binders.cc b/content/browser/renderer_interface_binders.cc
index 74a060e..e306536 100644
--- a/content/browser/renderer_interface_binders.cc
+++ b/content/browser/renderer_interface_binders.cc
@@ -8,6 +8,7 @@
 
 #include "base/bind.h"
 #include "content/browser/background_fetch/background_fetch_service_impl.h"
+#include "content/browser/cookie_store/cookie_store_context.h"
 #include "content/browser/locks/lock_manager.h"
 #include "content/browser/notifications/platform_notification_context_impl.h"
 #include "content/browser/payments/payment_manager.h"
@@ -31,6 +32,7 @@
 #include "services/shape_detection/public/mojom/constants.mojom.h"
 #include "services/shape_detection/public/mojom/facedetection_provider.mojom.h"
 #include "services/shape_detection/public/mojom/textdetection.mojom.h"
+#include "third_party/blink/public/mojom/cookie_store/cookie_store.mojom.h"
 #include "third_party/blink/public/platform/modules/cache_storage/cache_storage.mojom.h"
 #include "third_party/blink/public/platform/modules/notifications/notification_service.mojom.h"
 #include "url/origin.h"
@@ -180,6 +182,13 @@
       base::BindRepeating(GetRestrictedCookieManagerForWorker));
   parameterized_binder_registry_.AddInterface(
       base::BindRepeating(&QuotaDispatcherHost::CreateForWorker));
+  parameterized_binder_registry_.AddInterface(base::BindRepeating(
+      [](blink::mojom::CookieStoreRequest request, RenderProcessHost* host,
+         const url::Origin& origin) {
+        static_cast<StoragePartitionImpl*>(host->GetStoragePartition())
+            ->GetCookieStoreContext()
+            ->CreateService(std::move(request), origin);
+      }));
 }
 
 RendererInterfaceBinders& GetRendererInterfaceBinders() {
diff --git a/content/browser/service_worker/embedded_worker_test_helper.cc b/content/browser/service_worker/embedded_worker_test_helper.cc
index 4ff9fda..3b456e68 100644
--- a/content/browser/service_worker/embedded_worker_test_helper.cc
+++ b/content/browser/service_worker/embedded_worker_test_helper.cc
@@ -239,6 +239,15 @@
                                           std::move(callback));
   }
 
+  void DispatchCookieChangeEvent(
+      const net::CanonicalCookie& cookie,
+      ::network::mojom::CookieChangeCause cause,
+      DispatchCookieChangeEventCallback callback) override {
+    if (!helper_)
+      return;
+    helper_->OnCookieChangeEventStub(cookie, cause, std::move(callback));
+  }
+
   void DispatchFetchEvent(
       mojom::DispatchFetchEventParamsPtr params,
       mojom::ServiceWorkerFetchResponseCallbackPtr response_callback,
@@ -611,6 +620,15 @@
                           base::Time::Now());
 }
 
+void EmbeddedWorkerTestHelper::OnCookieChangeEvent(
+    const net::CanonicalCookie& cookie,
+    ::network::mojom::CookieChangeCause cause,
+    mojom::ServiceWorkerEventDispatcher::DispatchCookieChangeEventCallback
+        callback) {
+  std::move(callback).Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED,
+                          base::Time::Now());
+}
+
 void EmbeddedWorkerTestHelper::OnExtendableMessageEvent(
     mojom::ExtendableMessageEventPtr event,
     mojom::ServiceWorkerEventDispatcher::DispatchExtendableMessageEventCallback
@@ -911,6 +929,17 @@
                      std::move(callback)));
 }
 
+void EmbeddedWorkerTestHelper::OnCookieChangeEventStub(
+    const net::CanonicalCookie& cookie,
+    ::network::mojom::CookieChangeCause cause,
+    mojom::ServiceWorkerEventDispatcher::DispatchCookieChangeEventCallback
+        callback) {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&EmbeddedWorkerTestHelper::OnCookieChangeEvent,
+                     AsWeakPtr(), cookie, cause, std::move(callback)));
+}
+
 void EmbeddedWorkerTestHelper::OnExtendableMessageEventStub(
     mojom::ExtendableMessageEventPtr event,
     mojom::ServiceWorkerEventDispatcher::DispatchExtendableMessageEventCallback
diff --git a/content/browser/service_worker/embedded_worker_test_helper.h b/content/browser/service_worker/embedded_worker_test_helper.h
index 4501893..609a991 100644
--- a/content/browser/service_worker/embedded_worker_test_helper.h
+++ b/content/browser/service_worker/embedded_worker_test_helper.h
@@ -25,7 +25,9 @@
 #include "ipc/ipc_listener.h"
 #include "ipc/ipc_test_sink.h"
 #include "mojo/public/cpp/bindings/binding.h"
+#include "net/cookies/cookie_change_dispatcher.h"
 #include "net/http/http_response_info.h"
+#include "services/network/public/mojom/cookie_manager.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_installed_scripts_manager.mojom.h"
 #include "url/gurl.h"
@@ -218,6 +220,11 @@
       const std::vector<BackgroundFetchSettledFetch>& fetches,
       mojom::ServiceWorkerEventDispatcher::
           DispatchBackgroundFetchedEventCallback callback);
+  virtual void OnCookieChangeEvent(
+      const net::CanonicalCookie& cookie,
+      ::network::mojom::CookieChangeCause cause,
+      mojom::ServiceWorkerEventDispatcher::DispatchCookieChangeEventCallback
+          callback);
   virtual void OnExtendableMessageEvent(
       mojom::ExtendableMessageEventPtr event,
       mojom::ServiceWorkerEventDispatcher::
@@ -319,6 +326,11 @@
       const std::vector<BackgroundFetchSettledFetch>& fetches,
       mojom::ServiceWorkerEventDispatcher::
           DispatchBackgroundFetchedEventCallback callback);
+  void OnCookieChangeEventStub(
+      const net::CanonicalCookie& cookie,
+      ::network::mojom::CookieChangeCause cause,
+      mojom::ServiceWorkerEventDispatcher::DispatchCookieChangeEventCallback
+          callback);
   void OnExtendableMessageEventStub(
       mojom::ExtendableMessageEventPtr event,
       mojom::ServiceWorkerEventDispatcher::
diff --git a/content/browser/service_worker/service_worker_metrics.cc b/content/browser/service_worker/service_worker_metrics.cc
index 463d9e7..f5fc5073 100644
--- a/content/browser/service_worker/service_worker_metrics.cc
+++ b/content/browser/service_worker/service_worker_metrics.cc
@@ -94,6 +94,8 @@
       return "_CAN_MAKE_PAYMENT";
     case ServiceWorkerMetrics::EventType::ABORT_PAYMENT:
       return "_ABORT_PAYMENT";
+    case ServiceWorkerMetrics::EventType::COOKIE_CHANGE:
+      return "_COOKIE_CHANGE";
     case ServiceWorkerMetrics::EventType::NUM_TYPES:
       NOTREACHED() << static_cast<int>(event_type);
   }
@@ -339,8 +341,10 @@
       return "Navigation Hint";
     case EventType::CAN_MAKE_PAYMENT:
       return "Can Make Payment";
-    case ServiceWorkerMetrics::EventType::ABORT_PAYMENT:
+    case EventType::ABORT_PAYMENT:
       return "Abort Payment";
+    case EventType::COOKIE_CHANGE:
+      return "Cookie Change";
     case EventType::NUM_TYPES:
       break;
   }
@@ -717,6 +721,9 @@
     case EventType::ABORT_PAYMENT:
       UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.AbortPaymentEvent.Time", time);
       break;
+    case EventType::COOKIE_CHANGE:
+      UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.CookieChangeEvent.Time", time);
+      break;
 
     case EventType::NAVIGATION_HINT:
     // The navigation hint should not be sent as an event.
diff --git a/content/browser/service_worker/service_worker_metrics.h b/content/browser/service_worker/service_worker_metrics.h
index e0a5ce15..5c3c512 100644
--- a/content/browser/service_worker/service_worker_metrics.h
+++ b/content/browser/service_worker/service_worker_metrics.h
@@ -128,6 +128,7 @@
     NAVIGATION_HINT = 27,
     CAN_MAKE_PAYMENT = 28,
     ABORT_PAYMENT = 29,
+    COOKIE_CHANGE = 30,
     // Add new events to record here.
     NUM_TYPES
   };
diff --git a/content/browser/service_worker/service_worker_new_script_loader.cc b/content/browser/service_worker/service_worker_new_script_loader.cc
index 5a64bf4f..52b788a5 100644
--- a/content/browser/service_worker/service_worker_new_script_loader.cc
+++ b/content/browser/service_worker/service_worker_new_script_loader.cc
@@ -35,8 +35,8 @@
     const network::ResourceRequest& original_request,
     network::mojom::URLLoaderClientPtr client,
     scoped_refptr<ServiceWorkerVersion> version,
-    scoped_refptr<URLLoaderFactoryGetter> loader_factory_getter,
-    network::mojom::URLLoaderFactoryPtr non_network_loader_factory,
+    scoped_refptr<network::SharedURLLoaderFactory> network_factory,
+    network::mojom::URLLoaderFactoryPtr non_network_factory,
     const net::MutableNetworkTrafficAnnotationTag& traffic_annotation)
     : request_url_(original_request.url),
       resource_type_(static_cast<ResourceType>(original_request.resource_type)),
@@ -46,7 +46,8 @@
       network_watcher_(FROM_HERE,
                        mojo::SimpleWatcher::ArmingPolicy::MANUAL,
                        base::SequencedTaskRunnerHandle::Get()),
-      non_network_loader_factory_(std::move(non_network_loader_factory)),
+      network_factory_(std::move(network_factory)),
+      non_network_factory_(std::move(non_network_factory)),
       client_(std::move(client)),
       weak_factory_(this) {
   // ServiceWorkerNewScriptLoader is used for fetching the service worker main
@@ -113,13 +114,13 @@
 
   network::mojom::URLLoaderClientPtr network_client;
   network_client_binding_.Bind(mojo::MakeRequest(&network_client));
-  if (non_network_loader_factory_) {
-    non_network_loader_factory_->CreateLoaderAndStart(
+  if (non_network_factory_) {
+    non_network_factory_->CreateLoaderAndStart(
         mojo::MakeRequest(&network_loader_), routing_id, request_id, options,
         *resource_request_.get(), std::move(network_client),
         traffic_annotation);
   } else {
-    loader_factory_getter->GetNetworkFactory()->CreateLoaderAndStart(
+    network_factory_->CreateLoaderAndStart(
         mojo::MakeRequest(&network_loader_), routing_id, request_id, options,
         *resource_request_.get(), std::move(network_client),
         traffic_annotation);
diff --git a/content/browser/service_worker/service_worker_new_script_loader.h b/content/browser/service_worker/service_worker_new_script_loader.h
index b14c8d92..328091cb 100644
--- a/content/browser/service_worker/service_worker_new_script_loader.h
+++ b/content/browser/service_worker/service_worker_new_script_loader.h
@@ -19,7 +19,6 @@
 
 class ServiceWorkerCacheWriter;
 class ServiceWorkerVersion;
-class URLLoaderFactoryGetter;
 struct HttpResponseInfoIOBuffer;
 
 // S13nServiceWorker:
@@ -45,7 +44,7 @@
 // worker. If the script is identical, the load succeeds but no script is
 // written, and ServiceWorkerVersion is told to terminate startup.
 //
-// NOTE: To load a script, this class uses |non_network_loader_factory| when the
+// NOTE: To load a script, this class uses |non_network_factory| when the
 // URL has a non-http(s) scheme, e.g., a chrome-extension:// URL. Regardless,
 // that is still called a "network" request in comments and naming. "network" is
 // meant to distinguish from the load this URLLoader does for its client:
@@ -55,10 +54,9 @@
     : public network::mojom::URLLoader,
       public network::mojom::URLLoaderClient {
  public:
-  // |loader_factory_getter| is used to get the network factory when the script
-  // URL has an http(s) scheme.
+  // |network_factory| is used when the script URL has an http(s) scheme.
   //
-  // |non_network_loader_factory| is non-null when the script URL has a
+  // |non_network_factory| is non-null when the script URL has a
   // non-http(s) scheme (e.g., a chrome-extension:// URL). It is used in that
   // case since the network factory can't be used.
   ServiceWorkerNewScriptLoader(
@@ -68,8 +66,8 @@
       const network::ResourceRequest& original_request,
       network::mojom::URLLoaderClientPtr client,
       scoped_refptr<ServiceWorkerVersion> version,
-      scoped_refptr<URLLoaderFactoryGetter> loader_factory_getter,
-      network::mojom::URLLoaderFactoryPtr non_network_loader_factory,
+      scoped_refptr<network::SharedURLLoaderFactory> network_factory,
+      network::mojom::URLLoaderFactoryPtr non_network_factory,
       const net::MutableNetworkTrafficAnnotationTag& traffic_annotation);
   ~ServiceWorkerNewScriptLoader() override;
 
@@ -154,10 +152,11 @@
   mojo::ScopedDataPipeConsumerHandle network_consumer_;
   mojo::SimpleWatcher network_watcher_;
   bool network_load_completed_ = false;
-  // |non_network_loader_factory_| is non-null when the script URL is
+  scoped_refptr<network::SharedURLLoaderFactory> network_factory_;
+  // |non_network_factory_| is non-null when the script URL is
   // non-http(s). It is used to make the "network" request because the usual
   // network factory can't be used in that case. See class comments.
-  network::mojom::URLLoaderFactoryPtr non_network_loader_factory_;
+  network::mojom::URLLoaderFactoryPtr non_network_factory_;
 
   // Used for responding with the fetched script to this loader's client.
   network::mojom::URLLoaderClientPtr client_;
diff --git a/content/browser/service_worker/service_worker_new_script_loader_unittest.cc b/content/browser/service_worker/service_worker_new_script_loader_unittest.cc
index 52007754..fe1216a7 100644
--- a/content/browser/service_worker/service_worker_new_script_loader_unittest.cc
+++ b/content/browser/service_worker/service_worker_new_script_loader_unittest.cc
@@ -227,7 +227,7 @@
     client_ = std::make_unique<network::TestURLLoaderClient>();
     loader_ = std::make_unique<ServiceWorkerNewScriptLoader>(
         routing_id, request_id, options, request, client_->CreateInterfacePtr(),
-        version_, helper_->url_loader_factory_getter(),
+        version_, helper_->url_loader_factory_getter()->GetNetworkFactory(),
         nullptr /* non_network_loader_factory */,
         net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
   }
diff --git a/content/browser/service_worker/service_worker_provider_host.cc b/content/browser/service_worker/service_worker_provider_host.cc
index 2a22bfc..955c9043 100644
--- a/content/browser/service_worker/service_worker_provider_host.cc
+++ b/content/browser/service_worker/service_worker_provider_host.cc
@@ -688,7 +688,8 @@
   if (ServiceWorkerUtils::IsServicificationEnabled()) {
     mojo::MakeStrongAssociatedBinding(
         std::make_unique<ServiceWorkerScriptLoaderFactory>(
-            context_, AsWeakPtr(), context_->loader_factory_getter(),
+            context_, AsWeakPtr(),
+            context_->loader_factory_getter()->GetNetworkFactory(),
             std::move(non_network_loader_factory)),
         mojo::MakeRequest(&script_loader_factory_ptr_info));
     provider_info->script_loader_factory_ptr_info =
diff --git a/content/browser/service_worker/service_worker_script_loader_factory.cc b/content/browser/service_worker/service_worker_script_loader_factory.cc
index 6d779103..5a282d4 100644
--- a/content/browser/service_worker/service_worker_script_loader_factory.cc
+++ b/content/browser/service_worker/service_worker_script_loader_factory.cc
@@ -10,22 +10,22 @@
 #include "content/browser/service_worker/service_worker_new_script_loader.h"
 #include "content/browser/service_worker/service_worker_provider_host.h"
 #include "content/browser/service_worker/service_worker_version.h"
-#include "content/browser/url_loader_factory_getter.h"
 #include "content/common/service_worker/service_worker_utils.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "services/network/public/cpp/resource_response.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
 
 namespace content {
 
 ServiceWorkerScriptLoaderFactory::ServiceWorkerScriptLoaderFactory(
     base::WeakPtr<ServiceWorkerContextCore> context,
     base::WeakPtr<ServiceWorkerProviderHost> provider_host,
-    scoped_refptr<URLLoaderFactoryGetter> loader_factory_getter,
-    network::mojom::URLLoaderFactoryPtr non_network_loader_factory)
+    scoped_refptr<network::SharedURLLoaderFactory> network_factory,
+    network::mojom::URLLoaderFactoryPtr non_network_factory)
     : context_(context),
       provider_host_(provider_host),
-      loader_factory_getter_(loader_factory_getter),
-      non_network_loader_factory_(std::move(non_network_loader_factory)) {
+      network_factory_(std::move(network_factory)),
+      non_network_factory_(std::move(non_network_factory)) {
   DCHECK(provider_host_->IsProviderForServiceWorker());
 }
 
@@ -40,20 +40,19 @@
     network::mojom::URLLoaderClientPtr client,
     const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
   DCHECK(ServiceWorkerUtils::IsServicificationEnabled());
-  DCHECK(loader_factory_getter_);
+  DCHECK(network_factory_);
   if (!ShouldHandleScriptRequest(resource_request)) {
     // If the request should not be handled (e.g., a fetch() request), just do a
     // passthrough load. This needs a relaying as we use different associated
     // message pipes.
     // TODO(kinuko): Record the reason like what we do with netlog in
     // ServiceWorkerContextRequestHandler.
-    if (!resource_request.url.SchemeIsHTTPOrHTTPS() &&
-        non_network_loader_factory_) {
-      non_network_loader_factory_->CreateLoaderAndStart(
+    if (!resource_request.url.SchemeIsHTTPOrHTTPS() && non_network_factory_) {
+      non_network_factory_->CreateLoaderAndStart(
           std::move(request), routing_id, request_id, options, resource_request,
           std::move(client), traffic_annotation);
     } else {
-      loader_factory_getter_->GetNetworkFactory()->CreateLoaderAndStart(
+      network_factory_->CreateLoaderAndStart(
           std::move(request), routing_id, request_id, options, resource_request,
           std::move(client), traffic_annotation);
     }
@@ -81,17 +80,14 @@
   }
 
   // The common case: load the script and install it.
-  network::mojom::URLLoaderFactoryPtr cloned_non_network_loader_factory;
-  if (!resource_request.url.SchemeIsHTTPOrHTTPS() &&
-      non_network_loader_factory_) {
-    non_network_loader_factory_->Clone(
-        mojo::MakeRequest(&cloned_non_network_loader_factory));
-  }
+  network::mojom::URLLoaderFactoryPtr cloned_non_network_factory;
+  if (!resource_request.url.SchemeIsHTTPOrHTTPS() && non_network_factory_)
+    non_network_factory_->Clone(mojo::MakeRequest(&cloned_non_network_factory));
   mojo::MakeStrongBinding(
       std::make_unique<ServiceWorkerNewScriptLoader>(
           routing_id, request_id, options, resource_request, std::move(client),
-          provider_host_->running_hosted_version(), loader_factory_getter_,
-          std::move(cloned_non_network_loader_factory), traffic_annotation),
+          provider_host_->running_hosted_version(), network_factory_,
+          std::move(cloned_non_network_factory), traffic_annotation),
       std::move(request));
 }
 
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 b2f09ca8..069eb76 100644
--- a/content/browser/service_worker/service_worker_script_loader_factory.h
+++ b/content/browser/service_worker/service_worker_script_loader_factory.h
@@ -8,11 +8,14 @@
 #include "base/macros.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
 
+namespace network {
+class SharedURLLoaderFactory;
+}  // namespace network
+
 namespace content {
 
 class ServiceWorkerContextCore;
 class ServiceWorkerProviderHost;
-class URLLoaderFactoryGetter;
 
 // S13nServiceWorker:
 // Created per one running service worker for loading its scripts. This is kept
@@ -29,10 +32,10 @@
 class ServiceWorkerScriptLoaderFactory
     : public network::mojom::URLLoaderFactory {
  public:
-  // |loader_factory_getter| is used to get the network factory for
-  // loading the script.
+  // |network_factory| is used to load scripts when the service worker main
+  // script has an http(s) scheme.
   //
-  // |non_network_loader_factory| is non-null when the service worker main
+  // |non_network_factory| is non-null when the service worker main
   // script URL has a non-http(s) scheme (e.g., a chrome-extension:// URL).
   // It will be used to load the main script and any non-http(s) imported
   // script.
@@ -48,8 +51,8 @@
   ServiceWorkerScriptLoaderFactory(
       base::WeakPtr<ServiceWorkerContextCore> context,
       base::WeakPtr<ServiceWorkerProviderHost> provider_host,
-      scoped_refptr<URLLoaderFactoryGetter> loader_factory_getter,
-      network::mojom::URLLoaderFactoryPtr non_network_loader_factory);
+      scoped_refptr<network::SharedURLLoaderFactory> network_factory,
+      network::mojom::URLLoaderFactoryPtr non_network_factory);
   ~ServiceWorkerScriptLoaderFactory() override;
 
   // network::mojom::URLLoaderFactory:
@@ -69,8 +72,8 @@
 
   base::WeakPtr<ServiceWorkerContextCore> context_;
   base::WeakPtr<ServiceWorkerProviderHost> provider_host_;
-  scoped_refptr<URLLoaderFactoryGetter> loader_factory_getter_;
-  network::mojom::URLLoaderFactoryPtr non_network_loader_factory_;
+  scoped_refptr<network::SharedURLLoaderFactory> network_factory_;
+  network::mojom::URLLoaderFactoryPtr non_network_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerScriptLoaderFactory);
 };
diff --git a/content/browser/shared_worker/shared_worker_script_loader.cc b/content/browser/shared_worker/shared_worker_script_loader.cc
index ff39e06..c964947 100644
--- a/content/browser/shared_worker/shared_worker_script_loader.cc
+++ b/content/browser/shared_worker/shared_worker_script_loader.cc
@@ -6,10 +6,10 @@
 
 #include "content/browser/loader/navigation_loader_interceptor.h"
 #include "content/browser/service_worker/service_worker_provider_host.h"
-#include "content/browser/url_loader_factory_getter.h"
 #include "content/common/service_worker/service_worker_utils.h"
 #include "content/public/browser/resource_context.h"
 #include "net/url_request/redirect_util.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
 
 namespace content {
 
@@ -21,7 +21,7 @@
     network::mojom::URLLoaderClientPtr client,
     base::WeakPtr<ServiceWorkerProviderHost> service_worker_provider_host,
     ResourceContext* resource_context,
-    scoped_refptr<URLLoaderFactoryGetter> loader_factory_getter,
+    scoped_refptr<network::SharedURLLoaderFactory> network_factory,
     const net::MutableNetworkTrafficAnnotationTag& traffic_annotation)
     : routing_id_(routing_id),
       request_id_(request_id),
@@ -30,7 +30,7 @@
       client_(std::move(client)),
       service_worker_provider_host_(service_worker_provider_host),
       resource_context_(resource_context),
-      loader_factory_getter_(std::move(loader_factory_getter)),
+      network_factory_(std::move(network_factory)),
       traffic_annotation_(traffic_annotation),
       url_loader_client_binding_(this),
       weak_factory_(this) {
@@ -84,7 +84,7 @@
 void SharedWorkerScriptLoader::LoadFromNetwork() {
   network::mojom::URLLoaderClientPtr client;
   url_loader_client_binding_.Bind(mojo::MakeRequest(&client));
-  url_loader_factory_ = loader_factory_getter_->GetNetworkFactory();
+  url_loader_factory_ = network_factory_;
   url_loader_factory_->CreateLoaderAndStart(
       mojo::MakeRequest(&url_loader_), routing_id_, request_id_, options_,
       resource_request_, std::move(client), traffic_annotation_);
diff --git a/content/browser/shared_worker/shared_worker_script_loader.h b/content/browser/shared_worker/shared_worker_script_loader.h
index f1d55c74..38a29cd8 100644
--- a/content/browser/shared_worker/shared_worker_script_loader.h
+++ b/content/browser/shared_worker/shared_worker_script_loader.h
@@ -12,11 +12,14 @@
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/mojom/url_loader.mojom.h"
 
+namespace network {
+class SharedURLLoaderFactory;
+}  // namespace network
+
 namespace content {
 class NavigationLoaderInterceptor;
 class ResourceContext;
 class ServiceWorkerProviderHost;
-class URLLoaderFactoryGetter;
 
 // The URLLoader for loading a shared worker script. Only used for the main
 // script request.
@@ -40,7 +43,7 @@
       network::mojom::URLLoaderClientPtr client,
       base::WeakPtr<ServiceWorkerProviderHost> service_worker_provider_host,
       ResourceContext* resource_context,
-      scoped_refptr<URLLoaderFactoryGetter> loader_factory_getter,
+      scoped_refptr<network::SharedURLLoaderFactory> network_factory,
       const net::MutableNetworkTrafficAnnotationTag& traffic_annotation);
   ~SharedWorkerScriptLoader() override;
 
@@ -86,7 +89,7 @@
   network::mojom::URLLoaderClientPtr client_;
   base::WeakPtr<ServiceWorkerProviderHost> service_worker_provider_host_;
   ResourceContext* resource_context_;
-  scoped_refptr<URLLoaderFactoryGetter> loader_factory_getter_;
+  scoped_refptr<network::SharedURLLoaderFactory> network_factory_;
   net::MutableNetworkTrafficAnnotationTag traffic_annotation_;
 
   base::Optional<net::RedirectInfo> redirect_info_;
diff --git a/content/browser/shared_worker/shared_worker_script_loader_factory.cc b/content/browser/shared_worker/shared_worker_script_loader_factory.cc
index f819351..6329bcd 100644
--- a/content/browser/shared_worker/shared_worker_script_loader_factory.cc
+++ b/content/browser/shared_worker/shared_worker_script_loader_factory.cc
@@ -10,10 +10,10 @@
 #include "content/browser/service_worker/service_worker_provider_host.h"
 #include "content/browser/service_worker/service_worker_version.h"
 #include "content/browser/shared_worker/shared_worker_script_loader.h"
-#include "content/browser/url_loader_factory_getter.h"
 #include "content/common/service_worker/service_worker_utils.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "services/network/public/cpp/resource_response.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_provider_type.mojom.h"
 
 namespace content {
@@ -22,10 +22,10 @@
     ServiceWorkerContextWrapper* context,
     base::WeakPtr<ServiceWorkerProviderHost> service_worker_provider_host,
     ResourceContext* resource_context,
-    scoped_refptr<URLLoaderFactoryGetter> loader_factory_getter)
+    scoped_refptr<network::SharedURLLoaderFactory> network_factory)
     : service_worker_provider_host_(service_worker_provider_host),
       resource_context_(resource_context),
-      loader_factory_getter_(loader_factory_getter) {
+      network_factory_(std::move(network_factory)) {
   DCHECK(ServiceWorkerUtils::IsServicificationEnabled());
   DCHECK_EQ(service_worker_provider_host_->provider_type(),
             blink::mojom::ServiceWorkerProviderType::kForSharedWorker);
@@ -54,8 +54,8 @@
   mojo::MakeStrongBinding(
       std::make_unique<SharedWorkerScriptLoader>(
           routing_id, request_id, options, resource_request, std::move(client),
-          service_worker_provider_host_, resource_context_,
-          loader_factory_getter_, traffic_annotation),
+          service_worker_provider_host_, resource_context_, network_factory_,
+          traffic_annotation),
       std::move(request));
 }
 
diff --git a/content/browser/shared_worker/shared_worker_script_loader_factory.h b/content/browser/shared_worker/shared_worker_script_loader_factory.h
index 7e0e4c5c..9353548 100644
--- a/content/browser/shared_worker/shared_worker_script_loader_factory.h
+++ b/content/browser/shared_worker/shared_worker_script_loader_factory.h
@@ -8,11 +8,14 @@
 #include "base/macros.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
 
+namespace network {
+class SharedURLLoaderFactory;
+}  // namespace network
+
 namespace content {
 
 class ServiceWorkerContextWrapper;
 class ServiceWorkerProviderHost;
-class URLLoaderFactoryGetter;
 class ResourceContext;
 
 // S13nServiceWorker:
@@ -32,7 +35,7 @@
       ServiceWorkerContextWrapper* context,
       base::WeakPtr<ServiceWorkerProviderHost> provider_host,
       ResourceContext* resource_context,
-      scoped_refptr<URLLoaderFactoryGetter> loader_factory_getter);
+      scoped_refptr<network::SharedURLLoaderFactory> network_factory);
   ~SharedWorkerScriptLoaderFactory() override;
 
   // network::mojom::URLLoaderFactory:
@@ -49,7 +52,7 @@
  private:
   base::WeakPtr<ServiceWorkerProviderHost> service_worker_provider_host_;
   ResourceContext* resource_context_ = nullptr;
-  scoped_refptr<URLLoaderFactoryGetter> loader_factory_getter_;
+  scoped_refptr<network::SharedURLLoaderFactory> network_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(SharedWorkerScriptLoaderFactory);
 };
diff --git a/content/browser/shared_worker/shared_worker_service_impl.cc b/content/browser/shared_worker/shared_worker_service_impl.cc
index 597e99215..5b5d3a79 100644
--- a/content/browser/shared_worker/shared_worker_service_impl.cc
+++ b/content/browser/shared_worker/shared_worker_service_impl.cc
@@ -57,7 +57,7 @@
   mojo::MakeStrongAssociatedBinding(
       std::make_unique<SharedWorkerScriptLoaderFactory>(
           context.get(), host->AsWeakPtr(), context->resource_context(),
-          std::move(loader_factory_getter)),
+          loader_factory_getter->GetNetworkFactory()),
       mojo::MakeRequest(&script_loader_factory));
 
   BrowserThread::PostTask(
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index 735251f5..bd24432 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -24,6 +24,7 @@
 #include "content/browser/browser_main_loop.h"
 #include "content/browser/browsing_data/storage_partition_http_cache_data_remover.h"
 #include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/cookie_store/cookie_store_context.h"
 #include "content/browser/fileapi/browser_file_system_helper.h"
 #include "content/browser/gpu/shader_cache_factory.h"
 #include "content/browser/loader/prefetch_url_loader_service.h"
@@ -667,6 +668,14 @@
       base::MakeRefCounted<PrefetchURLLoaderService>(
           partition->url_loader_factory_getter_);
 
+  partition->cookie_store_context_ = base::MakeRefCounted<CookieStoreContext>();
+  // Unit tests use the Initialize() callback to crash early if restoring the
+  // CookieManagerStore's state from ServiceWorkerStorage fails. Production and
+  // browser tests rely on CookieStoreManager's well-defined behavior when
+  // restoring the state fails.
+  partition->cookie_store_context_->Initialize(
+      partition->service_worker_context_, base::DoNothing());
+
   return partition;
 }
 
@@ -827,6 +836,10 @@
   return prefetch_url_loader_service_.get();
 }
 
+CookieStoreContext* StoragePartitionImpl::GetCookieStoreContext() {
+  return cookie_store_context_.get();
+}
+
 void StoragePartitionImpl::OpenLocalStorage(
     const url::Origin& origin,
     mojom::LevelDBWrapperRequest request) {
diff --git a/content/browser/storage_partition_impl.h b/content/browser/storage_partition_impl.h
index 07992017..102e3ff 100644
--- a/content/browser/storage_partition_impl.h
+++ b/content/browser/storage_partition_impl.h
@@ -46,6 +46,7 @@
 namespace content {
 
 class BackgroundFetchContext;
+class CookieStoreContext;
 class BlobRegistryWrapper;
 class BlobURLLoaderFactory;
 class PrefetchURLLoaderService;
@@ -142,6 +143,7 @@
   BlobURLLoaderFactory* GetBlobURLLoaderFactory();
   BlobRegistryWrapper* GetBlobRegistry();
   PrefetchURLLoaderService* GetPrefetchURLLoaderService();
+  CookieStoreContext* GetCookieStoreContext();
 
   // mojom::StoragePartitionService interface.
   void OpenLocalStorage(const url::Origin& origin,
@@ -187,6 +189,7 @@
 
   friend class BackgroundSyncManagerTest;
   friend class BackgroundSyncServiceImplTest;
+  friend class CookieStoreManagerTest;
   friend class PaymentAppContentUnitTestBase;
   friend class StoragePartitionImplMap;
   friend class URLLoaderFactoryForBrowserProcess;
@@ -309,6 +312,7 @@
   scoped_refptr<BlobURLLoaderFactory> blob_url_loader_factory_;
   scoped_refptr<BlobRegistryWrapper> blob_registry_;
   scoped_refptr<PrefetchURLLoaderService> prefetch_url_loader_service_;
+  scoped_refptr<CookieStoreContext> cookie_store_context_;
 
   // BindingSet for StoragePartitionService, using the process id as the
   // binding context type. The process id can subsequently be used during
diff --git a/content/browser/storage_partition_impl_map.cc b/content/browser/storage_partition_impl_map.cc
index 2f5875b..3be6979 100644
--- a/content/browser/storage_partition_impl_map.cc
+++ b/content/browser/storage_partition_impl_map.cc
@@ -25,6 +25,7 @@
 #include "content/browser/appcache/chrome_appcache_service.h"
 #include "content/browser/background_fetch/background_fetch_context.h"
 #include "content/browser/blob_storage/chrome_blob_storage_context.h"
+#include "content/browser/cookie_store/cookie_store_context.h"
 #include "content/browser/devtools/devtools_url_request_interceptor.h"
 #include "content/browser/fileapi/browser_file_system_helper.h"
 #include "content/browser/loader/prefetch_url_loader_service.h"
@@ -435,10 +436,12 @@
       browser_context_->CreateMediaRequestContext() :
       browser_context_->CreateMediaRequestContextForStoragePartition(
           partition->GetPath(), in_memory));
+  partition->GetCookieStoreContext()->ListenToCookieChanges(
+      partition->GetNetworkContext(), base::DoNothing());
 
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) {
     // This needs to happen after SetURLRequestContext() since we need this
-    // code path only for non-NetworkService case where NetworkContext needs to
+    // code path only for non-NetworkService cases where NetworkContext needs to
     // be initialized using |url_request_context_|, which is initialized by
     // SetURLRequestContext().
     DCHECK(partition->url_loader_factory_getter());
diff --git a/content/common/BUILD.gn b/content/common/BUILD.gn
index e167401..04fce8c 100644
--- a/content/common/BUILD.gn
+++ b/content/common/BUILD.gn
@@ -329,6 +329,7 @@
     "//components/viz/service",
     "//content:resources",
     "//content/app/resources",
+    "//content/common/service_worker:service_worker_types_proto",
     "//content/public/common:interfaces",
     "//content/public/common:service_names",
     "//content/public/common:zygote_buildflags",
diff --git a/content/common/service_worker/BUILD.gn b/content/common/service_worker/BUILD.gn
new file mode 100644
index 0000000..65c27c1
--- /dev/null
+++ b/content/common/service_worker/BUILD.gn
@@ -0,0 +1,11 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/protobuf/proto_library.gni")
+
+proto_library("service_worker_types_proto") {
+  sources = [
+    "service_worker_types.proto",
+  ]
+}
diff --git a/content/common/service_worker/service_worker_event_dispatcher.mojom b/content/common/service_worker/service_worker_event_dispatcher.mojom
index 03204167..dc52bf4 100644
--- a/content/common/service_worker/service_worker_event_dispatcher.mojom
+++ b/content/common/service_worker/service_worker_event_dispatcher.mojom
@@ -8,6 +8,7 @@
 import "content/common/service_worker/service_worker_fetch_response_callback.mojom";
 import "mojo/public/mojom/base/string16.mojom";
 import "mojo/public/mojom/base/time.mojom";
+import "services/network/public/mojom/cookie_manager.mojom";
 import "third_party/blink/public/platform/modules/fetch/fetch_api_request.mojom";
 import "third_party/blink/public/platform/modules/payments/payment_app.mojom";
 import "third_party/blink/public/mojom/message_port/message_port.mojom";
@@ -109,6 +110,16 @@
       => (blink.mojom.ServiceWorkerEventStatus status,
           mojo_base.mojom.Time dispatch_event_time);
 
+  // Dispatches the cookie change events in the Async Cookie API specification.
+  // https://github.com/WICG/cookie-store/
+  // The callback is called once the event handler has run and the waitUntil()
+  // promise has settled.
+  DispatchCookieChangeEvent(
+      network.mojom.CanonicalCookie cookie,
+      network.mojom.CookieChangeCause cause)
+      => (blink.mojom.ServiceWorkerEventStatus status,
+          mojo_base.mojom.Time dispatch_event_time);
+
   // The Dispatch*FetchEvent() callback is called once the event finishes,
   // which means the event handler ran and all outstanding respondWith() and
   // waitUntil() promises have settled. |response_callback| is called once the
diff --git a/content/common/service_worker/service_worker_types.cc b/content/common/service_worker/service_worker_types.cc
index 4f93a652e..687235e 100644
--- a/content/common/service_worker/service_worker_types.cc
+++ b/content/common/service_worker/service_worker_types.cc
@@ -4,6 +4,7 @@
 
 #include "content/common/service_worker/service_worker_types.h"
 
+#include "content/common/service_worker/service_worker_types.pb.h"
 #include "net/base/load_flags.h"
 #include "storage/common/blob_storage/blob_handle.h"
 
@@ -55,6 +56,28 @@
 
 ServiceWorkerFetchRequest::~ServiceWorkerFetchRequest() {}
 
+std::string ServiceWorkerFetchRequest::Serialize() {
+  proto::internal::ServiceWorkerFetchRequest request_proto;
+
+  request_proto.set_url(url.spec());
+  request_proto.set_method(method);
+  request_proto.mutable_headers()->insert(headers.begin(), headers.end());
+  request_proto.mutable_referrer()->set_url(referrer.url.spec());
+  request_proto.mutable_referrer()->set_policy(referrer.policy);
+  request_proto.set_is_reload(is_reload);
+  request_proto.set_mode(static_cast<int>(mode));
+  request_proto.set_is_main_resource_load(is_main_resource_load);
+  request_proto.set_request_context_type(request_context_type);
+  request_proto.set_credentials_mode(static_cast<int>(credentials_mode));
+  request_proto.set_cache_mode(static_cast<int>(cache_mode));
+  request_proto.set_redirect_mode(static_cast<int>(redirect_mode));
+  request_proto.set_integrity(integrity);
+  request_proto.set_keepalive(keepalive);
+  request_proto.set_client_id(client_id);
+
+  return request_proto.SerializeAsString();
+}
+
 size_t ServiceWorkerFetchRequest::EstimatedStructSize() {
   size_t size = sizeof(ServiceWorkerFetchRequest);
   size += url.spec().size();
@@ -69,6 +92,40 @@
 }
 
 // static
+ServiceWorkerFetchRequest ServiceWorkerFetchRequest::ParseFromString(
+    const std::string& serialized) {
+  proto::internal::ServiceWorkerFetchRequest request_proto;
+  if (!request_proto.ParseFromString(serialized)) {
+    return ServiceWorkerFetchRequest();
+  }
+
+  ServiceWorkerFetchRequest request(
+      GURL(request_proto.url()), request_proto.method(),
+      ServiceWorkerHeaderMap(request_proto.headers().begin(),
+                             request_proto.headers().end()),
+      Referrer(GURL(request_proto.referrer().url()),
+               static_cast<blink::WebReferrerPolicy>(
+                   request_proto.referrer().policy())),
+      request_proto.is_reload());
+  request.mode =
+      static_cast<network::mojom::FetchRequestMode>(request_proto.mode());
+  request.is_main_resource_load = request_proto.is_main_resource_load();
+  request.request_context_type =
+      static_cast<RequestContextType>(request_proto.request_context_type());
+  request.credentials_mode = static_cast<network::mojom::FetchCredentialsMode>(
+      request_proto.credentials_mode());
+  request.cache_mode =
+      static_cast<blink::mojom::FetchCacheMode>(request_proto.cache_mode());
+  request.redirect_mode = static_cast<network::mojom::FetchRedirectMode>(
+      request_proto.redirect_mode());
+  request.integrity = request_proto.integrity();
+  request.keepalive = request_proto.keepalive();
+  request.client_id = request_proto.client_id();
+
+  return request;
+}
+
+// static
 blink::mojom::FetchCacheMode
 ServiceWorkerFetchRequest::GetCacheModeFromLoadFlags(int load_flags) {
   if (load_flags & net::LOAD_DISABLE_CACHE)
diff --git a/content/common/service_worker/service_worker_types.h b/content/common/service_worker/service_worker_types.h
index bf4983a..d84d525 100644
--- a/content/common/service_worker/service_worker_types.h
+++ b/content/common/service_worker/service_worker_types.h
@@ -90,10 +90,14 @@
   ServiceWorkerFetchRequest& operator=(const ServiceWorkerFetchRequest& other);
   ~ServiceWorkerFetchRequest();
   size_t EstimatedStructSize();
+  std::string Serialize();
 
   static blink::mojom::FetchCacheMode GetCacheModeFromLoadFlags(int load_flags);
+  static ServiceWorkerFetchRequest ParseFromString(
+      const std::string& serialized);
 
-  // Be sure to update EstimatedStructSize() when adding members.
+  // Be sure to update EstimatedStructSize(), Serialize(), and ParseFromString()
+  // when adding members.
   network::mojom::FetchRequestMode mode =
       network::mojom::FetchRequestMode::kNoCORS;
   bool is_main_resource_load = false;
diff --git a/content/common/service_worker/service_worker_types.proto b/content/common/service_worker/service_worker_types.proto
new file mode 100644
index 0000000..f4470b52
--- /dev/null
+++ b/content/common/service_worker/service_worker_types.proto
@@ -0,0 +1,41 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package content.proto.internal;
+
+// Serializable version of ServiceWorkerFetchRequest.
+//
+// Next Tag: 15
+message ServiceWorkerFetchRequest {
+  // Serializable version of the Referrer struct defined in
+  // https://cs.chromium.org/chromium/src/content/public/common/referrer.h
+  //
+  // Next Tag: 3
+  message Referrer {
+    optional string url = 1;
+    optional int32 policy = 2;  // blink::WebReferrerPolicy.
+  }
+
+  // Constructor params.
+  optional string url = 1;
+  optional string method = 2;
+  map<string, string> headers = 3;
+  optional Referrer referrer = 4;
+  optional bool is_reload = 5;
+
+  // Other params.
+  optional int32 mode = 6;  // network::mojom::FetchRequestMode.
+  optional bool is_main_resource_load = 7;
+  optional int32 request_context_type = 8;  // content::RequestContextType.
+  optional int32 credentials_mode = 9;  // network::mojom::FetchCredentialsMode.
+  optional int32 cache_mode = 10;       // blink::mojom::FetchCacheMode.
+  optional int32 redirect_mode = 11;    // network::mojom::FetchRedirectMode.
+  optional string integrity = 12;
+  optional bool keepalive = 13;
+  optional string client_id = 14;
+}
\ No newline at end of file
diff --git a/content/common/service_worker/service_worker_types_unittest.cc b/content/common/service_worker/service_worker_types_unittest.cc
index 546f56b..17a0818 100644
--- a/content/common/service_worker/service_worker_types_unittest.cc
+++ b/content/common/service_worker/service_worker_types_unittest.cc
@@ -87,6 +87,29 @@
   EXPECT_EQ(input.side_data_blob, output.side_data_blob);
 }
 
+TEST(ServiceWorkerRequestTest, SerialiazeDeserializeRoundTrip) {
+  ServiceWorkerFetchRequest request(
+      GURL("foo.com"), "GET", {{"User-Agent", "Chrome"}},
+      Referrer(
+          GURL("bar.com"),
+          blink::WebReferrerPolicy::kWebReferrerPolicyNoReferrerWhenDowngrade),
+      true);
+  request.mode = network::mojom::FetchRequestMode::kSameOrigin;
+  request.is_main_resource_load = true;
+  request.request_context_type =
+      RequestContextType::REQUEST_CONTEXT_TYPE_IFRAME;
+  request.credentials_mode = network::mojom::FetchCredentialsMode::kSameOrigin;
+  request.cache_mode = blink::mojom::FetchCacheMode::kForceCache;
+  request.redirect_mode = network::mojom::FetchRedirectMode::kManual;
+  request.integrity = "integrity";
+  request.keepalive = true;
+  request.client_id = "42";
+
+  EXPECT_EQ(request.Serialize(),
+            ServiceWorkerFetchRequest::ParseFromString(request.Serialize())
+                .Serialize());
+}
+
 }  // namespace
 
 }  // namespace content
diff --git a/content/public/app/mojo/content_browser_manifest.json b/content/public/app/mojo/content_browser_manifest.json
index 9642ff3..b8b3c922e 100644
--- a/content/public/app/mojo/content_browser_manifest.json
+++ b/content/public/app/mojo/content_browser_manifest.json
@@ -228,6 +228,7 @@
           "blink::mojom::BackgroundFetchService",
           "blink::mojom::BudgetService",
           "blink::mojom::CacheStorage",
+          "blink::mojom::CookieStore",
           "blink::mojom::LockManager",
           "blink::mojom::NotificationService",
           "blink::mojom::PermissionService",
diff --git a/content/public/test/browser_test_base.cc b/content/public/test/browser_test_base.cc
index aae1e6c..68df932 100644
--- a/content/public/test/browser_test_base.cc
+++ b/content/public/test/browser_test_base.cc
@@ -23,7 +23,6 @@
 #include "base/test/test_timeouts.h"
 #include "base/threading/thread_restrictions.h"
 #include "build/build_config.h"
-#include "content/browser/browser_process_sub_thread.h"
 #include "content/browser/renderer_host/render_process_host_impl.h"
 #include "content/browser/tracing/tracing_controller_impl.h"
 #include "content/public/app/content_main.h"
@@ -122,8 +121,7 @@
 
 }  // namespace
 
-extern int BrowserMain(const MainFunctionParams&,
-                       std::unique_ptr<BrowserProcessSubThread> io_thread);
+extern int BrowserMain(const MainFunctionParams&);
 
 BrowserTestBase::BrowserTestBase()
     : field_trial_list_(std::make_unique<base::FieldTrialList>(nullptr)),
@@ -315,7 +313,7 @@
   params.ui_task = ui_task.release();
   params.created_main_parts_closure = created_main_parts_closure.release();
   // TODO(phajdan.jr): Check return code, http://crbug.com/374738 .
-  BrowserMain(params, nullptr);
+  BrowserMain(params);
 #else
   GetContentMainParams()->ui_task = ui_task.release();
   GetContentMainParams()->created_main_parts_closure =
diff --git a/content/renderer/media/stream/webmediaplayer_ms.cc b/content/renderer/media/stream/webmediaplayer_ms.cc
index 125c03f..f2a1233 100644
--- a/content/renderer/media/stream/webmediaplayer_ms.cc
+++ b/content/renderer/media/stream/webmediaplayer_ms.cc
@@ -42,12 +42,19 @@
 #include "third_party/blink/public/web/web_local_frame.h"
 
 namespace {
+
 enum class RendererReloadAction {
   KEEP_RENDERER,
   REMOVE_RENDERER,
   NEW_RENDERER
 };
 
+bool IsPlayableTrack(const blink::WebMediaStreamTrack& track) {
+  return !track.IsNull() && !track.Source().IsNull() &&
+         track.Source().GetReadyState() !=
+             blink::WebMediaStreamSource::kReadyStateEnded;
+}
+
 }  // namespace
 
 namespace content {
@@ -417,6 +424,16 @@
   Reload();
 }
 
+void WebMediaPlayerMS::ActiveStateChanged(bool is_active) {
+  if (!is_active) {
+    // This makes the media element elegible to be garbage collected. Otherwise,
+    // the element will be considered active and will never be garbage
+    // collected.
+    SetNetworkState(kNetworkStateIdle);
+  }
+  // The case when the stream becomes active is handled by TrackAdded().
+}
+
 void WebMediaPlayerMS::Reload() {
   DCHECK(thread_checker_.CalledOnValidThread());
   if (web_stream_.IsNull())
@@ -438,7 +455,8 @@
     if (video_frame_provider_)
       renderer_action = RendererReloadAction::REMOVE_RENDERER;
     current_video_track_id_ = blink::WebString();
-  } else if (video_tracks[0].Id() != current_video_track_id_) {
+  } else if (video_tracks[0].Id() != current_video_track_id_ &&
+             IsPlayableTrack(video_tracks[0])) {
     renderer_action = RendererReloadAction::NEW_RENDERER;
     current_video_track_id_ = video_tracks[0].Id();
   }
@@ -448,6 +466,7 @@
       if (video_frame_provider_)
         video_frame_provider_->Stop();
 
+      SetNetworkState(kNetworkStateLoading);
       video_frame_provider_ = renderer_factory_->GetVideoRenderer(
           web_stream_,
           media::BindToCurrentLoop(
@@ -487,7 +506,8 @@
     if (audio_renderer_)
       renderer_action = RendererReloadAction::REMOVE_RENDERER;
     current_audio_track_id_ = blink::WebString();
-  } else if (audio_tracks[0].Id() != current_video_track_id_) {
+  } else if (audio_tracks[0].Id() != current_audio_track_id_ &&
+             IsPlayableTrack(audio_tracks[0])) {
     renderer_action = RendererReloadAction::NEW_RENDERER;
     current_audio_track_id_ = audio_tracks[0].Id();
   }
@@ -497,8 +517,14 @@
       if (audio_renderer_)
         audio_renderer_->Stop();
 
+      SetNetworkState(WebMediaPlayer::kNetworkStateLoading);
       audio_renderer_ = renderer_factory_->GetAudioRenderer(
           web_stream_, frame->GetRoutingID(), initial_audio_output_device_id_);
+
+      // |audio_renderer_| can be null in tests.
+      if (!audio_renderer_)
+        break;
+
       audio_renderer_->SetVolume(volume_);
       audio_renderer_->Start();
       audio_renderer_->Play();
diff --git a/content/renderer/media/stream/webmediaplayer_ms.h b/content/renderer/media/stream/webmediaplayer_ms.h
index d43fdad..cf07496 100644
--- a/content/renderer/media/stream/webmediaplayer_ms.h
+++ b/content/renderer/media/stream/webmediaplayer_ms.h
@@ -195,6 +195,7 @@
   // blink::WebMediaStreamObserver implementation
   void TrackAdded(const blink::WebMediaStreamTrack& track) override;
   void TrackRemoved(const blink::WebMediaStreamTrack& track) override;
+  void ActiveStateChanged(bool is_active) override;
 
  private:
   friend class WebMediaPlayerMSTest;
diff --git a/content/renderer/service_worker/service_worker_context_client.cc b/content/renderer/service_worker/service_worker_context_client.cc
index f2c3024a..3db7956 100644
--- a/content/renderer/service_worker/service_worker_context_client.cc
+++ b/content/renderer/service_worker/service_worker_context_client.cc
@@ -490,6 +490,8 @@
       notification_close_event_callbacks;
   std::map<int, DispatchPushEventCallback> push_event_callbacks;
   std::map<int, DispatchFetchEventCallback> fetch_event_callbacks;
+  std::map<int, DispatchCookieChangeEventCallback>
+      cookie_change_event_callbacks;
   std::map<int, DispatchExtendableMessageEventCallback> message_event_callbacks;
 
   // Maps for response callbacks.
@@ -970,6 +972,15 @@
                    base::Time::FromDoubleT(event_dispatch_time));
 }
 
+void ServiceWorkerContextClient::DidHandleCookieChangeEvent(
+    int request_id,
+    blink::mojom::ServiceWorkerEventStatus status,
+    double event_dispatch_time) {
+  RunEventCallback(&context_->cookie_change_event_callbacks,
+                   context_->timeout_timer.get(), request_id, status,
+                   base::Time::FromDoubleT(event_dispatch_time));
+}
+
 void ServiceWorkerContextClient::DidHandleExtendableMessageEvent(
     int request_id,
     blink::mojom::ServiceWorkerEventStatus status,
@@ -1606,6 +1617,27 @@
   proxy_->DispatchPushEvent(request_id, data);
 }
 
+void ServiceWorkerContextClient::DispatchCookieChangeEvent(
+    const net::CanonicalCookie& cookie,
+    ::network::mojom::CookieChangeCause cause,
+    DispatchCookieChangeEventCallback callback) {
+  TRACE_EVENT0("ServiceWorker",
+               "ServiceWorkerContextClient::DispatchCookieChangeEvent");
+
+  int request_id = context_->timeout_timer->StartEvent(
+      CreateAbortCallback(&context_->cookie_change_event_callbacks));
+  context_->cookie_change_event_callbacks.emplace(request_id,
+                                                  std::move(callback));
+
+  // TODO(pwnall): Map |cause| to a blink enum. Currently, a cookie overwrite
+  //               shows up as delete + insert.
+  bool is_cookie_delete =
+      cause != ::network::mojom::CookieChangeCause::INSERTED;
+  proxy_->DispatchCookieChangeEvent(
+      request_id, blink::WebString::FromUTF8(cookie.Name()),
+      blink::WebString::FromUTF8(cookie.Value()), is_cookie_delete);
+}
+
 void ServiceWorkerContextClient::Ping(PingCallback callback) {
   std::move(callback).Run();
 }
diff --git a/content/renderer/service_worker/service_worker_context_client.h b/content/renderer/service_worker/service_worker_context_client.h
index 3b02aa2..8151a71 100644
--- a/content/renderer/service_worker/service_worker_context_client.h
+++ b/content/renderer/service_worker/service_worker_context_client.h
@@ -163,6 +163,9 @@
       int request_id,
       blink::mojom::ServiceWorkerEventStatus status,
       double dispatch_event_time) override;
+  void DidHandleCookieChangeEvent(int request_id,
+                                  blink::mojom::ServiceWorkerEventStatus status,
+                                  double dispatch_event_time) override;
   void DidHandleExtendableMessageEvent(
       int request_id,
       blink::mojom::ServiceWorkerEventStatus status,
@@ -335,6 +338,10 @@
       payments::mojom::PaymentRequestEventDataPtr event_data,
       payments::mojom::PaymentHandlerResponseCallbackPtr response_callback,
       DispatchPaymentRequestEventCallback callback) override;
+  void DispatchCookieChangeEvent(
+      const net::CanonicalCookie& cookie,
+      ::network::mojom::CookieChangeCause cause,
+      DispatchCookieChangeEventCallback callback) override;
   void Ping(PingCallback callback) override;
   void SetIdleTimerDelayToZero() override;
 
diff --git a/content/renderer/service_worker/service_worker_context_client_unittest.cc b/content/renderer/service_worker/service_worker_context_client_unittest.cc
index 856d20e..d0c04b3 100644
--- a/content/renderer/service_worker/service_worker_context_client_unittest.cc
+++ b/content/renderer/service_worker/service_worker_context_client_unittest.cc
@@ -104,6 +104,12 @@
       override {
     NOTREACHED();
   }
+  void DispatchCookieChangeEvent(int event_id,
+                                 const blink::WebString& name,
+                                 const blink::WebString& value,
+                                 bool is_deleted) override {
+    NOTREACHED();
+  }
   void DispatchExtendableMessageEvent(
       int event_id,
       blink::TransferableMessage message,
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 92dda1c..d7cb4ae6 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1264,6 +1264,7 @@
     "../browser/compositor/gpu_vsync_begin_frame_source_unittest.cc",
     "../browser/compositor/reflector_impl_unittest.cc",
     "../browser/compositor/software_browser_compositor_output_surface_unittest.cc",
+    "../browser/cookie_store/cookie_store_manager_unittest.cc",
     "../browser/devtools/devtools_http_handler_unittest.cc",
     "../browser/devtools/devtools_manager_unittest.cc",
     "../browser/devtools/devtools_video_consumer_unittest.cc",
@@ -1363,6 +1364,7 @@
     "../browser/manifest/manifest_icon_selector_unittest.cc",
     "../browser/media/audible_metrics_unittest.cc",
     "../browser/media/audio_input_stream_broker_unittest.cc",
+    "../browser/media/audio_loopback_stream_broker_unittest.cc",
     "../browser/media/audio_output_stream_broker_unittest.cc",
     "../browser/media/audio_stream_monitor_unittest.cc",
     "../browser/media/capture/audio_mirroring_manager_unittest.cc",
diff --git a/docs/code_reviews.md b/docs/code_reviews.md
index 08af713..05b4cf0 100644
--- a/docs/code_reviews.md
+++ b/docs/code_reviews.md
@@ -33,7 +33,7 @@
     request more time, or suggest another reviewer.
 
   * Use the status field in Gerrit settings to indicate if you're away and when
-  * you'll be back.
+    you'll be back.
 
   * Don't generally discourage people from sending you code reviews. This
     includes using a blanket "slow" in your status field.
diff --git a/gpu/command_buffer/build_raster_cmd_buffer.py b/gpu/command_buffer/build_raster_cmd_buffer.py
index 2a688a3..d19b7432 100755
--- a/gpu/command_buffer/build_raster_cmd_buffer.py
+++ b/gpu/command_buffer/build_raster_cmd_buffer.py
@@ -313,6 +313,20 @@
   'OrderingBarrierCHROMIUM': {
     'type': 'NoCommand',
   },
+  'TraceBeginCHROMIUM': {
+    'type': 'Custom',
+    'impl_func': False,
+    'client_test': False,
+    'cmd_args': 'GLuint category_bucket_id, GLuint name_bucket_id',
+    'extension': 'CHROMIUM_trace_marker',
+  },
+  'TraceEndCHROMIUM': {
+    'impl_func': False,
+    'client_test': False,
+    'decoder_func': 'DoTraceEndCHROMIUM',
+    'unit_test': False,
+    'extension': 'CHROMIUM_trace_marker',
+  },
   'InsertFenceSyncCHROMIUM': {
     'type': 'Custom',
     'internal': True,
diff --git a/gpu/command_buffer/client/raster_cmd_helper_autogen.h b/gpu/command_buffer/client/raster_cmd_helper_autogen.h
index c252dac6..4d6d255d 100644
--- a/gpu/command_buffer/client/raster_cmd_helper_autogen.h
+++ b/gpu/command_buffer/client/raster_cmd_helper_autogen.h
@@ -294,4 +294,20 @@
   }
 }
 
+void TraceBeginCHROMIUM(GLuint category_bucket_id, GLuint name_bucket_id) {
+  raster::cmds::TraceBeginCHROMIUM* c =
+      GetCmdSpace<raster::cmds::TraceBeginCHROMIUM>();
+  if (c) {
+    c->Init(category_bucket_id, name_bucket_id);
+  }
+}
+
+void TraceEndCHROMIUM() {
+  raster::cmds::TraceEndCHROMIUM* c =
+      GetCmdSpace<raster::cmds::TraceEndCHROMIUM>();
+  if (c) {
+    c->Init();
+  }
+}
+
 #endif  // GPU_COMMAND_BUFFER_CLIENT_RASTER_CMD_HELPER_AUTOGEN_H_
diff --git a/gpu/command_buffer/client/raster_implementation.cc b/gpu/command_buffer/client/raster_implementation.cc
index c18bda98..5fb833f2 100644
--- a/gpu/command_buffer/client/raster_implementation.cc
+++ b/gpu/command_buffer/client/raster_implementation.cc
@@ -1231,6 +1231,32 @@
   NOTREACHED();
 }
 
+void RasterImplementation::TraceBeginCHROMIUM(const char* category_name,
+                                              const char* trace_name) {
+  GPU_CLIENT_SINGLE_THREAD_CHECK();
+  GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glTraceBeginCHROMIUM("
+                     << category_name << ", " << trace_name << ")");
+  SetBucketAsCString(kResultBucketId, category_name);
+  SetBucketAsCString(kResultBucketId + 1, trace_name);
+  helper_->TraceBeginCHROMIUM(kResultBucketId, kResultBucketId + 1);
+  helper_->SetBucketSize(kResultBucketId, 0);
+  helper_->SetBucketSize(kResultBucketId + 1, 0);
+  current_trace_stack_++;
+}
+
+void RasterImplementation::TraceEndCHROMIUM() {
+  GPU_CLIENT_SINGLE_THREAD_CHECK();
+  GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glTraceEndCHROMIUM("
+                     << ")");
+  if (current_trace_stack_ == 0) {
+    SetGLError(GL_INVALID_OPERATION, "glTraceEndCHROMIUM",
+               "missing begin trace");
+    return;
+  }
+  helper_->TraceEndCHROMIUM();
+  current_trace_stack_--;
+}
+
 RasterImplementation::RasterProperties::RasterProperties(
     SkColor background_color,
     bool can_use_lcd_text,
diff --git a/gpu/command_buffer/client/raster_implementation_autogen.h b/gpu/command_buffer/client/raster_implementation_autogen.h
index 9b064384..aee0e055 100644
--- a/gpu/command_buffer/client/raster_implementation_autogen.h
+++ b/gpu/command_buffer/client/raster_implementation_autogen.h
@@ -103,4 +103,9 @@
                     GLsizei width,
                     GLsizei height) override;
 
+void TraceBeginCHROMIUM(const char* category_name,
+                        const char* trace_name) override;
+
+void TraceEndCHROMIUM() override;
+
 #endif  // GPU_COMMAND_BUFFER_CLIENT_RASTER_IMPLEMENTATION_AUTOGEN_H_
diff --git a/gpu/command_buffer/client/raster_implementation_gles.cc b/gpu/command_buffer/client/raster_implementation_gles.cc
index 8630cf5..0a32278 100644
--- a/gpu/command_buffer/client/raster_implementation_gles.cc
+++ b/gpu/command_buffer/client/raster_implementation_gles.cc
@@ -352,5 +352,14 @@
   gl_->ActiveTexture(GL_TEXTURE0);
 }
 
+void RasterImplementationGLES::TraceBeginCHROMIUM(const char* category_name,
+                                                  const char* trace_name) {
+  gl_->TraceBeginCHROMIUM(category_name, trace_name);
+}
+
+void RasterImplementationGLES::TraceEndCHROMIUM() {
+  gl_->TraceEndCHROMIUM();
+}
+
 }  // namespace raster
 }  // namespace gpu
diff --git a/gpu/command_buffer/client/raster_implementation_gles.h b/gpu/command_buffer/client/raster_implementation_gles.h
index a665567f..a5a7172f 100644
--- a/gpu/command_buffer/client/raster_implementation_gles.h
+++ b/gpu/command_buffer/client/raster_implementation_gles.h
@@ -128,6 +128,10 @@
   void BeginGpuRaster() override;
   void EndGpuRaster() override;
 
+  void TraceBeginCHROMIUM(const char* category_name,
+                          const char* trace_name) override;
+  void TraceEndCHROMIUM() override;
+
  private:
   struct Texture {
     Texture(GLuint id,
diff --git a/gpu/command_buffer/client/raster_interface_autogen.h b/gpu/command_buffer/client/raster_interface_autogen.h
index abdfc4e..39fb283a 100644
--- a/gpu/command_buffer/client/raster_interface_autogen.h
+++ b/gpu/command_buffer/client/raster_interface_autogen.h
@@ -71,4 +71,7 @@
                             GLint y,
                             GLsizei width,
                             GLsizei height) = 0;
+virtual void TraceBeginCHROMIUM(const char* category_name,
+                                const char* trace_name) = 0;
+virtual void TraceEndCHROMIUM() = 0;
 #endif  // GPU_COMMAND_BUFFER_CLIENT_RASTER_INTERFACE_AUTOGEN_H_
diff --git a/gpu/command_buffer/common/raster_cmd_format_autogen.h b/gpu/command_buffer/common/raster_cmd_format_autogen.h
index faad7ed..51c39d02 100644
--- a/gpu/command_buffer/common/raster_cmd_format_autogen.h
+++ b/gpu/command_buffer/common/raster_cmd_format_autogen.h
@@ -1397,4 +1397,68 @@
 static_assert(offsetof(CopySubTexture, height) == 32,
               "offset of CopySubTexture height should be 32");
 
+struct TraceBeginCHROMIUM {
+  typedef TraceBeginCHROMIUM ValueType;
+  static const CommandId kCmdId = kTraceBeginCHROMIUM;
+  static const cmd::ArgFlags kArgFlags = cmd::kFixed;
+  static const uint8_t cmd_flags = CMD_FLAG_SET_TRACE_LEVEL(3);
+
+  static uint32_t ComputeSize() {
+    return static_cast<uint32_t>(sizeof(ValueType));  // NOLINT
+  }
+
+  void SetHeader() { header.SetCmd<ValueType>(); }
+
+  void Init(GLuint _category_bucket_id, GLuint _name_bucket_id) {
+    SetHeader();
+    category_bucket_id = _category_bucket_id;
+    name_bucket_id = _name_bucket_id;
+  }
+
+  void* Set(void* cmd, GLuint _category_bucket_id, GLuint _name_bucket_id) {
+    static_cast<ValueType*>(cmd)->Init(_category_bucket_id, _name_bucket_id);
+    return NextCmdAddress<ValueType>(cmd);
+  }
+
+  gpu::CommandHeader header;
+  uint32_t category_bucket_id;
+  uint32_t name_bucket_id;
+};
+
+static_assert(sizeof(TraceBeginCHROMIUM) == 12,
+              "size of TraceBeginCHROMIUM should be 12");
+static_assert(offsetof(TraceBeginCHROMIUM, header) == 0,
+              "offset of TraceBeginCHROMIUM header should be 0");
+static_assert(offsetof(TraceBeginCHROMIUM, category_bucket_id) == 4,
+              "offset of TraceBeginCHROMIUM category_bucket_id should be 4");
+static_assert(offsetof(TraceBeginCHROMIUM, name_bucket_id) == 8,
+              "offset of TraceBeginCHROMIUM name_bucket_id should be 8");
+
+struct TraceEndCHROMIUM {
+  typedef TraceEndCHROMIUM ValueType;
+  static const CommandId kCmdId = kTraceEndCHROMIUM;
+  static const cmd::ArgFlags kArgFlags = cmd::kFixed;
+  static const uint8_t cmd_flags = CMD_FLAG_SET_TRACE_LEVEL(3);
+
+  static uint32_t ComputeSize() {
+    return static_cast<uint32_t>(sizeof(ValueType));  // NOLINT
+  }
+
+  void SetHeader() { header.SetCmd<ValueType>(); }
+
+  void Init() { SetHeader(); }
+
+  void* Set(void* cmd) {
+    static_cast<ValueType*>(cmd)->Init();
+    return NextCmdAddress<ValueType>(cmd);
+  }
+
+  gpu::CommandHeader header;
+};
+
+static_assert(sizeof(TraceEndCHROMIUM) == 4,
+              "size of TraceEndCHROMIUM should be 4");
+static_assert(offsetof(TraceEndCHROMIUM, header) == 0,
+              "offset of TraceEndCHROMIUM header should be 0");
+
 #endif  // GPU_COMMAND_BUFFER_COMMON_RASTER_CMD_FORMAT_AUTOGEN_H_
diff --git a/gpu/command_buffer/common/raster_cmd_format_test_autogen.h b/gpu/command_buffer/common/raster_cmd_format_test_autogen.h
index 84bd76f..774ad6ca 100644
--- a/gpu/command_buffer/common/raster_cmd_format_test_autogen.h
+++ b/gpu/command_buffer/common/raster_cmd_format_test_autogen.h
@@ -481,4 +481,25 @@
   CheckBytesWrittenMatchesExpectedSize(next_cmd, sizeof(cmd));
 }
 
+TEST_F(RasterFormatTest, TraceBeginCHROMIUM) {
+  cmds::TraceBeginCHROMIUM& cmd = *GetBufferAs<cmds::TraceBeginCHROMIUM>();
+  void* next_cmd =
+      cmd.Set(&cmd, static_cast<GLuint>(11), static_cast<GLuint>(12));
+  EXPECT_EQ(static_cast<uint32_t>(cmds::TraceBeginCHROMIUM::kCmdId),
+            cmd.header.command);
+  EXPECT_EQ(sizeof(cmd), cmd.header.size * 4u);
+  EXPECT_EQ(static_cast<GLuint>(11), cmd.category_bucket_id);
+  EXPECT_EQ(static_cast<GLuint>(12), cmd.name_bucket_id);
+  CheckBytesWrittenMatchesExpectedSize(next_cmd, sizeof(cmd));
+}
+
+TEST_F(RasterFormatTest, TraceEndCHROMIUM) {
+  cmds::TraceEndCHROMIUM& cmd = *GetBufferAs<cmds::TraceEndCHROMIUM>();
+  void* next_cmd = cmd.Set(&cmd);
+  EXPECT_EQ(static_cast<uint32_t>(cmds::TraceEndCHROMIUM::kCmdId),
+            cmd.header.command);
+  EXPECT_EQ(sizeof(cmd), cmd.header.size * 4u);
+  CheckBytesWrittenMatchesExpectedSize(next_cmd, sizeof(cmd));
+}
+
 #endif  // GPU_COMMAND_BUFFER_COMMON_RASTER_CMD_FORMAT_TEST_AUTOGEN_H_
diff --git a/gpu/command_buffer/common/raster_cmd_ids_autogen.h b/gpu/command_buffer/common/raster_cmd_ids_autogen.h
index e64146a2..bfa96d3 100644
--- a/gpu/command_buffer/common/raster_cmd_ids_autogen.h
+++ b/gpu/command_buffer/common/raster_cmd_ids_autogen.h
@@ -40,7 +40,9 @@
   OP(BindTexImage2DCHROMIUM)                   /* 281 */ \
   OP(ReleaseTexImage2DCHROMIUM)                /* 282 */ \
   OP(TexStorage2D)                             /* 283 */ \
-  OP(CopySubTexture)                           /* 284 */
+  OP(CopySubTexture)                           /* 284 */ \
+  OP(TraceBeginCHROMIUM)                       /* 285 */ \
+  OP(TraceEndCHROMIUM)                         /* 286 */
 
 enum CommandId {
   kOneBeforeStartPoint =
diff --git a/gpu/command_buffer/raster_cmd_buffer_functions.txt b/gpu/command_buffer/raster_cmd_buffer_functions.txt
index f616108..69b2ab0 100644
--- a/gpu/command_buffer/raster_cmd_buffer_functions.txt
+++ b/gpu/command_buffer/raster_cmd_buffer_functions.txt
@@ -52,3 +52,5 @@
 GL_APICALL void         GL_APIENTRY glReleaseTexImage2DCHROMIUM (GLuint texture_id, GLint image_id);
 GL_APICALL void         GL_APIENTRY glTexStorage2D (GLuint texture_id, GLsizei levels, GLsizei width, GLsizei height);
 GL_APICALL void         GL_APIENTRY glCopySubTexture (GLuint source_id, GLuint dest_id, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height);
+GL_APICALL void         GL_APIENTRY glTraceBeginCHROMIUM (const char* category_name, const char* trace_name);
+GL_APICALL void         GL_APIENTRY glTraceEndCHROMIUM (void);
diff --git a/gpu/command_buffer/service/decoder_context.h b/gpu/command_buffer/service/decoder_context.h
index 2c2ab08..9aa5c53 100644
--- a/gpu/command_buffer/service/decoder_context.h
+++ b/gpu/command_buffer/service/decoder_context.h
@@ -35,6 +35,7 @@
 class ErrorState;
 class FeatureInfo;
 class GpuFenceManager;
+class Outputter;
 class Texture;
 struct ContextState;
 struct DisallowedFeatures;
@@ -218,6 +219,11 @@
   //
   // Set to true to LOG every command.
   virtual void SetLogCommands(bool log_commands) = 0;
+
+  //
+  // Methods required by GpuTracer
+  //
+  virtual gles2::Outputter* outputter() const = 0;
 };
 
 }  // namespace gpu
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index 00a82d0..f1245433 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -560,6 +560,10 @@
   log_commands_ = log_commands;
 }
 
+Outputter* GLES2Decoder::outputter() const {
+  return outputter_;
+}
+
 // This class implements GLES2Decoder so we don't have to expose all the GLES2
 // cmd stuff to outside this class.
 class GLES2DecoderImpl : public GLES2Decoder, public ErrorStateClient {
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.h b/gpu/command_buffer/service/gles2_cmd_decoder.h
index 56e53edd..fd5dacc 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.h
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.h
@@ -130,7 +130,7 @@
   // Set to true to LOG every command.
   void SetLogCommands(bool log_commands) override;
 
-  Outputter* outputter() const { return outputter_; }
+  Outputter* outputter() const override;
 
   // Set the surface associated with the default FBO.
   virtual void SetSurface(const scoped_refptr<gl::GLSurface>& surface) = 0;
diff --git a/gpu/command_buffer/service/gpu_tracer.cc b/gpu/command_buffer/service/gpu_tracer.cc
index 748dcc7..a0461746 100644
--- a/gpu/command_buffer/service/gpu_tracer.cc
+++ b/gpu/command_buffer/service/gpu_tracer.cc
@@ -17,6 +17,7 @@
 #include "base/trace_event/trace_event.h"
 #include "gpu/command_buffer/common/gles2_cmd_utils.h"
 #include "gpu/command_buffer/service/context_group.h"
+#include "gpu/command_buffer/service/decoder_context.h"
 #include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_context.h"
 #include "ui/gl/gl_version_info.h"
@@ -171,7 +172,7 @@
   }
 }
 
-GPUTracer::GPUTracer(GLES2Decoder* decoder)
+GPUTracer::GPUTracer(DecoderContext* decoder)
     : gpu_trace_srv_category(TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(
           TRACE_DISABLED_BY_DEFAULT("gpu.service"))),
       gpu_trace_dev_category(TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(
@@ -297,7 +298,7 @@
 
 void GPUTracer::ProcessTraces() {
   if (!gpu_timing_client_->IsAvailable()) {
-   while (!finished_traces_.empty()) {
+    while (!finished_traces_.empty()) {
       finished_traces_.front()->Destroy(false);
       finished_traces_.pop_front();
     }
diff --git a/gpu/command_buffer/service/gpu_tracer.h b/gpu/command_buffer/service/gpu_tracer.h
index 79de329..a78ed87 100644
--- a/gpu/command_buffer/service/gpu_tracer.h
+++ b/gpu/command_buffer/service/gpu_tracer.h
@@ -16,15 +16,17 @@
 #include "base/containers/stack.h"
 #include "base/macros.h"
 #include "base/threading/thread.h"
-#include "gpu/command_buffer/service/gles2_cmd_decoder.h"
 #include "gpu/gpu_gles2_export.h"
 
 namespace gl {
 class GPUTimingClient;
 class GPUTimer;
-}
+}  // namespace gl
 
 namespace gpu {
+
+class DecoderContext;
+
 namespace gles2 {
 
 class Outputter;
@@ -36,7 +38,7 @@
 
   kTraceCHROMIUM,
   kTraceDecoder,
-  kTraceDisjoint, // Used internally.
+  kTraceDisjoint,  // Used internally.
 
   NUM_TRACER_SOURCES
 };
@@ -55,7 +57,7 @@
 // Traces GPU Commands.
 class GPU_GLES2_EXPORT GPUTracer {
  public:
-  explicit GPUTracer(GLES2Decoder* decoder);
+  explicit GPUTracer(DecoderContext* decoder);
   virtual ~GPUTracer();
 
   void Destroy(bool have_context);
@@ -95,7 +97,7 @@
   Outputter* outputter_ = nullptr;
   std::vector<TraceMarker> markers_[NUM_TRACER_SOURCES];
   base::circular_deque<scoped_refptr<GPUTrace>> finished_traces_;
-  GLES2Decoder* decoder_;
+  DecoderContext* decoder_;
   int64_t disjoint_time_ = 0;
   bool gpu_executing_ = false;
   bool began_device_traces_ = false;
diff --git a/gpu/command_buffer/service/raster_decoder.cc b/gpu/command_buffer/service/raster_decoder.cc
index 6de44a8..c6b6456 100644
--- a/gpu/command_buffer/service/raster_decoder.cc
+++ b/gpu/command_buffer/service/raster_decoder.cc
@@ -45,6 +45,7 @@
 #include "gpu/command_buffer/service/gl_utils.h"
 #include "gpu/command_buffer/service/gles2_cmd_copy_tex_image.h"
 #include "gpu/command_buffer/service/gles2_cmd_copy_texture_chromium.h"
+#include "gpu/command_buffer/service/gpu_tracer.h"
 #include "gpu/command_buffer/service/image_factory.h"
 #include "gpu/command_buffer/service/image_manager.h"
 #include "gpu/command_buffer/service/indexed_buffer_binding_host.h"
@@ -543,6 +544,7 @@
   void DoGetIntegerv(GLenum pname, GLint* params, GLsizei params_size);
   void DoTexParameteri(GLuint texture_id, GLenum pname, GLint param);
   void DoBindTexImage2DCHROMIUM(GLuint texture_id, GLint image_id);
+  void DoTraceEndCHROMIUM();
   void DoProduceTextureDirect(GLuint texture, const volatile GLbyte* key);
   void DoReleaseTexImage2DCHROMIUM(GLuint texture_id, GLint image_id);
   void TexStorage2DImage(TextureRef* texture_ref,
@@ -724,8 +726,6 @@
   DecoderTextureState texture_state_;
   DecoderFramebufferState framebuffer_state_;
 
-  bool gpu_debug_commands_;
-
   // An optional behaviour to lose the context and group when OOM.
   bool lose_context_when_out_of_memory_ = false;
 
@@ -735,6 +735,12 @@
   std::unique_ptr<CopyTexImageResourceManager> copy_tex_image_blit_;
   std::unique_ptr<CopyTextureCHROMIUMResourceManager> copy_texture_chromium_;
 
+  std::unique_ptr<GPUTracer> gpu_tracer_;
+  const unsigned char* gpu_decoder_category_;
+  static constexpr int gpu_trace_level_ = 2;
+  bool gpu_trace_commands_ = false;
+  bool gpu_debug_commands_ = false;
+
   // Raster helpers.
   ServiceFontManager font_manager_;
   sk_sp<GrContext> gr_context_;
@@ -770,11 +776,9 @@
                                group);
 }
 
-RasterDecoder::RasterDecoder(CommandBufferServiceBase* command_buffer_service)
-    : CommonDecoder(command_buffer_service),
-      initialized_(false),
-      debug_(false),
-      log_commands_(false) {}
+RasterDecoder::RasterDecoder(CommandBufferServiceBase* command_buffer_service,
+                             gles2::Outputter* outputter)
+    : CommonDecoder(command_buffer_service), outputter_(outputter) {}
 
 RasterDecoder::~RasterDecoder() {}
 
@@ -804,6 +808,10 @@
   log_commands_ = log_commands;
 }
 
+gles2::Outputter* RasterDecoder::outputter() const {
+  return outputter_;
+}
+
 base::StringPiece RasterDecoder::GetLogPrefix() {
   return GetLogger()->GetLogPrefix();
 }
@@ -813,7 +821,7 @@
     CommandBufferServiceBase* command_buffer_service,
     Outputter* outputter,
     ContextGroup* group)
-    : RasterDecoder(command_buffer_service),
+    : RasterDecoder(command_buffer_service, outputter),
       client_(client),
       logger_(&debug_marker_manager_, client),
       group_(group),
@@ -823,6 +831,8 @@
       texture_state_(group_->feature_info()->workarounds()),
       service_logging_(
           group_->gpu_preferences().enable_gpu_service_logging_gpu),
+      gpu_decoder_category_(TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(
+          TRACE_DISABLED_BY_DEFAULT("gpu_decoder"))),
       font_manager_(this),
       weak_ptr_factory_(this) {}
 
@@ -859,6 +869,9 @@
   surface_ = surface;
   context_ = context;
 
+  // Create GPU Tracer for timing values.
+  gpu_tracer_.reset(new GPUTracer(this));
+
   // Save the loseContextWhenOutOfMemory context creation attribute.
   lose_context_when_out_of_memory_ =
       attrib_helper.lose_context_when_out_of_memory;
@@ -1258,10 +1271,11 @@
 }
 
 bool RasterDecoderImpl::HasMoreIdleWork() const {
-  return false;
+  return gpu_tracer_->HasTracesToProcess();
 }
 
 void RasterDecoderImpl::PerformIdleWork() {
+  gpu_tracer_->ProcessTraces();
 }
 
 bool RasterDecoderImpl::HasPollingWork() const {
@@ -1368,10 +1382,14 @@
 }
 
 void RasterDecoderImpl::BeginDecoding() {
-  gpu_debug_commands_ = log_commands() || debug();
+  gpu_tracer_->BeginDecoding();
+  gpu_trace_commands_ = gpu_tracer_->IsTracing() && *gpu_decoder_category_;
+  gpu_debug_commands_ = log_commands() || debug() || gpu_trace_commands_;
 }
 
-void RasterDecoderImpl::EndDecoding() {}
+void RasterDecoderImpl::EndDecoding() {
+  gpu_tracer_->EndDecoding();
+}
 
 const char* RasterDecoderImpl::GetCommandName(unsigned int command_id) const {
   if (command_id >= kFirstRasterCommand && command_id < kNumCommands) {
@@ -1431,9 +1449,22 @@
       unsigned int info_arg_count = static_cast<unsigned int>(info.arg_count);
       if ((info.arg_flags == cmd::kFixed && arg_count == info_arg_count) ||
           (info.arg_flags == cmd::kAtLeastN && arg_count >= info_arg_count)) {
+        bool doing_gpu_trace = false;
+        if (DebugImpl && gpu_trace_commands_) {
+          if (CMD_FLAG_GET_TRACE_LEVEL(info.cmd_flags) <= gpu_trace_level_) {
+            doing_gpu_trace = true;
+            gpu_tracer_->Begin(TRACE_DISABLED_BY_DEFAULT("gpu_decoder"),
+                               GetCommandName(command), kTraceDecoder);
+          }
+        }
+
         uint32_t immediate_data_size = (arg_count - info_arg_count) *
                                        sizeof(CommandBufferEntry);  // NOLINT
         result = (this->*info.cmd_handler)(immediate_data_size, cmd_data);
+
+        if (DebugImpl && doing_gpu_trace)
+          gpu_tracer_->End(kTraceDecoder);
+
         if (DebugImpl && debug() && !WasContextLost()) {
           GLenum error;
           while ((error = api()->glGetErrorFn()) != GL_NO_ERROR) {
@@ -2143,6 +2174,43 @@
                                    nullptr, Texture::UNBOUND);
 }
 
+error::Error RasterDecoderImpl::HandleTraceBeginCHROMIUM(
+    uint32_t immediate_data_size,
+    const volatile void* cmd_data) {
+  const volatile gles2::cmds::TraceBeginCHROMIUM& c =
+      *static_cast<const volatile gles2::cmds::TraceBeginCHROMIUM*>(cmd_data);
+  Bucket* category_bucket = GetBucket(c.category_bucket_id);
+  Bucket* name_bucket = GetBucket(c.name_bucket_id);
+  if (!category_bucket || category_bucket->size() == 0 || !name_bucket ||
+      name_bucket->size() == 0) {
+    return error::kInvalidArguments;
+  }
+
+  std::string category_name;
+  std::string trace_name;
+  if (!category_bucket->GetAsString(&category_name) ||
+      !name_bucket->GetAsString(&trace_name)) {
+    return error::kInvalidArguments;
+  }
+
+  debug_marker_manager_.PushGroup(trace_name);
+  if (!gpu_tracer_->Begin(category_name, trace_name, kTraceCHROMIUM)) {
+    LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glTraceBeginCHROMIUM",
+                       "unable to create begin trace");
+    return error::kNoError;
+  }
+  return error::kNoError;
+}
+
+void RasterDecoderImpl::DoTraceEndCHROMIUM() {
+  debug_marker_manager_.PopGroup();
+  if (!gpu_tracer_->End(kTraceCHROMIUM)) {
+    LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glTraceEndCHROMIUM",
+                       "no trace begin found");
+    return;
+  }
+}
+
 void RasterDecoderImpl::DoProduceTextureDirect(GLuint client_id,
                                                const volatile GLbyte* key) {
   TRACE_EVENT2("gpu", "RasterDecoderImpl::DoProduceTextureDirect", "context",
diff --git a/gpu/command_buffer/service/raster_decoder.h b/gpu/command_buffer/service/raster_decoder.h
index c2ce9cc5c..99eafe44 100644
--- a/gpu/command_buffer/service/raster_decoder.h
+++ b/gpu/command_buffer/service/raster_decoder.h
@@ -68,6 +68,7 @@
 
   // Set to true to LOG every command.
   void SetLogCommands(bool log_commands) override;
+  gles2::Outputter* outputter() const override;
   bool log_commands() const { return log_commands_; }
 
   virtual void SetCopyTextureResourceManagerForTest(
@@ -77,12 +78,14 @@
   virtual ServiceTransferCache* GetTransferCacheForTest() = 0;
 
  protected:
-  RasterDecoder(CommandBufferServiceBase* command_buffer_service);
+  RasterDecoder(CommandBufferServiceBase* command_buffer_service,
+                gles2::Outputter* outputter);
 
  private:
-  bool initialized_;
-  bool debug_;
-  bool log_commands_;
+  bool initialized_ = false;
+  bool debug_ = false;
+  bool log_commands_ = false;
+  gles2::Outputter* outputter_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(RasterDecoder);
 };
diff --git a/gpu/command_buffer/service/raster_decoder_autogen.h b/gpu/command_buffer/service/raster_decoder_autogen.h
index c090ea26..df594cf88 100644
--- a/gpu/command_buffer/service/raster_decoder_autogen.h
+++ b/gpu/command_buffer/service/raster_decoder_autogen.h
@@ -477,4 +477,11 @@
   return error::kNoError;
 }
 
+error::Error RasterDecoderImpl::HandleTraceEndCHROMIUM(
+    uint32_t immediate_data_size,
+    const volatile void* cmd_data) {
+  DoTraceEndCHROMIUM();
+  return error::kNoError;
+}
+
 #endif  // GPU_COMMAND_BUFFER_SERVICE_RASTER_DECODER_AUTOGEN_H_
diff --git a/gpu/command_buffer/service/raster_decoder_mock.cc b/gpu/command_buffer/service/raster_decoder_mock.cc
index ccef0fb..85a53cbc 100644
--- a/gpu/command_buffer/service/raster_decoder_mock.cc
+++ b/gpu/command_buffer/service/raster_decoder_mock.cc
@@ -11,7 +11,8 @@
 
 MockRasterDecoder::MockRasterDecoder(
     CommandBufferServiceBase* command_buffer_service)
-    : RasterDecoder(command_buffer_service), weak_ptr_factory_(this) {
+    : RasterDecoder(command_buffer_service, /*outputter=*/nullptr),
+      weak_ptr_factory_(this) {
   ON_CALL(*this, MakeCurrent()).WillByDefault(testing::Return(true));
 }
 
diff --git a/ios/chrome/browser/ui/infobars/confirm_infobar_view.mm b/ios/chrome/browser/ui/infobars/confirm_infobar_view.mm
index f9b5a03..2ab869f0 100644
--- a/ios/chrome/browser/ui/infobars/confirm_infobar_view.mm
+++ b/ios/chrome/browser/ui/infobars/confirm_infobar_view.mm
@@ -966,7 +966,8 @@
   if (IsRefreshInfobarEnabled()) {
     button.uppercaseTitle = NO;
     button.layer.cornerRadius = kButtonCornerRadius;
-    button.titleLabel.font = InfoBarButtonLabelFont();
+    [button setTitleFont:InfoBarButtonLabelFont()
+                forState:UIControlStateNormal];
   }
   button.inkColor = [[palette tint300] colorWithAlphaComponent:0.5f];
   [button setBackgroundColor:[palette tint500] forState:UIControlStateNormal];
diff --git a/media/audio/mac/core_audio_util_mac.cc b/media/audio/mac/core_audio_util_mac.cc
index 249ea67..3c050b4b 100644
--- a/media/audio/mac/core_audio_util_mac.cc
+++ b/media/audio/mac/core_audio_util_mac.cc
@@ -10,7 +10,6 @@
 #include "base/single_thread_task_runner.h"
 #include "base/strings/string_util.h"
 #include "base/strings/sys_string_conversions.h"
-#include "media/audio/audio_manager.h"
 
 namespace media {
 namespace core_audio_mac {
@@ -25,7 +24,6 @@
 base::Optional<std::string> GetDeviceStringProperty(
     AudioObjectID device_id,
     AudioObjectPropertySelector property_selector) {
-  DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
   CFStringRef property_value = nullptr;
   UInt32 size = sizeof(property_value);
   AudioObjectPropertyAddress property_address = {
@@ -55,7 +53,6 @@
     AudioObjectID device_id,
     AudioObjectPropertySelector property_selector,
     AudioObjectPropertyScope property_scope) {
-  DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
   AudioObjectPropertyAddress property_address = {
       property_selector, property_scope, kAudioObjectPropertyElementMaster};
   UInt32 property_value;
@@ -72,7 +69,6 @@
 uint32_t GetDevicePropertySize(AudioObjectID device_id,
                                AudioObjectPropertySelector property_selector,
                                AudioObjectPropertyScope property_scope) {
-  DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
   AudioObjectPropertyAddress property_address = {
       property_selector, property_scope, kAudioObjectPropertyElementMaster};
   UInt32 size = 0;
@@ -91,7 +87,6 @@
 std::vector<AudioObjectID> GetAudioDeviceIDs(
     AudioObjectID audio_object_id,
     AudioObjectPropertySelector property_selector) {
-  DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
   AudioObjectPropertyAddress property_address = {
       property_selector, kAudioObjectPropertyScopeGlobal,
       kAudioObjectPropertyElementMaster};
diff --git a/media/capture/BUILD.gn b/media/capture/BUILD.gn
index acdf83e..be5925b2 100644
--- a/media/capture/BUILD.gn
+++ b/media/capture/BUILD.gn
@@ -223,6 +223,8 @@
 
   if (is_chromeos) {
     sources += [
+      "video/chromeos/camera_3a_controller.cc",
+      "video/chromeos/camera_3a_controller.h",
       "video/chromeos/camera_buffer_factory.cc",
       "video/chromeos/camera_buffer_factory.h",
       "video/chromeos/camera_device_context.cc",
@@ -342,6 +344,7 @@
 
   if (is_chromeos) {
     sources += [
+      "video/chromeos/camera_3a_controller_unittest.cc",
       "video/chromeos/camera_device_delegate_unittest.cc",
       "video/chromeos/camera_hal_delegate_unittest.cc",
       "video/chromeos/camera_hal_dispatcher_impl_unittest.cc",
diff --git a/media/capture/video/chromeos/camera_3a_controller.cc b/media/capture/video/chromeos/camera_3a_controller.cc
new file mode 100644
index 0000000..0ff045e
--- /dev/null
+++ b/media/capture/video/chromeos/camera_3a_controller.cc
@@ -0,0 +1,327 @@
+// 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/camera_3a_controller.h"
+
+#include "media/capture/video/chromeos/camera_metadata_utils.h"
+
+namespace media {
+
+namespace {
+
+template <typename EntryType>
+bool Get3AEntry(const cros::mojom::CameraMetadataPtr& metadata,
+                cros::mojom::CameraMetadataTag control,
+                EntryType* result) {
+  const auto* entry = GetMetadataEntry(metadata, control);
+  if (entry) {
+    *result = static_cast<EntryType>((*entry)->data[0]);
+    return true;
+  } else {
+    return false;
+  }
+}
+
+}  // namespace
+
+Camera3AController::Camera3AController(
+    const cros::mojom::CameraMetadataPtr& static_metadata,
+    CaptureMetadataDispatcher* capture_metadata_dispatcher,
+    scoped_refptr<base::SingleThreadTaskRunner> task_runner)
+    : capture_metadata_dispatcher_(capture_metadata_dispatcher),
+      task_runner_(std::move(task_runner)),
+      af_mode_(cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_OFF),
+      af_state_(cros::mojom::AndroidControlAfState::
+                    ANDROID_CONTROL_AF_STATE_INACTIVE),
+      af_mode_set_(false),
+      ae_mode_(cros::mojom::AndroidControlAeMode::ANDROID_CONTROL_AE_MODE_ON),
+      ae_state_(cros::mojom::AndroidControlAeState::
+                    ANDROID_CONTROL_AE_STATE_INACTIVE),
+      ae_mode_set_(false),
+      awb_mode_(
+          cros::mojom::AndroidControlAwbMode::ANDROID_CONTROL_AWB_MODE_AUTO),
+      awb_state_(cros::mojom::AndroidControlAwbState::
+                     ANDROID_CONTROL_AWB_STATE_INACTIVE),
+      awb_mode_set_(false),
+      weak_ptr_factory_(this) {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+
+  capture_metadata_dispatcher_->AddResultMetadataObserver(this);
+
+  auto* af_modes = GetMetadataEntry(
+      static_metadata,
+      cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_AVAILABLE_MODES);
+  if (af_modes) {
+    for (const auto& m : (*af_modes)->data) {
+      available_af_modes_.insert(
+          static_cast<cros::mojom::AndroidControlAfMode>(m));
+    }
+  }
+  auto* ae_modes = GetMetadataEntry(
+      static_metadata,
+      cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_AVAILABLE_MODES);
+  if (ae_modes) {
+    for (const auto& m : (*ae_modes)->data) {
+      available_ae_modes_.insert(
+          static_cast<cros::mojom::AndroidControlAeMode>(m));
+    }
+  }
+  auto* awb_modes = GetMetadataEntry(
+      static_metadata,
+      cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_AVAILABLE_MODES);
+  if (awb_modes) {
+    for (const auto& m : (*awb_modes)->data) {
+      available_awb_modes_.insert(
+          static_cast<cros::mojom::AndroidControlAwbMode>(m));
+    }
+  }
+
+  // Enable AF if supported.  MODE_AUTO is always supported on auto-focus camera
+  // modules; fixed focus camera modules always has MODE_OFF.
+  if (available_af_modes_.count(
+          cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_AUTO)) {
+    af_mode_ = cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_AUTO;
+  }
+  // AE should always be MODE_ON unless we enable manual sensor control.  Since
+  // we don't have flash on any of our devices we don't care about the
+  // flash-related AE modes.
+  //
+  // AWB should always be MODE_AUTO unless we enable manual sensor control.
+  Set3AMode(cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE,
+            base::checked_cast<uint8_t>(af_mode_));
+  Set3AMode(cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_MODE,
+            base::checked_cast<uint8_t>(ae_mode_));
+  Set3AMode(cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_MODE,
+            base::checked_cast<uint8_t>(awb_mode_));
+}
+
+Camera3AController::~Camera3AController() {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+
+  capture_metadata_dispatcher_->RemoveResultMetadataObserver(this);
+}
+
+void Camera3AController::Stabilize3AForStillCapture(
+    base::OnceClosure on_3a_stabilized_callback) {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+
+  if (on_3a_stabilized_callback_ || on_3a_mode_set_callback_) {
+    // Already stabilizing 3A.
+    return;
+  }
+
+  if (Is3AStabilized()) {
+    std::move(on_3a_stabilized_callback).Run();
+    return;
+  }
+
+  // Wait until all the 3A modes are set in the HAL; otherwise the AF trigger
+  // and AE precapture trigger may be invalidated during mode transition.
+  if (!af_mode_set_ || !ae_mode_set_ || !awb_mode_set_) {
+    on_3a_mode_set_callback_ =
+        base::BindOnce(&Camera3AController::Stabilize3AForStillCapture,
+                       GetWeakPtr(), base::Passed(&on_3a_stabilized_callback));
+    return;
+  }
+
+  on_3a_stabilized_callback_ = std::move(on_3a_stabilized_callback);
+
+  if (af_mode_ !=
+      cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_OFF) {
+    DVLOG(1) << "Start AF trigger to lock focus";
+    std::vector<uint8_t> af_trigger = {
+        base::checked_cast<uint8_t>(cros::mojom::AndroidControlAfTrigger::
+                                        ANDROID_CONTROL_AF_TRIGGER_START)};
+    capture_metadata_dispatcher_->SetCaptureMetadata(
+        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_TRIGGER,
+        cros::mojom::EntryType::TYPE_BYTE, 1, std::move(af_trigger));
+  }
+
+  if (ae_mode_ !=
+      cros::mojom::AndroidControlAeMode::ANDROID_CONTROL_AE_MODE_OFF) {
+    DVLOG(1) << "Start AE precapture trigger to converge exposure";
+    std::vector<uint8_t> ae_precapture_trigger = {base::checked_cast<uint8_t>(
+        cros::mojom::AndroidControlAePrecaptureTrigger::
+            ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER_START)};
+    capture_metadata_dispatcher_->SetCaptureMetadata(
+        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER,
+        cros::mojom::EntryType::TYPE_BYTE, 1, std::move(ae_precapture_trigger));
+  }
+}
+
+void Camera3AController::OnResultMetadataAvailable(
+    const cros::mojom::CameraMetadataPtr& result_metadata) {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+
+  if (af_mode_set_ && ae_mode_set_ && awb_mode_set_ &&
+      !on_3a_stabilized_callback_) {
+    // Process the result metadata only when we need to check if 3A modes are
+    // synchronized, or when there's a pending 3A stabilization request.
+    return;
+  }
+
+  cros::mojom::AndroidControlAfMode af_mode;
+  if (Get3AEntry(result_metadata,
+                 cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE,
+                 &af_mode)) {
+    af_mode_set_ = (af_mode == af_mode_);
+  } else {
+    DVLOG(2) << "AF mode is not available in the metadata";
+  }
+  if (!Get3AEntry(result_metadata,
+                  cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_STATE,
+                  &af_state_)) {
+    DVLOG(2) << "AF state is not available in the metadata";
+  }
+
+  cros::mojom::AndroidControlAeMode ae_mode;
+  if (Get3AEntry(result_metadata,
+                 cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_MODE,
+                 &ae_mode)) {
+    ae_mode_set_ = (ae_mode == ae_mode_);
+  } else {
+    DVLOG(2) << "AE mode is not available in the metadata";
+  }
+  if (!Get3AEntry(result_metadata,
+                  cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_STATE,
+                  &ae_state_)) {
+    DVLOG(2) << "AE state is not available in the metadata";
+  }
+
+  cros::mojom::AndroidControlAwbMode awb_mode;
+  if (Get3AEntry(result_metadata,
+                 cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_MODE,
+                 &awb_mode)) {
+    awb_mode_set_ = (awb_mode == awb_mode_);
+  } else {
+    DVLOG(2) << "AWB mode is not available in the metadata";
+  }
+  if (!Get3AEntry(result_metadata,
+                  cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_STATE,
+                  &awb_state_)) {
+    DVLOG(2) << "AWB state is not available in the metadata";
+  }
+
+  DVLOG(2) << "AF mode: " << af_mode_;
+  DVLOG(2) << "AF state: " << af_state_;
+  DVLOG(2) << "AE mode: " << ae_mode_;
+  DVLOG(2) << "AE state: " << ae_state_;
+  DVLOG(2) << "AWB mode: " << awb_mode_;
+  DVLOG(2) << "AWB state: " << awb_state_;
+
+  if (on_3a_mode_set_callback_ && af_mode_set_ && ae_mode_set_ &&
+      awb_mode_set_) {
+    task_runner_->PostTask(FROM_HERE, std::move(on_3a_mode_set_callback_));
+  }
+
+  if (on_3a_stabilized_callback_ && Is3AStabilized()) {
+    std::move(on_3a_stabilized_callback_).Run();
+  }
+}
+
+void Camera3AController::SetAutoFocusModeForStillCapture() {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+
+  std::vector<uint8_t> af_trigger = {base::checked_cast<uint8_t>(
+      cros::mojom::AndroidControlAfTrigger::ANDROID_CONTROL_AF_TRIGGER_CANCEL)};
+  capture_metadata_dispatcher_->SetCaptureMetadata(
+      cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_TRIGGER,
+      cros::mojom::EntryType::TYPE_BYTE, 1, std::move(af_trigger));
+
+  if (available_af_modes_.count(
+          cros::mojom::AndroidControlAfMode::
+              ANDROID_CONTROL_AF_MODE_CONTINUOUS_PICTURE)) {
+    af_mode_ = cros::mojom::AndroidControlAfMode::
+        ANDROID_CONTROL_AF_MODE_CONTINUOUS_PICTURE;
+  }
+  std::vector<uint8_t> af_mode = {base::checked_cast<uint8_t>(af_mode_)};
+  Set3AMode(cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE,
+            base::checked_cast<uint8_t>(af_mode_));
+  DVLOG(1) << "Setting AF mode to: " << af_mode_;
+}
+
+void Camera3AController::SetAutoFocusModeForVideoRecording() {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+
+  std::vector<uint8_t> af_trigger = {base::checked_cast<uint8_t>(
+      cros::mojom::AndroidControlAfTrigger::ANDROID_CONTROL_AF_TRIGGER_CANCEL)};
+  capture_metadata_dispatcher_->SetCaptureMetadata(
+      cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_TRIGGER,
+      cros::mojom::EntryType::TYPE_BYTE, 1, std::move(af_trigger));
+
+  if (available_af_modes_.count(cros::mojom::AndroidControlAfMode::
+                                    ANDROID_CONTROL_AF_MODE_CONTINUOUS_VIDEO)) {
+    af_mode_ = cros::mojom::AndroidControlAfMode::
+        ANDROID_CONTROL_AF_MODE_CONTINUOUS_VIDEO;
+  }
+  Set3AMode(cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE,
+            base::checked_cast<uint8_t>(af_mode_));
+  DVLOG(1) << "Setting AF mode to: " << af_mode_;
+}
+
+base::WeakPtr<Camera3AController> Camera3AController::GetWeakPtr() {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+
+  return weak_ptr_factory_.GetWeakPtr();
+}
+
+void Camera3AController::Set3AMode(cros::mojom::CameraMetadataTag tag,
+                                   uint8_t target_mode) {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+  DCHECK(tag == cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE ||
+         tag == cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_MODE ||
+         tag == cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_MODE);
+
+  std::vector<uint8_t> mode = {base::checked_cast<uint8_t>(target_mode)};
+  capture_metadata_dispatcher_->SetCaptureMetadata(
+      tag, cros::mojom::EntryType::TYPE_BYTE, 1, std::move(mode));
+
+  switch (tag) {
+    case cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE:
+      af_mode_set_ = false;
+      break;
+    case cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_MODE:
+      ae_mode_set_ = false;
+      break;
+    case cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_MODE:
+      awb_mode_set_ = false;
+      break;
+    default:
+      NOTREACHED() << "Invalid 3A mode: " << tag;
+  }
+}
+
+bool Camera3AController::Is3AStabilized() {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+
+  if (af_mode_ !=
+      cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_OFF) {
+    if (af_state_ != cros::mojom::AndroidControlAfState::
+                         ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED &&
+        af_state_ != cros::mojom::AndroidControlAfState::
+                         ANDROID_CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
+      return false;
+    }
+  }
+  if (ae_mode_ !=
+      cros::mojom::AndroidControlAeMode::ANDROID_CONTROL_AE_MODE_OFF) {
+    if (ae_state_ != cros::mojom::AndroidControlAeState::
+                         ANDROID_CONTROL_AE_STATE_CONVERGED &&
+        ae_state_ != cros::mojom::AndroidControlAeState::
+                         ANDROID_CONTROL_AE_STATE_FLASH_REQUIRED) {
+      return false;
+    }
+  }
+  if (awb_mode_ ==
+      cros::mojom::AndroidControlAwbMode::ANDROID_CONTROL_AWB_MODE_AUTO) {
+    if (awb_state_ != cros::mojom::AndroidControlAwbState::
+                          ANDROID_CONTROL_AWB_STATE_CONVERGED) {
+      return false;
+    }
+  }
+  DVLOG(1) << "3A stabilized";
+  return true;
+}
+
+}  // namespace media
diff --git a/media/capture/video/chromeos/camera_3a_controller.h b/media/capture/video/chromeos/camera_3a_controller.h
new file mode 100644
index 0000000..c96b550a
--- /dev/null
+++ b/media/capture/video/chromeos/camera_3a_controller.h
@@ -0,0 +1,83 @@
+// 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_CAMERA_3A_CONTROLLER_H_
+#define MEDIA_CAPTURE_VIDEO_CHROMEOS_CAMERA_3A_CONTROLLER_H_
+
+#include <unordered_set>
+
+#include "media/base/media_export.h"
+#include "media/capture/video/chromeos/mojo/camera3.mojom.h"
+#include "media/capture/video/chromeos/stream_buffer_manager.h"
+
+namespace media {
+
+// A class to control the auto-exposure, auto-focus, and auto-white-balancing
+// operations and modes of the camera.  For the detailed state transitions for
+// auto-exposure, auto-focus, and auto-white-balancing, see
+// https://source.android.com/devices/camera/camera3_3Amodes
+class CAPTURE_EXPORT Camera3AController
+    : public CaptureMetadataDispatcher::ResultMetadataObserver {
+ public:
+  Camera3AController(const cros::mojom::CameraMetadataPtr& static_metadata,
+                     CaptureMetadataDispatcher* capture_metadata_dispatcher,
+                     scoped_refptr<base::SingleThreadTaskRunner> task_runner);
+  ~Camera3AController() final;
+
+  // Trigger the camera to start exposure, focus, and white-balance metering and
+  // lock them for still capture.
+  void Stabilize3AForStillCapture(base::OnceClosure on_3a_stabilized_callback);
+
+  // CaptureMetadataDispatcher::ResultMetadataObserver implementation.
+  void OnResultMetadataAvailable(
+      const cros::mojom::CameraMetadataPtr& result_metadata) final;
+
+  // Enable the auto-focus mode suitable for still capture.
+  void SetAutoFocusModeForStillCapture();
+
+  // Enable the auto-focus mode suitable for video recording.
+  void SetAutoFocusModeForVideoRecording();
+
+  base::WeakPtr<Camera3AController> GetWeakPtr();
+
+ private:
+  void Set3AMode(cros::mojom::CameraMetadataTag tag, uint8_t target_mode);
+  bool Is3AStabilized();
+
+  CaptureMetadataDispatcher* capture_metadata_dispatcher_;
+  const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+  std::unordered_set<cros::mojom::AndroidControlAfMode> available_af_modes_;
+  cros::mojom::AndroidControlAfMode af_mode_;
+  cros::mojom::AndroidControlAfState af_state_;
+  // |af_mode_set_| is set to true when the AF mode is synchronized between the
+  // HAL and the Camera3AController.
+  bool af_mode_set_;
+
+  std::unordered_set<cros::mojom::AndroidControlAeMode> available_ae_modes_;
+  cros::mojom::AndroidControlAeMode ae_mode_;
+  cros::mojom::AndroidControlAeState ae_state_;
+  // |ae_mode_set_| is set to true when the AE mode is synchronized between the
+  // HAL and the Camera3AController.
+  bool ae_mode_set_;
+
+  std::unordered_set<cros::mojom::AndroidControlAwbMode> available_awb_modes_;
+  cros::mojom::AndroidControlAwbMode awb_mode_;
+  cros::mojom::AndroidControlAwbState awb_state_;
+  // |awb_mode_set_| is set to true when the AWB mode is synchronized between
+  // the HAL and the Camera3AController.
+  bool awb_mode_set_;
+
+  base::OnceClosure on_3a_mode_set_callback_;
+
+  base::OnceClosure on_3a_stabilized_callback_;
+
+  base::WeakPtrFactory<Camera3AController> weak_ptr_factory_;
+
+  DISALLOW_IMPLICIT_CONSTRUCTORS(Camera3AController);
+};
+
+}  // namespace media
+
+#endif  // MEDIA_CAPTURE_VIDEO_CHROMEOS_CAMERA_3A_CONTROLLER_H_
diff --git a/media/capture/video/chromeos/camera_3a_controller_unittest.cc b/media/capture/video/chromeos/camera_3a_controller_unittest.cc
new file mode 100644
index 0000000..070e0fe1
--- /dev/null
+++ b/media/capture/video/chromeos/camera_3a_controller_unittest.cc
@@ -0,0 +1,502 @@
+// 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/camera_3a_controller.h"
+
+#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 "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+
+namespace media {
+
+namespace {
+
+class MockCaptureMetadataDispatcher : public CaptureMetadataDispatcher {
+ public:
+  MockCaptureMetadataDispatcher() {}
+  ~MockCaptureMetadataDispatcher() override {}
+  MOCK_METHOD1(
+      AddResultMetadataObserver,
+      void(CaptureMetadataDispatcher::ResultMetadataObserver* observer));
+  MOCK_METHOD1(
+      RemoveResultMetadataObserver,
+      void(CaptureMetadataDispatcher::ResultMetadataObserver* observer));
+  MOCK_METHOD4(SetCaptureMetadata,
+               void(cros::mojom::CameraMetadataTag tag,
+                    cros::mojom::EntryType type,
+                    size_t count,
+                    std::vector<uint8_t> value));
+};
+
+}  // namespace
+
+class Camera3AControllerTest : public ::testing::Test {
+ public:
+  Camera3AControllerTest() : thread_("Camera3AControllerThread") {}
+
+  void SetUp() override {
+    thread_.Start();
+    mock_capture_metadata_dispatcher_ =
+        std::make_unique<MockCaptureMetadataDispatcher>();
+  }
+
+  void TearDown() override {
+    thread_.task_runner()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&Camera3AControllerTest::Clear3AControllerOnThread,
+                       base::Unretained(this)));
+    thread_.Stop();
+    mock_capture_metadata_dispatcher_.reset();
+  }
+
+  void RunOnThreadSync(const base::Location& location,
+                       base::OnceClosure closure) {
+    base::WaitableEvent done(base::WaitableEvent::ResetPolicy::MANUAL,
+                             base::WaitableEvent::InitialState::NOT_SIGNALED);
+    thread_.task_runner()->PostTask(
+        location,
+        base::BindOnce(&Camera3AControllerTest::RunOnThread,
+                       base::Unretained(this), base::ConstRef(location),
+                       base::Passed(&closure), base::Unretained(&done)));
+    done.Wait();
+  }
+
+  void Reset3AController(
+      const cros::mojom::CameraMetadataPtr& static_metadata) {
+    RunOnThreadSync(
+        FROM_HERE,
+        base::BindOnce(&Camera3AControllerTest::Reset3AControllerOnThread,
+                       base::Unretained(this),
+                       base::ConstRef(static_metadata)));
+  }
+
+  template <typename Value>
+  void Set3AMode(cros::mojom::CameraMetadataPtr* metadata,
+                 cros::mojom::CameraMetadataTag control,
+                 Value value,
+                 bool append = false) {
+    auto* e = GetMetadataEntry(*metadata, control);
+    if (e) {
+      if (append) {
+        (*e)->count++;
+        (*e)->data.push_back(base::checked_cast<uint8_t>(value));
+      } else {
+        (*e)->count = 1;
+        (*e)->data = {base::checked_cast<uint8_t>(value)};
+      }
+    } else {
+      cros::mojom::CameraMetadataEntryPtr entry =
+          cros::mojom::CameraMetadataEntry::New();
+      entry->index = (*metadata)->entries.value().size();
+      entry->tag = control;
+      entry->type = cros::mojom::EntryType::TYPE_BYTE;
+      entry->count = 1;
+      entry->data = {base::checked_cast<uint8_t>(value)};
+
+      (*metadata)->entries.value().push_back(std::move(entry));
+      (*metadata)->entry_count++;
+      (*metadata)->entry_capacity++;
+    }
+    SortCameraMetadata(metadata);
+  }
+
+  cros::mojom::CameraMetadataPtr CreateDefaultFakeStaticMetadata() {
+    auto metadata = cros::mojom::CameraMetadata::New();
+    metadata->entries = std::vector<cros::mojom::CameraMetadataEntryPtr>();
+    metadata->entry_count = 0;
+    metadata->entry_capacity = 0;
+
+    // Set the available AF modes.
+    Set3AMode(
+        &metadata,
+        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_AVAILABLE_MODES,
+        cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_OFF);
+    Set3AMode(
+        &metadata,
+        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_AVAILABLE_MODES,
+        cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_AUTO,
+        /* append */ true);
+    Set3AMode(
+        &metadata,
+        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_AVAILABLE_MODES,
+        cros::mojom::AndroidControlAfMode::
+            ANDROID_CONTROL_AF_MODE_CONTINUOUS_PICTURE,
+        /* append */ true);
+    Set3AMode(
+        &metadata,
+        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_AVAILABLE_MODES,
+        cros::mojom::AndroidControlAfMode::
+            ANDROID_CONTROL_AF_MODE_CONTINUOUS_VIDEO,
+        /* append */ true);
+
+    // Set the available AE modes.
+    Set3AMode(
+        &metadata,
+        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_AVAILABLE_MODES,
+        cros::mojom::AndroidControlAeMode::ANDROID_CONTROL_AE_MODE_OFF);
+    Set3AMode(
+        &metadata,
+        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_AVAILABLE_MODES,
+        cros::mojom::AndroidControlAeMode::ANDROID_CONTROL_AE_MODE_ON,
+        /* append */ true);
+
+    // Set the available AWB modes.
+    Set3AMode(
+        &metadata,
+        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_AVAILABLE_MODES,
+        cros::mojom::AndroidControlAwbMode::ANDROID_CONTROL_AWB_MODE_OFF);
+    Set3AMode(
+        &metadata,
+        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_AVAILABLE_MODES,
+        cros::mojom::AndroidControlAwbMode::ANDROID_CONTROL_AWB_MODE_AUTO,
+        /* append */ true);
+
+    return metadata;
+  }
+
+  void On3AStabilizedCallback(base::WaitableEvent* done) { done->Signal(); }
+
+ protected:
+  base::Thread thread_;
+  std::unique_ptr<MockCaptureMetadataDispatcher>
+      mock_capture_metadata_dispatcher_;
+  std::unique_ptr<Camera3AController> camera_3a_controller_;
+
+ private:
+  void RunOnThread(const base::Location& location,
+                   base::OnceClosure closure,
+                   base::WaitableEvent* done) {
+    DCHECK(thread_.task_runner()->BelongsToCurrentThread());
+
+    std::move(closure).Run();
+    done->Signal();
+  }
+
+  void Clear3AControllerOnThread() {
+    DCHECK(thread_.task_runner()->BelongsToCurrentThread());
+
+    if (camera_3a_controller_) {
+      EXPECT_CALL(*mock_capture_metadata_dispatcher_,
+                  RemoveResultMetadataObserver(camera_3a_controller_.get()))
+          .Times(1);
+    }
+    camera_3a_controller_.reset();
+  }
+
+  void Reset3AControllerOnThread(
+      const cros::mojom::CameraMetadataPtr& static_metadata) {
+    DCHECK(thread_.task_runner()->BelongsToCurrentThread());
+
+    Clear3AControllerOnThread();
+    EXPECT_CALL(*mock_capture_metadata_dispatcher_,
+                AddResultMetadataObserver(_))
+        .Times(1);
+    EXPECT_CALL(*mock_capture_metadata_dispatcher_,
+                SetCaptureMetadata(
+                    cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE,
+                    cros::mojom::EntryType::TYPE_BYTE, 1, _))
+        .Times(1);
+    EXPECT_CALL(*mock_capture_metadata_dispatcher_,
+                SetCaptureMetadata(
+                    cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_MODE,
+                    cros::mojom::EntryType::TYPE_BYTE, 1, _))
+        .Times(1);
+    EXPECT_CALL(*mock_capture_metadata_dispatcher_,
+                SetCaptureMetadata(
+                    cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_MODE,
+                    cros::mojom::EntryType::TYPE_BYTE, 1, _))
+        .Times(1);
+    camera_3a_controller_ = std::make_unique<Camera3AController>(
+        static_metadata, mock_capture_metadata_dispatcher_.get(),
+        thread_.task_runner());
+  }
+};
+
+TEST_F(Camera3AControllerTest, Stabilize3AForStillCaptureTest) {
+  Reset3AController(CreateDefaultFakeStaticMetadata());
+
+  // Set AF mode.
+  std::vector<uint8_t> af_trigger_start, af_trigger_cancel, af_mode, ae_trigger;
+  af_trigger_start = {base::checked_cast<uint8_t>(
+      cros::mojom::AndroidControlAfTrigger::ANDROID_CONTROL_AF_TRIGGER_START)};
+  af_trigger_cancel = {base::checked_cast<uint8_t>(
+      cros::mojom::AndroidControlAfTrigger::ANDROID_CONTROL_AF_TRIGGER_CANCEL)};
+  af_mode = {base::checked_cast<uint8_t>(
+      cros::mojom::AndroidControlAfMode::
+          ANDROID_CONTROL_AF_MODE_CONTINUOUS_PICTURE)};
+  ae_trigger = {base::checked_cast<uint8_t>(
+      cros::mojom::AndroidControlAePrecaptureTrigger::
+          ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER_START)};
+
+  EXPECT_CALL(*mock_capture_metadata_dispatcher_,
+              SetCaptureMetadata(
+                  cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_TRIGGER,
+                  cros::mojom::EntryType::TYPE_BYTE, 1, af_trigger_cancel))
+      .Times(1);
+  EXPECT_CALL(*mock_capture_metadata_dispatcher_,
+              SetCaptureMetadata(
+                  cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE,
+                  cros::mojom::EntryType::TYPE_BYTE, 1, af_mode))
+      .Times(1);
+  RunOnThreadSync(
+      FROM_HERE,
+      base::BindOnce(&Camera3AController::SetAutoFocusModeForStillCapture,
+                     base::Unretained(camera_3a_controller_.get())));
+
+  // |camera_3a_controller_| should wait until the AF mode is set
+  // before setting the AF and AE precapture triggers.
+  EXPECT_CALL(*mock_capture_metadata_dispatcher_,
+              SetCaptureMetadata(
+                  cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_TRIGGER,
+                  cros::mojom::EntryType::TYPE_BYTE, 1, af_trigger_start))
+      .Times(0);
+  EXPECT_CALL(
+      *mock_capture_metadata_dispatcher_,
+      SetCaptureMetadata(
+          cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER,
+          cros::mojom::EntryType::TYPE_BYTE, 1, ae_trigger))
+      .Times(0);
+  base::WaitableEvent done(base::WaitableEvent::ResetPolicy::MANUAL,
+                           base::WaitableEvent::InitialState::NOT_SIGNALED);
+  RunOnThreadSync(
+      FROM_HERE,
+      base::BindOnce(
+          &Camera3AController::Stabilize3AForStillCapture,
+          base::Unretained(camera_3a_controller_.get()),
+          base::BindOnce(&Camera3AControllerTest::On3AStabilizedCallback,
+                         base::Unretained(this), &done)));
+  testing::Mock::VerifyAndClearExpectations(camera_3a_controller_.get());
+
+  // |camera_3a_controller_| should set the AF and AE precapture triggers once
+  // the 3A modes are set.
+  auto result_metadata = CreateDefaultFakeStaticMetadata();
+  Set3AMode(&result_metadata,
+            cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE,
+            cros::mojom::AndroidControlAfMode::
+                ANDROID_CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+  Set3AMode(
+      &result_metadata,
+      cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_STATE,
+      cros::mojom::AndroidControlAfState::ANDROID_CONTROL_AF_STATE_INACTIVE);
+  Set3AMode(&result_metadata,
+            cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_MODE,
+            cros::mojom::AndroidControlAeMode::ANDROID_CONTROL_AE_MODE_ON);
+  Set3AMode(
+      &result_metadata,
+      cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_STATE,
+      cros::mojom::AndroidControlAeState::ANDROID_CONTROL_AE_STATE_INACTIVE);
+  Set3AMode(&result_metadata,
+            cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_MODE,
+            cros::mojom::AndroidControlAwbMode::ANDROID_CONTROL_AWB_MODE_AUTO);
+  Set3AMode(
+      &result_metadata,
+      cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_STATE,
+      cros::mojom::AndroidControlAwbState::ANDROID_CONTROL_AWB_STATE_INACTIVE);
+  EXPECT_CALL(*mock_capture_metadata_dispatcher_,
+              SetCaptureMetadata(
+                  cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_TRIGGER,
+                  cros::mojom::EntryType::TYPE_BYTE, 1, af_trigger_start))
+      .Times(1);
+  EXPECT_CALL(
+      *mock_capture_metadata_dispatcher_,
+      SetCaptureMetadata(
+          cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER,
+          cros::mojom::EntryType::TYPE_BYTE, 1, ae_trigger))
+      .Times(1);
+  RunOnThreadSync(FROM_HERE,
+                  base::BindOnce(&Camera3AController::OnResultMetadataAvailable,
+                                 base::Unretained(camera_3a_controller_.get()),
+                                 base::ConstRef(result_metadata)));
+
+  // |camera_3a_controller_| should call the registered callback once 3A are
+  // stabilized.
+  Set3AMode(&result_metadata,
+            cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_STATE,
+            cros::mojom::AndroidControlAfState::
+                ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED);
+  Set3AMode(
+      &result_metadata,
+      cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_STATE,
+      cros::mojom::AndroidControlAeState::ANDROID_CONTROL_AE_STATE_CONVERGED);
+  Set3AMode(
+      &result_metadata,
+      cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_STATE,
+      cros::mojom::AndroidControlAwbState::ANDROID_CONTROL_AWB_STATE_CONVERGED);
+  RunOnThreadSync(FROM_HERE,
+                  base::BindOnce(&Camera3AController::OnResultMetadataAvailable,
+                                 base::Unretained(camera_3a_controller_.get()),
+                                 base::ConstRef(result_metadata)));
+  done.Wait();
+}
+
+// Test that SetAutoFocusModeForStillCapture sets the right auto-focus mode on
+// cameras with different capabilities.
+TEST_F(Camera3AControllerTest, SetAutoFocusModeForStillCaptureTest) {
+  auto static_metadata = CreateDefaultFakeStaticMetadata();
+  std::vector<uint8_t> af_mode;
+  std::vector<uint8_t> af_trigger = {base::checked_cast<uint8_t>(
+      cros::mojom::AndroidControlAfTrigger::ANDROID_CONTROL_AF_TRIGGER_CANCEL)};
+
+  // For camera that supports continuous auto-focus for picture mode.
+  Reset3AController(static_metadata);
+  af_mode = {base::checked_cast<uint8_t>(
+      cros::mojom::AndroidControlAfMode::
+          ANDROID_CONTROL_AF_MODE_CONTINUOUS_PICTURE)};
+  EXPECT_CALL(*mock_capture_metadata_dispatcher_,
+              SetCaptureMetadata(
+                  cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_TRIGGER,
+                  cros::mojom::EntryType::TYPE_BYTE, 1, af_trigger))
+      .Times(1);
+  EXPECT_CALL(*mock_capture_metadata_dispatcher_,
+              SetCaptureMetadata(
+                  cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE,
+                  cros::mojom::EntryType::TYPE_BYTE, 1, af_mode))
+      .Times(1);
+  RunOnThreadSync(
+      FROM_HERE,
+      base::BindOnce(&Camera3AController::SetAutoFocusModeForStillCapture,
+                     base::Unretained(camera_3a_controller_.get())));
+  testing::Mock::VerifyAndClearExpectations(camera_3a_controller_.get());
+
+  // For camera that only supports basic auto focus.
+  Set3AMode(&static_metadata,
+            cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_AVAILABLE_MODES,
+            cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_OFF);
+  Set3AMode(&static_metadata,
+            cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_AVAILABLE_MODES,
+            cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_AUTO,
+            /* append */ true);
+  Reset3AController(static_metadata);
+  af_mode.clear();
+  af_mode = {base::checked_cast<uint8_t>(
+      cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_AUTO)};
+  EXPECT_CALL(*mock_capture_metadata_dispatcher_,
+              SetCaptureMetadata(
+                  cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_TRIGGER,
+                  cros::mojom::EntryType::TYPE_BYTE, 1, af_trigger))
+      .Times(1);
+  EXPECT_CALL(*mock_capture_metadata_dispatcher_,
+              SetCaptureMetadata(
+                  cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE,
+                  cros::mojom::EntryType::TYPE_BYTE, 1, af_mode))
+      .Times(1);
+  RunOnThreadSync(
+      FROM_HERE,
+      base::BindOnce(&Camera3AController::SetAutoFocusModeForStillCapture,
+                     base::Unretained(camera_3a_controller_.get())));
+  testing::Mock::VerifyAndClearExpectations(camera_3a_controller_.get());
+
+  // For camera that is fixed-focus.
+  Set3AMode(&static_metadata,
+            cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_AVAILABLE_MODES,
+            cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_OFF);
+  Reset3AController(static_metadata);
+  af_mode.clear();
+  af_mode = {base::checked_cast<uint8_t>(
+      cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_OFF)};
+  EXPECT_CALL(*mock_capture_metadata_dispatcher_,
+              SetCaptureMetadata(
+                  cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_TRIGGER,
+                  cros::mojom::EntryType::TYPE_BYTE, 1, af_trigger))
+      .Times(1);
+  EXPECT_CALL(*mock_capture_metadata_dispatcher_,
+              SetCaptureMetadata(
+                  cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE,
+                  cros::mojom::EntryType::TYPE_BYTE, 1, af_mode))
+      .Times(1);
+  RunOnThreadSync(
+      FROM_HERE,
+      base::BindOnce(&Camera3AController::SetAutoFocusModeForStillCapture,
+                     base::Unretained(camera_3a_controller_.get())));
+  testing::Mock::VerifyAndClearExpectations(camera_3a_controller_.get());
+}
+
+// Test that SetAutoFocusModeForVideoRecording sets the right auto-focus mode on
+// cameras with different capabilities.
+TEST_F(Camera3AControllerTest, SetAutoFocusModeForVideoRecordingTest) {
+  auto static_metadata = CreateDefaultFakeStaticMetadata();
+  std::vector<uint8_t> af_mode;
+  std::vector<uint8_t> af_trigger = {base::checked_cast<uint8_t>(
+      cros::mojom::AndroidControlAfTrigger::ANDROID_CONTROL_AF_TRIGGER_CANCEL)};
+
+  // For camera that supports continuous auto-focus for picture mode.
+  Reset3AController(static_metadata);
+  af_mode = {base::checked_cast<uint8_t>(
+      cros::mojom::AndroidControlAfMode::
+          ANDROID_CONTROL_AF_MODE_CONTINUOUS_VIDEO)};
+  EXPECT_CALL(*mock_capture_metadata_dispatcher_,
+              SetCaptureMetadata(
+                  cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_TRIGGER,
+                  cros::mojom::EntryType::TYPE_BYTE, 1, af_trigger))
+      .Times(1);
+  EXPECT_CALL(*mock_capture_metadata_dispatcher_,
+              SetCaptureMetadata(
+                  cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE,
+                  cros::mojom::EntryType::TYPE_BYTE, 1, af_mode))
+      .Times(1);
+  RunOnThreadSync(
+      FROM_HERE,
+      base::BindOnce(&Camera3AController::SetAutoFocusModeForVideoRecording,
+                     base::Unretained(camera_3a_controller_.get())));
+  testing::Mock::VerifyAndClearExpectations(camera_3a_controller_.get());
+
+  // For camera that only supports basic auto focus.
+  Set3AMode(&static_metadata,
+            cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_AVAILABLE_MODES,
+            cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_OFF);
+  Set3AMode(&static_metadata,
+            cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_AVAILABLE_MODES,
+            cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_AUTO,
+            /* append */ true);
+  Reset3AController(static_metadata);
+  af_mode.clear();
+  af_mode = {base::checked_cast<uint8_t>(
+      cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_AUTO)};
+  EXPECT_CALL(*mock_capture_metadata_dispatcher_,
+              SetCaptureMetadata(
+                  cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_TRIGGER,
+                  cros::mojom::EntryType::TYPE_BYTE, 1, af_trigger))
+      .Times(1);
+  EXPECT_CALL(*mock_capture_metadata_dispatcher_,
+              SetCaptureMetadata(
+                  cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE,
+                  cros::mojom::EntryType::TYPE_BYTE, 1, af_mode))
+      .Times(1);
+  RunOnThreadSync(
+      FROM_HERE,
+      base::BindOnce(&Camera3AController::SetAutoFocusModeForVideoRecording,
+                     base::Unretained(camera_3a_controller_.get())));
+  testing::Mock::VerifyAndClearExpectations(camera_3a_controller_.get());
+
+  // For camera that is fixed-focus.
+  Set3AMode(&static_metadata,
+            cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_AVAILABLE_MODES,
+            cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_OFF);
+  Reset3AController(static_metadata);
+  af_mode.clear();
+  af_mode = {base::checked_cast<uint8_t>(
+      cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_OFF)};
+  EXPECT_CALL(*mock_capture_metadata_dispatcher_,
+              SetCaptureMetadata(
+                  cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_TRIGGER,
+                  cros::mojom::EntryType::TYPE_BYTE, 1, af_trigger))
+      .Times(1);
+  EXPECT_CALL(*mock_capture_metadata_dispatcher_,
+              SetCaptureMetadata(
+                  cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE,
+                  cros::mojom::EntryType::TYPE_BYTE, 1, af_mode))
+      .Times(1);
+  RunOnThreadSync(
+      FROM_HERE,
+      base::BindOnce(&Camera3AController::SetAutoFocusModeForVideoRecording,
+                     base::Unretained(camera_3a_controller_.get())));
+  testing::Mock::VerifyAndClearExpectations(camera_3a_controller_.get());
+}
+
+}  // namespace media
diff --git a/media/capture/video/chromeos/camera_buffer_factory.cc b/media/capture/video/chromeos/camera_buffer_factory.cc
index 02df0b66..7cdd186 100644
--- a/media/capture/video/chromeos/camera_buffer_factory.cc
+++ b/media/capture/video/chromeos/camera_buffer_factory.cc
@@ -21,9 +21,12 @@
     LOG(ERROR) << "GpuMemoryBufferManager not set";
     return std::unique_ptr<gfx::GpuMemoryBuffer>();
   }
-  return buf_manager->CreateGpuMemoryBuffer(
-      size, format, gfx::BufferUsage::SCANOUT_CAMERA_READ_WRITE,
-      gpu::kNullSurfaceHandle);
+  gfx::BufferUsage buffer_usage = gfx::BufferUsage::SCANOUT_CAMERA_READ_WRITE;
+  if (format == gfx::BufferFormat::R_8) {
+    buffer_usage = gfx::BufferUsage::CAMERA_AND_CPU_READ_WRITE;
+  }
+  return buf_manager->CreateGpuMemoryBuffer(size, format, buffer_usage,
+                                            gpu::kNullSurfaceHandle);
 }
 
 // There's no good way to resolve the HAL pixel format to the platform-specific
diff --git a/media/capture/video/chromeos/camera_device_context.h b/media/capture/video/chromeos/camera_device_context.h
index aaeedc7..a114ffe 100644
--- a/media/capture/video/chromeos/camera_device_context.h
+++ b/media/capture/video/chromeos/camera_device_context.h
@@ -56,7 +56,7 @@
     //
     //   ConstructDefaultRequestSettings() ->
     //   OnConstructedDefaultRequestSettings() ->
-    //   |stream_buffer_manager_|->StartCapture()
+    //   |stream_buffer_manager_|->StartPreview()
     //
     // In the kCapturing state the |stream_buffer_manager_| runs the capture
     // loop to send capture requests and process capture results.
diff --git a/media/capture/video/chromeos/camera_device_delegate.cc b/media/capture/video/chromeos/camera_device_delegate.cc
index b668249..d4bb290f 100644
--- a/media/capture/video/chromeos/camera_device_delegate.cc
+++ b/media/capture/video/chromeos/camera_device_delegate.cc
@@ -10,6 +10,9 @@
 #include <vector>
 
 #include "media/base/bind_to_current_loop.h"
+#include "media/capture/mojom/image_capture_types.h"
+#include "media/capture/video/blob_utils.h"
+#include "media/capture/video/chromeos/camera_3a_controller.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_hal_delegate.h"
@@ -20,6 +23,81 @@
 
 namespace media {
 
+namespace {
+
+void GetMaxBlobStreamResolution(
+    const cros::mojom::CameraMetadataPtr& static_metadata,
+    int32_t* max_blob_width,
+    int32_t* max_blob_height) {
+  const cros::mojom::CameraMetadataEntryPtr* stream_configurations =
+      GetMetadataEntry(static_metadata,
+                       cros::mojom::CameraMetadataTag::
+                           ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS);
+  DCHECK(stream_configurations);
+  // The available stream configurations are stored as tuples of four int32s:
+  // (hal_pixel_format, width, height, type) x n
+  const size_t kStreamFormatOffset = 0;
+  const size_t kStreamWidthOffset = 1;
+  const size_t kStreamHeightOffset = 2;
+  const size_t kStreamTypeOffset = 3;
+  const size_t kStreamConfigurationSize = 4;
+  int32_t* iter =
+      reinterpret_cast<int32_t*>((*stream_configurations)->data.data());
+  *max_blob_width = 0;
+  *max_blob_height = 0;
+  for (size_t i = 0; i < (*stream_configurations)->count;
+       i += kStreamConfigurationSize) {
+    auto format =
+        static_cast<cros::mojom::HalPixelFormat>(iter[kStreamFormatOffset]);
+    int32_t width = iter[kStreamWidthOffset];
+    int32_t height = iter[kStreamHeightOffset];
+    auto type =
+        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) {
+      continue;
+    }
+    if (width > *max_blob_width && height > *max_blob_height) {
+      *max_blob_width = width;
+      *max_blob_height = height;
+    }
+  }
+  DCHECK_GT(*max_blob_width, 0);
+  DCHECK_GT(*max_blob_height, 0);
+}
+
+// VideoCaptureDevice::TakePhotoCallback is given by the application and is used
+// to return the captured JPEG blob buffer.  The second base::OnceClosure is
+// created locally by the caller of TakePhoto(), and can be used to, for
+// exmaple, restore some settings to the values before TakePhoto() is called to
+// facilitate the switch between photo and non-photo modes.
+void TakePhotoCallbackBundle(VideoCaptureDevice::TakePhotoCallback callback,
+                             base::OnceClosure on_photo_taken_callback,
+                             mojom::BlobPtr blob) {
+  std::move(callback).Run(std::move(blob));
+  std::move(on_photo_taken_callback).Run();
+}
+
+}  // namespace
+
+std::string StreamTypeToString(StreamType stream_type) {
+  switch (stream_type) {
+    case StreamType::kPreview:
+      return std::string("StreamType::kPreview");
+    case StreamType::kStillCapture:
+      return std::string("StreamType::kStillCapture");
+    default:
+      return std::string("Unknown StreamType value: ") +
+             std::to_string(static_cast<int32_t>(stream_type));
+  }
+}  // namespace media
+
+std::ostream& operator<<(std::ostream& os, StreamType stream_type) {
+  return os << StreamTypeToString(stream_type);
+}
+
 StreamCaptureInterface::Plane::Plane() = default;
 
 StreamCaptureInterface::Plane::~Plane() = default;
@@ -110,7 +188,7 @@
     // The device delegate is in the process of opening the camera device.
     return;
   }
-  stream_buffer_manager_->StopCapture();
+  stream_buffer_manager_->StopPreview();
   device_ops_->Close(
       base::BindOnce(&CameraDeviceDelegate::OnClosed, GetWeakPtr()));
 }
@@ -118,23 +196,57 @@
 void CameraDeviceDelegate::TakePhoto(
     VideoCaptureDevice::TakePhotoCallback callback) {
   DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-  // TODO(jcliang): Implement TakePhoto.
-  NOTIMPLEMENTED() << "TakePhoto is not implemented";
+
+  take_photo_callbacks_.push(std::move(callback));
+
+  if (!device_context_ ||
+      (device_context_->GetState() !=
+           CameraDeviceContext::State::kStreamConfigured &&
+       device_context_->GetState() != CameraDeviceContext::State::kCapturing)) {
+    return;
+  }
+
+  camera_3a_controller_->Stabilize3AForStillCapture(
+      base::BindOnce(&CameraDeviceDelegate::ConstructDefaultRequestSettings,
+                     GetWeakPtr(), StreamType::kStillCapture));
 }
 
 void CameraDeviceDelegate::GetPhotoState(
     VideoCaptureDevice::GetPhotoStateCallback callback) {
   DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-  // TODO(jcliang): Implement GetPhotoState.
-  NOTIMPLEMENTED() << "GetPhotoState is not implemented";
+
+  auto photo_state = mojo::CreateEmptyPhotoState();
+
+  if (!device_context_ ||
+      (device_context_->GetState() !=
+           CameraDeviceContext::State::kStreamConfigured &&
+       device_context_->GetState() != CameraDeviceContext::State::kCapturing)) {
+    std::move(callback).Run(std::move(photo_state));
+    return;
+  }
+
+  auto stream_config =
+      stream_buffer_manager_->GetStreamConfiguration(StreamType::kStillCapture);
+  if (stream_config) {
+    photo_state->width->current = stream_config->width;
+    photo_state->width->min = stream_config->width;
+    photo_state->width->max = stream_config->width;
+    photo_state->width->step = 0.0;
+    photo_state->height->current = stream_config->height;
+    photo_state->height->min = stream_config->height;
+    photo_state->height->max = stream_config->height;
+    photo_state->height->step = 0.0;
+  }
+  std::move(callback).Run(std::move(photo_state));
 }
 
 void CameraDeviceDelegate::SetPhotoOptions(
     mojom::PhotoSettingsPtr settings,
     VideoCaptureDevice::SetPhotoOptionsCallback callback) {
   DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-  // TODO(jcliang): Implement SetPhotoOptions.
-  NOTIMPLEMENTED() << "SetPhotoOptions is not implemented";
+
+  // Not supported at the moment.
+  std::move(callback).Run(true);
 }
 
 void CameraDeviceDelegate::SetRotation(int rotation) {
@@ -158,7 +270,7 @@
   } else {
     // The Mojo channel terminated unexpectedly.
     if (stream_buffer_manager_) {
-      stream_buffer_manager_->StopCapture();
+      stream_buffer_manager_->StopPreview();
     }
     device_context_->SetState(CameraDeviceContext::State::kStopped);
     device_context_->SetErrorState(FROM_HERE, "Mojo connection error");
@@ -187,6 +299,7 @@
   DCHECK(ipc_task_runner_->BelongsToCurrentThread());
 
   device_ops_.reset();
+  camera_3a_controller_.reset();
   stream_buffer_manager_.reset();
 }
 
@@ -206,6 +319,7 @@
     device_context_->SetErrorState(FROM_HERE, "Failed to get camera info");
     return;
   }
+  SortCameraMetadata(&camera_info->static_camera_characteristics);
   static_metadata_ = std::move(camera_info->static_camera_characteristics);
 
   const cros::mojom::CameraMetadataEntryPtr* sensor_orientation =
@@ -265,7 +379,9 @@
       std::move(callback_ops_request),
       std::make_unique<StreamCaptureInterfaceImpl>(GetWeakPtr()),
       device_context_, std::make_unique<CameraBufferFactory>(),
-      ipc_task_runner_);
+      base::BindRepeating(&Blobify), ipc_task_runner_);
+  camera_3a_controller_ = std::make_unique<Camera3AController>(
+      static_metadata_, stream_buffer_manager_.get(), ipc_task_runner_);
   device_ops_->Initialize(
       std::move(callback_ops_ptr),
       base::BindOnce(&CameraDeviceDelegate::OnInitialized, GetWeakPtr()));
@@ -297,8 +413,7 @@
   // Set up context for preview stream.
   cros::mojom::Camera3StreamPtr preview_stream =
       cros::mojom::Camera3Stream::New();
-  preview_stream->id = static_cast<uint64_t>(
-      cros::mojom::Camera3RequestTemplate::CAMERA3_TEMPLATE_PREVIEW);
+  preview_stream->id = static_cast<uint64_t>(StreamType::kPreview);
   preview_stream->stream_type =
       cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT;
   preview_stream->width =
@@ -311,9 +426,31 @@
   preview_stream->rotation =
       cros::mojom::Camera3StreamRotation::CAMERA3_STREAM_ROTATION_0;
 
+  // Set up context for still capture stream. We set still capture stream to the
+  // JPEG stream configuration with maximum supported resolution.
+  // TODO(jcliang): Once we support SetPhotoOptions() the still capture stream
+  // should be configured dynamically per the photo options.
+  int32_t max_blob_width = 0, max_blob_height = 0;
+  GetMaxBlobStreamResolution(static_metadata_, &max_blob_width,
+                             &max_blob_height);
+
+  cros::mojom::Camera3StreamPtr still_capture_stream =
+      cros::mojom::Camera3Stream::New();
+  still_capture_stream->id = static_cast<uint64_t>(StreamType::kStillCapture);
+  still_capture_stream->stream_type =
+      cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT;
+  still_capture_stream->width = max_blob_width;
+  still_capture_stream->height = max_blob_height;
+  still_capture_stream->format =
+      cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_BLOB;
+  still_capture_stream->data_space = 0;
+  still_capture_stream->rotation =
+      cros::mojom::Camera3StreamRotation::CAMERA3_STREAM_ROTATION_0;
+
   cros::mojom::Camera3StreamConfigurationPtr stream_config =
       cros::mojom::Camera3StreamConfiguration::New();
   stream_config->streams.push_back(std::move(preview_stream));
+  stream_config->streams.push_back(std::move(still_capture_stream));
   stream_config->operation_mode = cros::mojom::Camera3StreamConfigurationMode::
       CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE;
   device_ops_->ConfigureStreams(
@@ -337,43 +474,46 @@
                        std::string(strerror(result)));
     return;
   }
-  if (!updated_config || updated_config->streams.size() != 1) {
+  if (!updated_config ||
+      updated_config->streams.size() != kMaxConfiguredStreams) {
     device_context_->SetErrorState(
         FROM_HERE, std::string("Wrong number of streams configured: ") +
                        std::to_string(updated_config->streams.size()));
     return;
   }
 
-  // The partial result count metadata is optional; defaults to 1 in case it
-  // is not set in the static metadata.
-  uint32_t partial_result_count = 1;
-  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());
-  }
-  stream_buffer_manager_->SetUpStreamAndBuffers(
-      chrome_capture_params_.requested_format, partial_result_count,
-      std::move(updated_config->streams[0]));
+  stream_buffer_manager_->SetUpStreamsAndBuffers(
+      chrome_capture_params_.requested_format, static_metadata_,
+      std::move(updated_config->streams));
 
   device_context_->SetState(CameraDeviceContext::State::kStreamConfigured);
-  ConstructDefaultRequestSettings();
+  // Kick off the preview stream.
+  ConstructDefaultRequestSettings(StreamType::kPreview);
 }
 
-void CameraDeviceDelegate::ConstructDefaultRequestSettings() {
+void CameraDeviceDelegate::ConstructDefaultRequestSettings(
+    StreamType stream_type) {
   DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-  DCHECK_EQ(device_context_->GetState(),
-            CameraDeviceContext::State::kStreamConfigured);
+  DCHECK(device_context_->GetState() ==
+             CameraDeviceContext::State::kStreamConfigured ||
+         device_context_->GetState() == CameraDeviceContext::State::kCapturing);
 
-  device_ops_->ConstructDefaultRequestSettings(
-      cros::mojom::Camera3RequestTemplate::CAMERA3_TEMPLATE_PREVIEW,
-      base::BindOnce(&CameraDeviceDelegate::OnConstructedDefaultRequestSettings,
-                     GetWeakPtr()));
+  if (stream_type == StreamType::kPreview) {
+    device_ops_->ConstructDefaultRequestSettings(
+        cros::mojom::Camera3RequestTemplate::CAMERA3_TEMPLATE_PREVIEW,
+        base::BindOnce(
+            &CameraDeviceDelegate::OnConstructedDefaultPreviewRequestSettings,
+            GetWeakPtr()));
+  } else {  // stream_type == StreamType::kStillCapture
+    device_ops_->ConstructDefaultRequestSettings(
+        cros::mojom::Camera3RequestTemplate::CAMERA3_TEMPLATE_STILL_CAPTURE,
+        base::BindOnce(&CameraDeviceDelegate::
+                           OnConstructedDefaultStillCaptureRequestSettings,
+                       GetWeakPtr()));
+  }
 }
 
-void CameraDeviceDelegate::OnConstructedDefaultRequestSettings(
+void CameraDeviceDelegate::OnConstructedDefaultPreviewRequestSettings(
     cros::mojom::CameraMetadataPtr settings) {
   DCHECK(ipc_task_runner_->BelongsToCurrentThread());
 
@@ -389,7 +529,29 @@
     return;
   }
   device_context_->SetState(CameraDeviceContext::State::kCapturing);
-  stream_buffer_manager_->StartCapture(std::move(settings));
+  camera_3a_controller_->SetAutoFocusModeForStillCapture();
+  stream_buffer_manager_->StartPreview(std::move(settings));
+
+  if (!take_photo_callbacks_.empty()) {
+    camera_3a_controller_->Stabilize3AForStillCapture(
+        base::BindOnce(&CameraDeviceDelegate::ConstructDefaultRequestSettings,
+                       GetWeakPtr(), StreamType::kStillCapture));
+  }
+}
+
+void CameraDeviceDelegate::OnConstructedDefaultStillCaptureRequestSettings(
+    cros::mojom::CameraMetadataPtr settings) {
+  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
+
+  while (!take_photo_callbacks_.empty()) {
+    stream_buffer_manager_->TakePhoto(
+        std::move(settings),
+        base::BindOnce(
+            &TakePhotoCallbackBundle, std::move(take_photo_callbacks_.front()),
+            base::BindOnce(&Camera3AController::SetAutoFocusModeForStillCapture,
+                           camera_3a_controller_->GetWeakPtr())));
+    take_photo_callbacks_.pop();
+  }
 }
 
 void CameraDeviceDelegate::RegisterBuffer(
diff --git a/media/capture/video/chromeos/camera_device_delegate.h b/media/capture/video/chromeos/camera_device_delegate.h
index 8d268013..2b297b6 100644
--- a/media/capture/video/chromeos/camera_device_delegate.h
+++ b/media/capture/video/chromeos/camera_device_delegate.h
@@ -17,10 +17,21 @@
 
 namespace media {
 
-class CameraHalDelegate;
+class Camera3AController;
 class CameraDeviceContext;
+class CameraHalDelegate;
 class StreamBufferManager;
 
+enum class StreamType : int32_t {
+  kPreview = 0,
+  kStillCapture = 1,
+  kUnknown,
+};
+
+std::string StreamTypeToString(StreamType stream_type);
+
+std::ostream& operator<<(std::ostream& os, StreamType stream_type);
+
 // The interface to register buffer with and send capture request to the
 // camera HAL.
 class CAPTURE_EXPORT StreamCaptureInterface {
@@ -122,10 +133,14 @@
   // settings of the stream in |stream_context_|.
   // OnConstructedDefaultRequestSettings sets the request settings in
   // |streams_context_|.  If there's no error
-  // OnConstructedDefaultRequestSettings calls StartCapture to start the video
-  // capture loop.
-  void ConstructDefaultRequestSettings();
-  void OnConstructedDefaultRequestSettings(
+  // OnConstructedDefaultPreviewRequestSettings calls StartPreview to start the
+  // video capture loop.
+  // OnConstructDefaultStillCaptureRequestSettings triggers
+  // |stream_buffer_manager_| to request a still capture.
+  void ConstructDefaultRequestSettings(StreamType stream_type);
+  void OnConstructedDefaultPreviewRequestSettings(
+      cros::mojom::CameraMetadataPtr settings);
+  void OnConstructedDefaultStillCaptureRequestSettings(
       cros::mojom::CameraMetadataPtr settings);
 
   // StreamCaptureInterface implementations.  These methods are called by
@@ -151,8 +166,12 @@
 
   CameraDeviceContext* device_context_;
 
+  std::queue<VideoCaptureDevice::TakePhotoCallback> take_photo_callbacks_;
+
   std::unique_ptr<StreamBufferManager> stream_buffer_manager_;
 
+  std::unique_ptr<Camera3AController> camera_3a_controller_;
+
   // Stores the static camera characteristics of the camera device. E.g. the
   // supported formats and resolution, various available exposure and apeture
   // settings, etc.
diff --git a/media/capture/video/chromeos/camera_device_delegate_unittest.cc b/media/capture/video/chromeos/camera_device_delegate_unittest.cc
index d29c322..aa0f3c91 100644
--- a/media/capture/video/chromeos/camera_device_delegate_unittest.cc
+++ b/media/capture/video/chromeos/camera_device_delegate_unittest.cc
@@ -110,8 +110,11 @@
   DISALLOW_COPY_AND_ASSIGN(MockCameraDevice);
 };
 
+constexpr int32_t kJpegMaxBufferSize = 1024;
+constexpr size_t kDefaultWidth = 1280, kDefaultHeight = 720;
 const VideoCaptureDeviceDescriptor kDefaultDescriptor("Fake device", "0");
-const VideoCaptureFormat kDefaultCaptureFormat(gfx::Size(1280, 720),
+const VideoCaptureFormat kDefaultCaptureFormat(gfx::Size(kDefaultWidth,
+                                                         kDefaultHeight),
                                                30.0,
                                                PIXEL_FORMAT_I420);
 
@@ -153,16 +156,60 @@
     cros::mojom::CameraInfoPtr camera_info = cros::mojom::CameraInfo::New();
     cros::mojom::CameraMetadataPtr static_metadata =
         cros::mojom::CameraMetadata::New();
+
+    static_metadata->entry_count = 3;
+    static_metadata->entry_capacity = 3;
+    static_metadata->entries =
+        std::vector<cros::mojom::CameraMetadataEntryPtr>();
+
     cros::mojom::CameraMetadataEntryPtr entry =
         cros::mojom::CameraMetadataEntry::New();
     entry->index = 0;
+    entry->tag = cros::mojom::CameraMetadataTag::
+        ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS;
+    entry->type = cros::mojom::EntryType::TYPE_INT32;
+    entry->count = 12;
+    std::vector<int32_t> stream_configurations(entry->count);
+    stream_configurations[0] = static_cast<int32_t>(
+        cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED);
+    stream_configurations[1] = kDefaultWidth;
+    stream_configurations[2] = kDefaultHeight;
+    stream_configurations[3] = static_cast<int32_t>(
+        cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT);
+    stream_configurations[4] = static_cast<int32_t>(
+        cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_YCbCr_420_888);
+    stream_configurations[5] = kDefaultWidth;
+    stream_configurations[6] = kDefaultHeight;
+    stream_configurations[7] = static_cast<int32_t>(
+        cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT);
+    stream_configurations[8] = static_cast<int32_t>(
+        cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_BLOB);
+    stream_configurations[9] = kDefaultWidth;
+    stream_configurations[10] = kDefaultHeight;
+    stream_configurations[11] = static_cast<int32_t>(
+        cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT);
+    uint8_t* as_int8 = reinterpret_cast<uint8_t*>(stream_configurations.data());
+    entry->data.assign(as_int8, as_int8 + entry->count * sizeof(int32_t));
+    static_metadata->entries->push_back(std::move(entry));
+
+    entry = cros::mojom::CameraMetadataEntry::New();
+    entry->index = 1;
     entry->tag = cros::mojom::CameraMetadataTag::ANDROID_SENSOR_ORIENTATION;
     entry->type = cros::mojom::EntryType::TYPE_INT32;
     entry->count = 1;
     entry->data = std::vector<uint8_t>(4, 0);
-    static_metadata->entries =
-        std::vector<cros::mojom::CameraMetadataEntryPtr>();
     static_metadata->entries->push_back(std::move(entry));
+
+    entry = cros::mojom::CameraMetadataEntry::New();
+    entry->index = 2;
+    entry->tag = cros::mojom::CameraMetadataTag::ANDROID_JPEG_MAX_SIZE;
+    entry->type = cros::mojom::EntryType::TYPE_INT32;
+    entry->count = 1;
+    int32_t jpeg_max_size = kJpegMaxBufferSize;
+    as_int8 = reinterpret_cast<uint8_t*>(&jpeg_max_size);
+    entry->data.assign(as_int8, as_int8 + entry->count * sizeof(int32_t));
+    static_metadata->entries->push_back(std::move(entry));
+
     switch (camera_id) {
       case 0:
         camera_info->facing = cros::mojom::CameraFacing::CAMERA_FACING_FRONT;
@@ -195,23 +242,17 @@
       base::OnceCallback<void(int32_t,
                               cros::mojom::Camera3StreamConfigurationPtr)>&
           callback) {
-    ASSERT_EQ(1u, config->streams.size());
-    ASSERT_EQ(static_cast<uint32_t>(kDefaultCaptureFormat.frame_size.width()),
-              config->streams[0]->width);
-    ASSERT_EQ(static_cast<uint32_t>(kDefaultCaptureFormat.frame_size.height()),
-              config->streams[0]->height);
-    ASSERT_EQ(cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_YCbCr_420_888,
-              config->streams[0]->format);
-    config->streams[0]->usage = 0;
-    config->streams[0]->max_buffers = 1;
+    ASSERT_EQ(2u, config->streams.size());
+    for (size_t i = 0; i < config->streams.size(); ++i) {
+      config->streams[i]->usage = 0;
+      config->streams[i]->max_buffers = 1;
+    }
     std::move(callback).Run(0, std::move(config));
   }
 
   void ConstructFakeRequestSettings(
       cros::mojom::Camera3RequestTemplate type,
       base::OnceCallback<void(cros::mojom::CameraMetadataPtr)>& callback) {
-    ASSERT_EQ(cros::mojom::Camera3RequestTemplate::CAMERA3_TEMPLATE_PREVIEW,
-              type);
     cros::mojom::CameraMetadataPtr fake_settings =
         cros::mojom::CameraMetadata::New();
     fake_settings->entry_count = 1;
@@ -291,10 +332,27 @@
         .Times(1)
         .WillOnce(Invoke(&unittest_internal::MockGpuMemoryBufferManager::
                              CreateFakeGpuMemoryBuffer));
+    EXPECT_CALL(
+        mock_gpu_memory_buffer_manager_,
+        CreateGpuMemoryBuffer(_, gfx::BufferFormat::R_8,
+                              gfx::BufferUsage::CAMERA_AND_CPU_READ_WRITE,
+                              gpu::kNullSurfaceHandle))
+        .Times(1)
+        .WillOnce(Invoke(&unittest_internal::MockGpuMemoryBufferManager::
+                             CreateFakeGpuMemoryBuffer));
+    EXPECT_CALL(
+        mock_gpu_memory_buffer_manager_,
+        CreateGpuMemoryBuffer(gfx::Size(kDefaultWidth, kDefaultHeight),
+                              gfx::BufferFormat::YUV_420_BIPLANAR,
+                              gfx::BufferUsage::SCANOUT_CAMERA_READ_WRITE,
+                              gpu::kNullSurfaceHandle))
+        .Times(1)
+        .WillOnce(Invoke(&unittest_internal::MockGpuMemoryBufferManager::
+                             CreateFakeGpuMemoryBuffer));
     EXPECT_CALL(mock_gpu_memory_buffer_manager_,
                 CreateGpuMemoryBuffer(
-                    gfx::Size(1280, 720), gfx::BufferFormat::YUV_420_BIPLANAR,
-                    gfx::BufferUsage::SCANOUT_CAMERA_READ_WRITE,
+                    gfx::Size(kJpegMaxBufferSize, 1), gfx::BufferFormat::R_8,
+                    gfx::BufferUsage::CAMERA_AND_CPU_READ_WRITE,
                     gpu::kNullSurfaceHandle))
         .Times(1)
         .WillOnce(Invoke(&unittest_internal::MockGpuMemoryBufferManager::
diff --git a/media/capture/video/chromeos/camera_hal_delegate.cc b/media/capture/video/chromeos/camera_hal_delegate.cc
index 33a145f..3a33d9b 100644
--- a/media/capture/video/chromeos/camera_hal_delegate.cc
+++ b/media/capture/video/chromeos/camera_hal_delegate.cc
@@ -130,21 +130,25 @@
       reinterpret_cast<int64_t*>((*min_frame_durations)->data.data());
   for (size_t i = 0; i < (*min_frame_durations)->count;
        i += kStreamDurationSize) {
-    int32_t format = base::checked_cast<int32_t>(iter[kStreamFormatOffset]);
+    auto hal_format =
+        static_cast<cros::mojom::HalPixelFormat>(iter[kStreamFormatOffset]);
     int32_t width = base::checked_cast<int32_t>(iter[kStreamWidthOffset]);
     int32_t height = base::checked_cast<int32_t>(iter[kStreamHeightOffset]);
     int64_t duration = iter[kStreamDurationOffset];
     iter += kStreamDurationSize;
 
+    if (hal_format == cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_BLOB) {
+      // Skip BLOB formats and use it only for TakePicture() since it's
+      // inefficient to stream JPEG frames for CrOS camera HAL.
+      continue;
+    }
+
     if (duration <= 0) {
       LOG(ERROR) << "Ignoring invalid frame duration: " << duration;
       continue;
     }
     float max_fps = 1.0 * 1000000000LL / duration;
 
-    DVLOG(1) << "[" << std::hex << format << " " << std::dec << width << " "
-             << height << " " << duration << "]";
-    auto hal_format = static_cast<cros::mojom::HalPixelFormat>(format);
     const ChromiumPixelFormat cr_format =
         camera_buffer_factory_->ResolveStreamBufferFormat(hal_format);
     if (cr_format.video_format == PIXEL_FORMAT_UNKNOWN) {
@@ -327,6 +331,7 @@
     LOG(ERROR) << "Failed to get camera info. Camera id: " << camera_id;
   }
   // In case of error |camera_info| is empty.
+  SortCameraMetadata(&camera_info->static_camera_characteristics);
   camera_info_[std::to_string(camera_id)] = std::move(camera_info);
   if (camera_info_.size() == num_builtin_cameras_) {
     builtin_camera_info_updated_.Signal();
diff --git a/media/capture/video/chromeos/camera_metadata_utils.cc b/media/capture/video/chromeos/camera_metadata_utils.cc
index 8f3d3a4..c168f87b 100644
--- a/media/capture/video/chromeos/camera_metadata_utils.cc
+++ b/media/capture/video/chromeos/camera_metadata_utils.cc
@@ -4,22 +4,43 @@
 
 #include "media/capture/video/chromeos/camera_metadata_utils.h"
 
-#include <set>
+#include <algorithm>
+#include <unordered_set>
 
 namespace media {
 
-const cros::mojom::CameraMetadataEntryPtr* GetMetadataEntry(
+cros::mojom::CameraMetadataEntryPtr* GetMetadataEntry(
     const cros::mojom::CameraMetadataPtr& camera_metadata,
     cros::mojom::CameraMetadataTag tag) {
-  if (!camera_metadata->entries.has_value()) {
+  if (!camera_metadata || !camera_metadata->entries.has_value()) {
     return nullptr;
   }
-  for (const auto& entry : camera_metadata->entries.value()) {
-    if (entry->tag == tag) {
-      return &entry;
-    }
+  // We assume the metadata entries are sorted.
+  auto iter = std::find_if(camera_metadata->entries.value().begin(),
+                           camera_metadata->entries.value().end(),
+                           [tag](const cros::mojom::CameraMetadataEntryPtr& e) {
+                             return e->tag == tag;
+                           });
+  if (iter == camera_metadata->entries.value().end()) {
+    return nullptr;
   }
-  return nullptr;
+  return &(camera_metadata->entries.value()[(*iter)->index]);
+}
+
+void SortCameraMetadata(cros::mojom::CameraMetadataPtr* camera_metadata) {
+  if (!camera_metadata || !(*camera_metadata) ||
+      !(*camera_metadata)->entries.has_value()) {
+    return;
+  }
+  std::sort((*camera_metadata)->entries.value().begin(),
+            (*camera_metadata)->entries.value().end(),
+            [](const cros::mojom::CameraMetadataEntryPtr& a,
+               const cros::mojom::CameraMetadataEntryPtr& b) {
+              return a->tag < b->tag;
+            });
+  for (size_t i = 0; i < (*camera_metadata)->entries.value().size(); ++i) {
+    (*camera_metadata)->entries.value()[i]->index = i;
+  }
 }
 
 void MergeMetadata(cros::mojom::CameraMetadataPtr* to,
@@ -34,7 +55,7 @@
     return;
   }
 
-  std::set<cros::mojom::CameraMetadataTag> tags;
+  std::unordered_set<cros::mojom::CameraMetadataTag> tags;
   if ((*to)->entries) {
     for (const auto& entry : (*to)->entries.value()) {
       tags.insert(entry->tag);
diff --git a/media/capture/video/chromeos/camera_metadata_utils.h b/media/capture/video/chromeos/camera_metadata_utils.h
index 8dbb1084..172bcd0 100644
--- a/media/capture/video/chromeos/camera_metadata_utils.h
+++ b/media/capture/video/chromeos/camera_metadata_utils.h
@@ -5,16 +5,21 @@
 #ifndef MEDIA_CAPTURE_VIDEO_CHROMEOS_CAMERA_METADATA_UTILS_H_
 #define MEDIA_CAPTURE_VIDEO_CHROMEOS_CAMERA_METADATA_UTILS_H_
 
+#include "media/capture/capture_export.h"
 #include "media/capture/video/chromeos/mojo/camera_metadata.mojom.h"
 
 namespace media {
 
-const cros::mojom::CameraMetadataEntryPtr* GetMetadataEntry(
+CAPTURE_EXPORT cros::mojom::CameraMetadataEntryPtr* GetMetadataEntry(
     const cros::mojom::CameraMetadataPtr& camera_metadata,
     cros::mojom::CameraMetadataTag tag);
 
-void MergeMetadata(cros::mojom::CameraMetadataPtr* to,
-                   const cros::mojom::CameraMetadataPtr& from);
+// Sort the camera metadata entries using the metadata tags.
+CAPTURE_EXPORT void SortCameraMetadata(
+    cros::mojom::CameraMetadataPtr* camera_metadata);
+
+CAPTURE_EXPORT void MergeMetadata(cros::mojom::CameraMetadataPtr* to,
+                                  const cros::mojom::CameraMetadataPtr& from);
 
 }  // namespace media
 
diff --git a/media/capture/video/chromeos/local_gpu_memory_buffer_manager.cc b/media/capture/video/chromeos/local_gpu_memory_buffer_manager.cc
index 2396d59..020ca13 100644
--- a/media/capture/video/chromeos/local_gpu_memory_buffer_manager.cc
+++ b/media/capture/video/chromeos/local_gpu_memory_buffer_manager.cc
@@ -51,6 +51,8 @@
 
 uint32_t GetDrmFormat(gfx::BufferFormat gfx_format) {
   switch (gfx_format) {
+    case gfx::BufferFormat::R_8:
+      return DRM_FORMAT_R8;
     case gfx::BufferFormat::YUV_420_BIPLANAR:
       return DRM_FORMAT_NV12;
     // Add more formats when needed.
@@ -186,7 +188,8 @@
     gfx::BufferFormat format,
     gfx::BufferUsage usage,
     gpu::SurfaceHandle surface_handle) {
-  if (usage != gfx::BufferUsage::SCANOUT_CAMERA_READ_WRITE) {
+  if (usage != gfx::BufferUsage::SCANOUT_CAMERA_READ_WRITE &&
+      usage != gfx::BufferUsage::CAMERA_AND_CPU_READ_WRITE) {
     LOG(ERROR) << "Unsupported gfx::BufferUsage" << static_cast<int>(usage);
     return std::unique_ptr<gfx::GpuMemoryBuffer>();
   }
diff --git a/media/capture/video/chromeos/pixel_format_utils.cc b/media/capture/video/chromeos/pixel_format_utils.cc
index 9e1449a..6f9d4e3 100644
--- a/media/capture/video/chromeos/pixel_format_utils.cc
+++ b/media/capture/video/chromeos/pixel_format_utils.cc
@@ -31,6 +31,9 @@
     // support YUV flexbile format video streams.
     {cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_YCbCr_420_888,
      {PIXEL_FORMAT_NV12, gfx::BufferFormat::YUV_420_BIPLANAR}},
+    // FIXME(jcliang): MJPEG is not accurate; we should have BLOB or JPEG
+    {cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_BLOB,
+     {PIXEL_FORMAT_MJPEG, gfx::BufferFormat::R_8}},
     // Add more mappings when we have more devices.
 };
 
@@ -51,6 +54,8 @@
   switch (from) {
     case PIXEL_FORMAT_NV12:
       return DRM_FORMAT_NV12;
+    case PIXEL_FORMAT_MJPEG:
+      return DRM_FORMAT_R8;
     default:
       // Unsupported format.
       return 0;
diff --git a/media/capture/video/chromeos/stream_buffer_manager.cc b/media/capture/video/chromeos/stream_buffer_manager.cc
index df8f42bd..3fc9457 100644
--- a/media/capture/video/chromeos/stream_buffer_manager.cc
+++ b/media/capture/video/chromeos/stream_buffer_manager.cc
@@ -15,16 +15,40 @@
 
 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)> 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),
       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),
@@ -38,107 +62,235 @@
 
 StreamBufferManager::~StreamBufferManager() {
   DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-  if (stream_context_) {
-    for (const auto& buf : stream_context_->buffers) {
-      if (buf) {
-        buf->Unmap();
+  for (const auto& iter : stream_context_) {
+    if (iter.second) {
+      for (const auto& buf : iter.second->buffers) {
+        if (buf) {
+          buf->Unmap();
+        }
       }
     }
   }
 }
 
-void StreamBufferManager::SetUpStreamAndBuffers(
+void StreamBufferManager::SetUpStreamsAndBuffers(
     VideoCaptureFormat capture_format,
-    uint32_t partial_result_count,
-    cros::mojom::Camera3StreamPtr stream) {
+    const cros::mojom::CameraMetadataPtr& static_metadata,
+    std::vector<cros::mojom::Camera3StreamPtr> streams) {
   DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-  DCHECK(!stream_context_);
+  DCHECK(!stream_context_[StreamType::kPreview]);
 
-  VLOG(2) << "Stream " << stream->id << " configured: usage=" << stream->usage
-          << " max_buffers=" << stream->max_buffers;
-
-  const size_t kMaximumAllowedBuffers = 15;
-  if (stream->max_buffers > kMaximumAllowedBuffers) {
-    device_context_->SetErrorState(
-        FROM_HERE, std::string("Camera HAL requested ") +
-                       std::to_string(stream->max_buffers) +
-                       std::string(" buffers which exceeds the allowed maximum "
-                                   "number of buffers"));
-    return;
+  // 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());
   }
 
-  partial_result_count_ = partial_result_count;
-  stream_context_ = std::make_unique<StreamContext>();
-  stream_context_->capture_format = capture_format;
-  stream_context_->stream = std::move(stream);
+  for (auto& stream : streams) {
+    DVLOG(2) << "Stream " << stream->id
+             << " configured: usage=" << stream->usage
+             << " max_buffers=" << stream->max_buffers;
 
-  const ChromiumPixelFormat stream_format =
-      camera_buffer_factory_->ResolveStreamBufferFormat(
-          stream_context_->stream->format);
-  stream_context_->capture_format.pixel_format = stream_format.video_format;
-
-  // Allocate buffers.
-  size_t num_buffers = stream_context_->stream->max_buffers;
-  stream_context_->buffers.resize(num_buffers);
-  for (size_t j = 0; j < num_buffers; ++j) {
-    auto buffer = camera_buffer_factory_->CreateGpuMemoryBuffer(
-        gfx::Size(stream_context_->stream->width,
-                  stream_context_->stream->height),
-        stream_format.gfx_format);
-    if (!buffer) {
-      device_context_->SetErrorState(FROM_HERE,
-                                     "Failed to create GpuMemoryBuffer");
+    const size_t kMaximumAllowedBuffers = 15;
+    if (stream->max_buffers > kMaximumAllowedBuffers) {
+      device_context_->SetErrorState(
+          FROM_HERE,
+          std::string("Camera HAL requested ") +
+              std::to_string(stream->max_buffers) +
+              std::string(" buffers which exceeds the allowed maximum "
+                          "number of buffers"));
       return;
     }
-    bool ret = buffer->Map();
-    if (!ret) {
-      device_context_->SetErrorState(FROM_HERE,
-                                     "Failed to map GpuMemoryBuffer");
-      return;
+
+    // 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;
     }
-    stream_context_->buffers[j] = std::move(buffer);
-    stream_context_->free_buffers.push(j);
+    stream_context_[stream_type] = std::make_unique<StreamContext>();
+    stream_context_[stream_type]->capture_format = capture_format;
+    stream_context_[stream_type]->stream = std::move(stream);
+
+    const ChromiumPixelFormat stream_format =
+        camera_buffer_factory_->ResolveStreamBufferFormat(
+            stream_context_[stream_type]->stream->format);
+    stream_context_[stream_type]->capture_format.pixel_format =
+        stream_format.video_format;
+
+    // Allocate buffers.
+    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;
+    }
+    for (size_t j = 0; j < num_buffers; ++j) {
+      auto buffer = camera_buffer_factory_->CreateGpuMemoryBuffer(
+          gfx::Size(buffer_width, buffer_height), stream_format.gfx_format);
+      if (!buffer) {
+        device_context_->SetErrorState(FROM_HERE,
+                                       "Failed to create GpuMemoryBuffer");
+        return;
+      }
+      bool ret = buffer->Map();
+      if (!ret) {
+        device_context_->SetErrorState(FROM_HERE,
+                                       "Failed to map GpuMemoryBuffer");
+        return;
+      }
+      stream_context_[stream_type]->buffers[j] = std::move(buffer);
+      stream_context_[stream_type]->free_buffers.push(
+          GetBufferIpcId(stream_type, j));
+    }
+    DVLOG(2) << "Allocated "
+             << stream_context_[stream_type]->stream->max_buffers << " buffers";
   }
-  VLOG(2) << "Allocated " << stream_context_->stream->max_buffers << " buffers";
 }
 
-void StreamBufferManager::StartCapture(
-    cros::mojom::CameraMetadataPtr settings) {
+void StreamBufferManager::StartPreview(
+    cros::mojom::CameraMetadataPtr preview_settings) {
   DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-  DCHECK(stream_context_);
-  DCHECK(stream_context_->request_settings.is_null());
+  DCHECK(stream_context_[StreamType::kPreview]);
+  DCHECK(repeating_request_settings_.is_null());
 
   capturing_ = true;
-  stream_context_->request_settings = std::move(settings);
+  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();
+  RegisterBuffer(StreamType::kPreview);
 }
 
-void StreamBufferManager::StopCapture() {
+void StreamBufferManager::StopPreview() {
   DCHECK(ipc_task_runner_->BelongsToCurrentThread());
   capturing_ = false;
 }
 
-void StreamBufferManager::RegisterBuffer() {
+cros::mojom::Camera3StreamPtr StreamBufferManager::GetStreamConfiguration(
+    StreamType stream_type) {
+  if (!stream_context_.count(stream_type)) {
+    return cros::mojom::Camera3Stream::New();
+  }
+  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_);
+  DCHECK(stream_context_[StreamType::kStillCapture]);
+
+  pending_still_capture_callbacks_.push(std::move(callback));
+  oneshot_request_settings_.push(std::move(settings));
+  RegisterBuffer(StreamType::kStillCapture);
+}
+
+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));
+}
+
+// static
+uint64_t StreamBufferManager::GetBufferIpcId(StreamType stream_type,
+                                             size_t index) {
+  uint64_t id = 0;
+  id |= static_cast<int64_t>(stream_type) << 32;
+  id |= index;
+  return id;
+}
+
+void StreamBufferManager::ApplyCaptureSettings(
+    cros::mojom::CameraMetadataPtr* capture_settings) {
+  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
+
+  if (capture_settings_override_.empty()) {
+    return;
+  }
+  for (auto& s : capture_settings_override_) {
+    auto* entry = GetMetadataEntry(*capture_settings, s->tag);
+    if (entry) {
+      DCHECK_EQ((*entry)->type, s->type);
+      (*entry).Swap(&s);
+    } else {
+      (*capture_settings)->entry_count += 1;
+      (*capture_settings)->entry_capacity += 1;
+      (*capture_settings)->data_count += s->data.size();
+      (*capture_settings)->data_capacity += s->data.size();
+      if (!(*capture_settings)->entries) {
+        (*capture_settings)->entries =
+            std::vector<cros::mojom::CameraMetadataEntryPtr>();
+      }
+      (*capture_settings)->entries.value().push_back(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_->free_buffers.empty()) {
+  if (stream_context_[stream_type]->free_buffers.empty()) {
     return;
   }
 
-  size_t buffer_id = stream_context_->free_buffers.front();
-  stream_context_->free_buffers.pop();
+  size_t buffer_id = stream_context_[stream_type]->free_buffers.front();
+  stream_context_[stream_type]->free_buffers.pop();
   const gfx::GpuMemoryBuffer* buffer =
-      stream_context_->buffers[buffer_id].get();
+      stream_context_[stream_type]->buffers[GetBufferIndex(buffer_id)].get();
 
-  VideoPixelFormat buffer_format = stream_context_->capture_format.pixel_format;
+  VideoPixelFormat buffer_format =
+      stream_context_[stream_type]->capture_format.pixel_format;
   uint32_t drm_format = PixFormatVideoToDrm(buffer_format);
   if (!drm_format) {
     device_context_->SetErrorState(
@@ -147,7 +299,7 @@
     return;
   }
   cros::mojom::HalPixelFormat hal_pixel_format =
-      stream_context_->stream->format;
+      stream_context_[stream_type]->stream->format;
 
   gfx::NativePixmapHandle buffer_handle =
       buffer->GetHandle().native_pixmap_handle;
@@ -178,15 +330,18 @@
   // gralloc buffers.
   capture_interface_->RegisterBuffer(
       buffer_id, cros::mojom::Camera3DeviceOps::BufferType::GRALLOC, drm_format,
-      hal_pixel_format, stream_context_->stream->width,
-      stream_context_->stream->height, std::move(planes),
+      hal_pixel_format, buffer->GetSize().width(), buffer->GetSize().height(),
+      std::move(planes),
       base::BindOnce(&StreamBufferManager::OnRegisteredBuffer,
-                     weak_ptr_factory_.GetWeakPtr(), buffer_id));
-  VLOG(2) << "Registered buffer " << buffer_id;
+                     weak_ptr_factory_.GetWeakPtr(), stream_type, buffer_id));
+  DVLOG(2) << "Registered buffer " << buffer_id;
 }
 
-void StreamBufferManager::OnRegisteredBuffer(size_t buffer_id, int32_t result) {
+void StreamBufferManager::OnRegisteredBuffer(StreamType stream_type,
+                                             size_t buffer_id,
+                                             int32_t result) {
   DCHECK(ipc_task_runner_->BelongsToCurrentThread());
+  DCHECK(stream_context_[stream_type]);
 
   if (!capturing_) {
     return;
@@ -197,32 +352,64 @@
                                        std::string(strerror(result)));
     return;
   }
-  ProcessCaptureRequest(buffer_id);
+  stream_context_[stream_type]->registered_buffers.push(buffer_id);
+  ProcessCaptureRequest();
 }
 
-void StreamBufferManager::ProcessCaptureRequest(size_t buffer_id) {
+void StreamBufferManager::ProcessCaptureRequest() {
   DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-  DCHECK(stream_context_);
-
-  cros::mojom::Camera3StreamBufferPtr buffer =
-      cros::mojom::Camera3StreamBuffer::New();
-  buffer->stream_id = static_cast<uint64_t>(
-      cros::mojom::Camera3RequestTemplate::CAMERA3_TEMPLATE_PREVIEW);
-  buffer->buffer_id = buffer_id;
-  buffer->status = cros::mojom::Camera3BufferStatus::CAMERA3_BUFFER_STATUS_OK;
+  DCHECK(stream_context_[StreamType::kPreview]);
+  DCHECK(stream_context_[StreamType::kStillCapture]);
 
   cros::mojom::Camera3CaptureRequestPtr request =
       cros::mojom::Camera3CaptureRequest::New();
   request->frame_number = frame_number_;
-  request->settings = stream_context_->request_settings.Clone();
-  request->output_buffers.push_back(std::move(buffer));
 
+  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_[StreamType::kStillCapture]->registered_buffers.empty()) {
+    DCHECK(!pending_still_capture_callbacks_.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(pending_still_capture_callbacks_.front());
+    pending_still_capture_callbacks_.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()));
-  VLOG(2) << "Requested capture for frame " << frame_number_ << " with buffer "
-          << buffer_id;
   frame_number_++;
 }
 
@@ -238,7 +425,8 @@
                        std::string(strerror(result)));
     return;
   }
-  RegisterBuffer();
+  // Keeps the preview stream going.
+  RegisterBuffer(StreamType::kPreview);
 }
 
 void StreamBufferManager::ProcessCaptureResult(
@@ -251,70 +439,78 @@
   uint32_t frame_number = result->frame_number;
   // A new partial result may be created in either ProcessCaptureResult or
   // Notify.
-  CaptureResult& partial_result = partial_results_[frame_number];
-  if (partial_results_.size() > stream_context_->stream->max_buffers) {
-    device_context_->SetErrorState(
-        FROM_HERE,
-        "Received more capture results than the maximum number of buffers");
-    return;
+  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(
+          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(
+          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() != 1) {
+    if (result->output_buffers->size() > kMaxConfiguredStreams) {
       device_context_->SetErrorState(
           FROM_HERE,
           std::string("Incorrect number of output buffers received: ") +
               std::to_string(result->output_buffers->size()));
       return;
     }
-    cros::mojom::Camera3StreamBufferPtr& stream_buffer =
-        result->output_buffers.value()[0];
-    VLOG(2) << "Received capture result for frame " << frame_number
-            << " stream_id: " << stream_buffer->stream_id;
-    // The camera HAL v3 API specifies that only one capture result can carry
-    // the result buffer for any given frame number.
-    if (!partial_result.buffer.is_null()) {
-      device_context_->SetErrorState(
-          FROM_HERE,
-          std::string("Received multiple result buffers for frame ") +
-              std::to_string(frame_number));
-      return;
-    } else {
-      partial_result.buffer = std::move(stream_buffer);
-      // If the buffer is marked as error it is due to either a request or a
-      // buffer error.  In either case the content of the buffer must be dropped
-      // and the buffer can be reused.  We simply submit the buffer here and
-      // don't wait for any partial results.  SubmitCaptureResult() will drop
-      // and reuse the buffer.
-      if (partial_result.buffer->status ==
-          cros::mojom::Camera3BufferStatus::CAMERA3_BUFFER_STATUS_ERROR) {
-        SubmitCaptureResult(frame_number);
+    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(
+            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(
+            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);
+      }
     }
   }
 
-  // |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) {
-    uint32_t result_id = result->partial_result;
-    if (result_id > partial_result_count_) {
-      device_context_->SetErrorState(
-          FROM_HERE, std::string("Invalid partial_result id: ") +
-                         std::to_string(result_id));
-      return;
-    }
-    if (partial_result.partial_metadata_received.find(result_id) !=
-        partial_result.partial_metadata_received.end()) {
-      device_context_->SetErrorState(
-          FROM_HERE, std::string("Received duplicated partial metadata: ") +
-                         std::to_string(result_id));
-      return;
-    }
-    partial_result.partial_metadata_received.insert(result_id);
-    MergeMetadata(&partial_result.metadata, result->result);
+  for (const auto& iter : stream_context_) {
+    SubmitCaptureResultIfComplete(frame_number, iter.first);
   }
-
-  SubmitCaptureResultIfComplete(frame_number);
 }
 
 void StreamBufferManager::Notify(cros::mojom::Camera3NotifyMsgPtr message) {
@@ -326,46 +522,49 @@
   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(
+          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, error_stream_id, 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;
-    // A new partial result may be created in either ProcessCaptureResult or
-    // Notify.
-    VLOG(2) << "Received shutter time for frame " << frame_number;
+    DVLOG(2) << "Received shutter time for frame " << frame_number;
     if (!shutter_time) {
       device_context_->SetErrorState(
           FROM_HERE, std::string("Received invalid shutter time: ") +
                          std::to_string(shutter_time));
       return;
     }
-    CaptureResult& partial_result = partial_results_[frame_number];
-    if (partial_results_.size() > stream_context_->stream->max_buffers) {
-      device_context_->SetErrorState(
-          FROM_HERE,
-          "Received more capture results than the maximum number of buffers");
-      return;
-    }
+    CaptureResult& pending_result = pending_results_[frame_number];
     // Shutter timestamp is in ns.
     base::TimeTicks reference_time =
         base::TimeTicks::FromInternalValue(shutter_time / 1000);
-    partial_result.reference_time = reference_time;
+    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;
     }
-    partial_result.timestamp = reference_time - first_frame_shutter_time_;
-    SubmitCaptureResultIfComplete(frame_number);
+    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,
-    uint64_t error_stream_id,
+    StreamType stream_type,
     cros::mojom::Camera3ErrorMsgCode error_code) {
+  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
+
   std::string warning_msg;
 
   switch (error_code) {
@@ -400,7 +599,7 @@
     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 |error_stream_id| specifies the stream that dropped
+      // was dropped, and |stream_type| specifies the stream that dropped
       // the buffer.
       //
       // The HAL will call ProcessCaptureResult with the buffer's state set to
@@ -409,7 +608,7 @@
       warning_msg =
           std::string(
               "An error occurred while filling output buffer of stream ") +
-          std::to_string(error_stream_id) + std::string(" in frame ") +
+          StreamTypeToString(stream_type) + std::string(" in frame ") +
           std::to_string(frame_number);
       break;
 
@@ -418,55 +617,76 @@
       break;
   }
 
-  LOG(WARNING) << warning_msg;
+  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.
-  auto partial_result = partial_results_.find(frame_number);
-  if (partial_result != partial_results_.end() &&
-      !partial_result->second.buffer.is_null()) {
-    SubmitCaptureResult(frame_number);
+  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) {
+void StreamBufferManager::SubmitCaptureResultIfComplete(
+    uint32_t frame_number,
+    StreamType stream_type) {
   DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-  DCHECK(partial_results_.find(frame_number) != partial_results_.end());
 
-  CaptureResult& partial_result = partial_results_[frame_number];
-  if (partial_result.partial_metadata_received.size() < partial_result_count_ ||
-      partial_result.buffer.is_null() ||
-      partial_result.reference_time == base::TimeTicks()) {
-    // We can only submit the result buffer when:
-    //   1. All the result metadata are received, and
-    //   2. The result buffer is received, and
-    //   3. The the shutter time is received.
+  if (!pending_results_.count(frame_number)) {
+    // The capture result may be discarded in case of error.
     return;
   }
-  SubmitCaptureResult(frame_number);
+
+  CaptureResult& pending_result = pending_results_[frame_number];
+  if (!stream_context_[stream_type]->capture_results_with_buffer.count(
+          frame_number) ||
+      pending_result.partial_metadata_received.size() < 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. All the result metadata are received, and
+    //   3. The shutter time is received.
+    return;
+  }
+  SubmitCaptureResult(frame_number, stream_type);
 }
 
-void StreamBufferManager::SubmitCaptureResult(uint32_t frame_number) {
+void StreamBufferManager::SubmitCaptureResult(uint32_t frame_number,
+                                              StreamType stream_type) {
   DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-  DCHECK(partial_results_.find(frame_number) != partial_results_.end());
+  DCHECK(pending_results_.count(frame_number));
+  DCHECK(stream_context_[stream_type]->capture_results_with_buffer.count(
+      frame_number));
 
-  CaptureResult& partial_result = partial_results_[frame_number];
-  if (partial_results_.begin()->first != 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(
         FROM_HERE, std::string("Received frame is out-of-order; expect ") +
-                       std::to_string(partial_results_.begin()->first) +
+                       std::to_string(pending_results_.begin()->first) +
                        std::string(" but got ") + std::to_string(frame_number));
     return;
   }
 
-  VLOG(2) << "Submit capture result of frame " << frame_number;
-  uint32_t buffer_id = partial_result.buffer->buffer_id;
+  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 (partial_result.buffer->release_fence.is_valid()) {
+  if (stream_buffer->release_fence.is_valid()) {
     const int kSyncWaitTimeoutMs = 1000;
     mojo::edk::ScopedPlatformHandle fence;
     MojoResult result = mojo::edk::PassWrappedPlatformHandle(
-        partial_result.buffer->release_fence.release().value(), &fence);
+        stream_buffer->release_fence.release().value(), &fence);
     if (result != MOJO_RESULT_OK) {
       device_context_->SetErrorState(FROM_HERE,
                                      "Failed to unwrap release fence fd");
@@ -479,17 +699,55 @@
     }
   }
 
-  // Deliver the captured data to client and then re-queue the buffer.
-  if (partial_result.buffer->status !=
+  // Deliver the captured data to client.
+  if (stream_buffer->status !=
       cros::mojom::Camera3BufferStatus::CAMERA3_BUFFER_STATUS_ERROR) {
-    gfx::GpuMemoryBuffer* buffer = stream_context_->buffers[buffer_id].get();
-    device_context_->SubmitCapturedData(buffer, stream_context_->capture_format,
-                                        partial_result.reference_time,
-                                        partial_result.timestamp);
+    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(FROM_HERE, "Invalid JPEG blob");
+        return;
+      }
+      mojom::BlobPtr blob = blobify_callback_.Run(
+          reinterpret_cast<uint8_t*>(buffer->memory(0)), header->jpeg_size,
+          stream_context_[stream_type]->capture_format);
+      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_->free_buffers.push(buffer_id);
-  partial_results_.erase(frame_number);
-  RegisterBuffer();
+
+  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 (!pending_still_capture_callbacks_.empty()) {
+      RegisterBuffer(StreamType::kStillCapture);
+    }
+  }
 }
 
 StreamBufferManager::StreamContext::StreamContext() = default;
@@ -497,7 +755,8 @@
 StreamBufferManager::StreamContext::~StreamContext() = default;
 
 StreamBufferManager::CaptureResult::CaptureResult()
-    : metadata(cros::mojom::CameraMetadata::New()) {}
+    : metadata(cros::mojom::CameraMetadata::New()),
+      unsubmitted_buffer_count(0) {}
 
 StreamBufferManager::CaptureResult::~CaptureResult() = default;
 
diff --git a/media/capture/video/chromeos/stream_buffer_manager.h b/media/capture/video/chromeos/stream_buffer_manager.h
index 5c544c9..75a1e58 100644
--- a/media/capture/video/chromeos/stream_buffer_manager.h
+++ b/media/capture/video/chromeos/stream_buffer_manager.h
@@ -5,6 +5,11 @@
 #ifndef MEDIA_CAPTURE_VIDEO_CHROMEOS_STREAM_BUFFER_MANAGER_H_
 #define MEDIA_CAPTURE_VIDEO_CHROMEOS_STREAM_BUFFER_MANAGER_H_
 
+#include <memory>
+#include <queue>
+#include <unordered_map>
+#include <vector>
+
 #include "base/containers/queue.h"
 #include "base/memory/weak_ptr.h"
 #include "base/single_thread_task_runner.h"
@@ -24,29 +29,65 @@
 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;
+};
+
 // 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 cros::mojom::Camera3CallbackOps,
+      public CaptureMetadataDispatcher {
  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)> blobify_callback,
       scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner);
 
-  ~StreamBufferManager() final;
+  ~StreamBufferManager() override;
 
   // Sets up the stream context and allocate buffers according to the
   // configuration specified in |stream|.
-  void SetUpStreamAndBuffers(VideoCaptureFormat capture_format,
-                             uint32_t partial_result_count,
-                             cros::mojom::Camera3StreamPtr stream);
+  void SetUpStreamsAndBuffers(
+      VideoCaptureFormat capture_format,
+      const cros::mojom::CameraMetadataPtr& static_metadata,
+      std::vector<cros::mojom::Camera3StreamPtr> streams);
 
-  // StartCapture is the entry point to starting the video capture.  The way
+  // 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
@@ -60,24 +101,48 @@
   //      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.
-  void StartCapture(cros::mojom::CameraMetadataPtr settings);
+  //
+  // 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 StopCapture is called |callback_ops_| is
+  // Stops the capture loop.  After StopPreview is called |callback_ops_| is
   // unbound, so no new capture request or result will be processed.
-  void StopCapture();
+  void StopPreview();
+
+  cros::mojom::Camera3StreamPtr GetStreamConfiguration(StreamType stream_type);
+
+  void TakePhoto(cros::mojom::CameraMetadataPtr settings,
+                 VideoCaptureDevice::TakePhotoCallback callback);
+
+  // 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;
+
+  static uint64_t GetBufferIpcId(StreamType stream_type, size_t index);
 
  private:
   friend class StreamBufferManagerTest;
 
-  // Registers a free buffer, if any, to the camera HAL.
-  void RegisterBuffer();
+  // 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(size_t buffer_id, int32_t result);
+  void OnRegisteredBuffer(StreamType stream_type,
+                          size_t buffer_id,
+                          int32_t result);
 
-  // The capture request contains the buffer handle specified by |buffer_id|.
-  void ProcessCaptureRequest(size_t buffer_id);
+  // 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);
 
@@ -86,23 +151,27 @@
   // 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 |partial_results_|.
-  void ProcessCaptureResult(cros::mojom::Camera3CaptureResultPtr result) final;
+  // 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) final;
+  void Notify(cros::mojom::Camera3NotifyMsgPtr message) override;
   void HandleNotifyError(uint32_t frame_number,
-                         uint64_t error_stream_id,
+                         StreamType stream_type,
                          cros::mojom::Camera3ErrorMsgCode error_code);
 
-  // Submits the captured buffer of frame |frame_number_| 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);
-  void SubmitCaptureResult(uint32_t frame_number);
+  // 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_;
 
@@ -112,6 +181,12 @@
 
   std::unique_ptr<CameraBufferFactory> camera_buffer_factory_;
 
+  base::RepeatingCallback<mojom::BlobPtr(
+      const uint8_t* buffer,
+      const uint32_t bytesused,
+      const VideoCaptureFormat& capture_format)>
+      blobify_callback_;
+
   // Where all the Mojo IPC calls takes place.
   const scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner_;
 
@@ -122,26 +197,7 @@
   // to zero in AllocateAndStart.
   uint32_t frame_number_;
 
-  struct StreamContext {
-    StreamContext();
-    ~StreamContext();
-    // The actual pixel format used in the capture request.
-    VideoCaptureFormat capture_format;
-    // The camera HAL stream.
-    cros::mojom::Camera3StreamPtr stream;
-    // The request settings used in the capture request of this stream.
-    cros::mojom::CameraMetadataPtr request_settings;
-    // The allocated buffers of this stream.
-    std::vector<std::unique_ptr<gfx::GpuMemoryBuffer>> buffers;
-    // The free buffers of this stream.  The queue stores indices into the
-    // |buffers| vector.
-    base::queue<size_t> free_buffers;
-  };
-
-  // The stream context of the preview stream.
-  std::unique_ptr<StreamContext> stream_context_;
-
-  // CaptureResult is used to hold the partial capture results for each frame.
+  // CaptureResult is used to hold the pending capture results for each frame.
   struct CaptureResult {
     CaptureResult();
     ~CaptureResult();
@@ -153,14 +209,59 @@
     // The result metadata.  Contains various information about the captured
     // frame.
     cros::mojom::CameraMetadataPtr metadata;
-    // The buffer handle that hold the captured data of this frame.
-    cros::mojom::Camera3StreamBufferPtr buffer;
+    // 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;
   };
 
+  struct StreamContext {
+    StreamContext();
+    ~StreamContext();
+    // The actual pixel format used in the capture request.
+    VideoCaptureFormat capture_format;
+    // The camera HAL stream.
+    cros::mojom::Camera3StreamPtr stream;
+    // The allocated buffers of this stream.
+    std::vector<std::unique_ptr<gfx::GpuMemoryBuffer>> buffers;
+    // The free buffers of this stream.  The queue stores indices into the
+    // |buffers| vector.
+    std::queue<size_t> free_buffers;
+    // The buffers that are registered to the HAL, which can be used as the
+    // output buffers for capture requests.
+    std::queue<size_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_;
+
+  // 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>
+      pending_still_capture_callbacks_;
+
   // 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
@@ -173,8 +274,15 @@
   // |first_frame_shutter_time_|.
   base::TimeTicks first_frame_shutter_time_;
 
-  // Stores the partial capture results of the current in-flight frames.
-  std::map<uint32_t, CaptureResult> partial_results_;
+  // 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 in the capture request.
+  std::vector<cros::mojom::CameraMetadataEntryPtr> capture_settings_override_;
 
   base::WeakPtrFactory<StreamBufferManager> weak_ptr_factory_;
 
diff --git a/media/capture/video/chromeos/stream_buffer_manager_unittest.cc b/media/capture/video/chromeos/stream_buffer_manager_unittest.cc
index a1714d0..dafe3d3c 100644
--- a/media/capture/video/chromeos/stream_buffer_manager_unittest.cc
+++ b/media/capture/video/chromeos/stream_buffer_manager_unittest.cc
@@ -21,6 +21,7 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#include "media/capture/video/blob_utils.h"
 using testing::_;
 using testing::A;
 using testing::AtLeast;
@@ -109,6 +110,10 @@
         std::move(callback_ops_request),
         std::make_unique<MockStreamCaptureInterface>(), device_context_.get(),
         std::make_unique<FakeCameraBufferFactory>(),
+        base::BindRepeating([](const uint8_t* buffer, const uint32_t bytesused,
+                               const VideoCaptureFormat& capture_format) {
+          return mojom::Blob::New();
+        }),
         base::ThreadTaskRunnerHandle::Get());
   }
 
@@ -126,6 +131,39 @@
     }
   }
 
+  cros::mojom::CameraMetadataPtr GetFakeStaticMetadata(
+      int32_t partial_result_count) {
+    cros::mojom::CameraMetadataPtr static_metadata =
+        cros::mojom::CameraMetadata::New();
+    static_metadata->entry_count = 2;
+    static_metadata->entry_capacity = 2;
+    static_metadata->entries =
+        std::vector<cros::mojom::CameraMetadataEntryPtr>();
+
+    cros::mojom::CameraMetadataEntryPtr entry =
+        cros::mojom::CameraMetadataEntry::New();
+    entry->index = 0;
+    entry->tag =
+        cros::mojom::CameraMetadataTag::ANDROID_REQUEST_PARTIAL_RESULT_COUNT;
+    entry->type = cros::mojom::EntryType::TYPE_INT32;
+    entry->count = 1;
+    uint8_t* as_int8 = reinterpret_cast<uint8_t*>(&partial_result_count);
+    entry->data.assign(as_int8, as_int8 + entry->count * sizeof(int32_t));
+    static_metadata->entries->push_back(std::move(entry));
+
+    entry = cros::mojom::CameraMetadataEntry::New();
+    entry->index = 1;
+    entry->tag = cros::mojom::CameraMetadataTag::ANDROID_JPEG_MAX_SIZE;
+    entry->type = cros::mojom::EntryType::TYPE_INT32;
+    entry->count = 1;
+    int32_t jpeg_max_size = 65535;
+    as_int8 = reinterpret_cast<uint8_t*>(&jpeg_max_size);
+    entry->data.assign(as_int8, as_int8 + entry->count * sizeof(int32_t));
+    static_metadata->entries->push_back(std::move(entry));
+
+    return static_metadata;
+  }
+
   void RegisterBuffer(uint64_t buffer_id,
                       cros::mojom::Camera3DeviceOps::BufferType type,
                       uint32_t drm_format,
@@ -165,25 +203,46 @@
         device_context_->client_.get());
   }
 
-  std::map<uint32_t, StreamBufferManager::CaptureResult>& GetPartialResults() {
+  std::map<uint32_t, StreamBufferManager::CaptureResult>& GetPendingResults() {
     EXPECT_NE(nullptr, stream_buffer_manager_.get());
-    return stream_buffer_manager_->partial_results_;
+    return stream_buffer_manager_->pending_results_;
   }
 
-  cros::mojom::Camera3StreamPtr PrepareCaptureStream(uint32_t max_buffers) {
-    auto stream = cros::mojom::Camera3Stream::New();
-    stream->id = 0;
-    stream->stream_type = cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT;
-    stream->width = kDefaultCaptureFormat.frame_size.width();
-    stream->height = kDefaultCaptureFormat.frame_size.height();
-    stream->format =
+  std::vector<cros::mojom::Camera3StreamPtr> PrepareCaptureStream(
+      uint32_t max_buffers) {
+    std::vector<cros::mojom::Camera3StreamPtr> streams;
+
+    auto preview_stream = cros::mojom::Camera3Stream::New();
+    preview_stream->id = static_cast<uint64_t>(StreamType::kPreview);
+    preview_stream->stream_type =
+        cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT;
+    preview_stream->width = kDefaultCaptureFormat.frame_size.width();
+    preview_stream->height = kDefaultCaptureFormat.frame_size.height();
+    preview_stream->format =
         cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_YCbCr_420_888;
-    stream->usage = 0;
-    stream->max_buffers = max_buffers;
-    stream->data_space = 0;
-    stream->rotation =
+    preview_stream->usage = 0;
+    preview_stream->max_buffers = max_buffers;
+    preview_stream->data_space = 0;
+    preview_stream->rotation =
         cros::mojom::Camera3StreamRotation::CAMERA3_STREAM_ROTATION_0;
-    return stream;
+    streams.push_back(std::move(preview_stream));
+
+    auto still_capture_stream = cros::mojom::Camera3Stream::New();
+    still_capture_stream->id = static_cast<uint64_t>(StreamType::kStillCapture);
+    still_capture_stream->stream_type =
+        cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT;
+    still_capture_stream->width = kDefaultCaptureFormat.frame_size.width();
+    still_capture_stream->height = kDefaultCaptureFormat.frame_size.height();
+    still_capture_stream->format =
+        cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_BLOB;
+    still_capture_stream->usage = 0;
+    still_capture_stream->max_buffers = max_buffers;
+    still_capture_stream->data_space = 0;
+    still_capture_stream->rotation =
+        cros::mojom::Camera3StreamRotation::CAMERA3_STREAM_ROTATION_0;
+    streams.push_back(std::move(still_capture_stream));
+
+    return streams;
   }
 
   cros::mojom::Camera3NotifyMsgPtr PrepareErrorNotifyMessage(
@@ -192,7 +251,7 @@
     auto error_msg = cros::mojom::Camera3ErrorMsg::New();
     error_msg->frame_number = frame_number;
     // There is only the preview stream.
-    error_msg->error_stream_id = 1;
+    error_msg->error_stream_id = static_cast<uint64_t>(StreamType::kPreview);
     error_msg->error_code = error_code;
     auto notify_msg = cros::mojom::Camera3NotifyMsg::New();
     notify_msg->message = cros::mojom::Camera3NotifyMsgMessage::New();
@@ -247,18 +306,20 @@
       &StreamBufferManagerTest::QuitCaptureLoop, base::Unretained(this)));
   EXPECT_CALL(
       *GetMockCaptureInterface(),
-      DoRegisterBuffer(0, cros::mojom::Camera3DeviceOps::BufferType::GRALLOC, _,
-                       _, _, _, _, _))
+      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)
       .WillOnce(Invoke(this, &StreamBufferManagerTest::ProcessCaptureRequest));
 
-  stream_buffer_manager_->SetUpStreamAndBuffers(
-      kDefaultCaptureFormat, /* partial_result_count */ 1,
+  stream_buffer_manager_->SetUpStreamsAndBuffers(
+      kDefaultCaptureFormat,
+      GetFakeStaticMetadata(/* partial_result_count */ 1),
       PrepareCaptureStream(/* max_buffers */ 1));
-  stream_buffer_manager_->StartCapture(cros::mojom::CameraMetadata::New());
+  stream_buffer_manager_->StartPreview(cros::mojom::CameraMetadata::New());
 
   // Wait until a captured frame is received by MockVideoCaptureClient.
   DoLoop();
@@ -269,18 +330,19 @@
 TEST_F(StreamBufferManagerTest, PartialResultTest) {
   GetMockVideoCaptureClient()->SetFrameCb(base::BindOnce(
       [](StreamBufferManagerTest* test) {
-        EXPECT_EQ(1u, test->GetPartialResults().size());
+        EXPECT_EQ(1u, test->GetPendingResults().size());
         // Make sure all the three partial metadata are received before the
         // captured result is submitted.
         EXPECT_EQ(
-            3u, test->GetPartialResults()[0].partial_metadata_received.size());
+            3u, test->GetPendingResults()[0].partial_metadata_received.size());
         test->QuitCaptureLoop();
       },
       base::Unretained(this)));
   EXPECT_CALL(
       *GetMockCaptureInterface(),
-      DoRegisterBuffer(0, cros::mojom::Camera3DeviceOps::BufferType::GRALLOC, _,
-                       _, _, _, _, _))
+      DoRegisterBuffer(
+          StreamBufferManager::GetBufferIpcId(StreamType::kPreview, 0),
+          cros::mojom::Camera3DeviceOps::BufferType::GRALLOC, _, _, _, _, _, _))
       .Times(AtLeast(1))
       .WillOnce(Invoke(this, &StreamBufferManagerTest::RegisterBuffer));
   EXPECT_CALL(*GetMockCaptureInterface(), DoProcessCaptureRequest(_, _))
@@ -293,20 +355,19 @@
         mock_callback_ops_->ProcessCaptureResult(PrepareCapturedResult(
             request->frame_number, cros::mojom::CameraMetadata::New(), 1,
             std::move(request->output_buffers)));
-
         mock_callback_ops_->ProcessCaptureResult(PrepareCapturedResult(
             request->frame_number, cros::mojom::CameraMetadata::New(), 2,
             std::vector<cros::mojom::Camera3StreamBufferPtr>()));
-
         mock_callback_ops_->ProcessCaptureResult(PrepareCapturedResult(
             request->frame_number, cros::mojom::CameraMetadata::New(), 3,
             std::vector<cros::mojom::Camera3StreamBufferPtr>()));
       }));
 
-  stream_buffer_manager_->SetUpStreamAndBuffers(
-      kDefaultCaptureFormat, /* partial_result_count */ 3,
+  stream_buffer_manager_->SetUpStreamsAndBuffers(
+      kDefaultCaptureFormat,
+      GetFakeStaticMetadata(/* partial_result_count */ 3),
       PrepareCaptureStream(/* max_buffers */ 1));
-  stream_buffer_manager_->StartCapture(cros::mojom::CameraMetadata::New());
+  stream_buffer_manager_->StartPreview(cros::mojom::CameraMetadata::New());
 
   // Wait until a captured frame is received by MockVideoCaptureClient.
   DoLoop();
@@ -327,8 +388,9 @@
           InvokeWithoutArgs(this, &StreamBufferManagerTest::QuitCaptureLoop));
   EXPECT_CALL(
       *GetMockCaptureInterface(),
-      DoRegisterBuffer(0, cros::mojom::Camera3DeviceOps::BufferType::GRALLOC, _,
-                       _, _, _, _, _))
+      DoRegisterBuffer(
+          StreamBufferManager::GetBufferIpcId(StreamType::kPreview, 0),
+          cros::mojom::Camera3DeviceOps::BufferType::GRALLOC, _, _, _, _, _, _))
       .Times(1)
       .WillOnce(Invoke(this, &StreamBufferManagerTest::RegisterBuffer));
   EXPECT_CALL(*GetMockCaptureInterface(), DoProcessCaptureRequest(_, _))
@@ -341,10 +403,11 @@
             cros::mojom::Camera3ErrorMsgCode::CAMERA3_MSG_ERROR_DEVICE));
       }));
 
-  stream_buffer_manager_->SetUpStreamAndBuffers(
-      kDefaultCaptureFormat, /* partial_result_count */ 1,
+  stream_buffer_manager_->SetUpStreamsAndBuffers(
+      kDefaultCaptureFormat,
+      GetFakeStaticMetadata(/* partial_result_count */ 1),
       PrepareCaptureStream(/* max_buffers */ 1));
-  stream_buffer_manager_->StartCapture(cros::mojom::CameraMetadata::New());
+  stream_buffer_manager_->StartPreview(cros::mojom::CameraMetadata::New());
 
   // Wait until the MockVideoCaptureClient is deleted.
   DoLoop();
@@ -357,17 +420,18 @@
       [](StreamBufferManagerTest* test) {
         // Frame 0 should be dropped, and the frame callback should be called
         // with frame 1.
-        EXPECT_EQ(test->GetPartialResults().end(),
-                  test->GetPartialResults().find(0));
-        EXPECT_NE(test->GetPartialResults().end(),
-                  test->GetPartialResults().find(1));
+        EXPECT_EQ(test->GetPendingResults().end(),
+                  test->GetPendingResults().find(0));
+        EXPECT_NE(test->GetPendingResults().end(),
+                  test->GetPendingResults().find(1));
         test->QuitCaptureLoop();
       },
       base::Unretained(this)));
   EXPECT_CALL(
       *GetMockCaptureInterface(),
-      DoRegisterBuffer(0, cros::mojom::Camera3DeviceOps::BufferType::GRALLOC, _,
-                       _, _, _, _, _))
+      DoRegisterBuffer(
+          StreamBufferManager::GetBufferIpcId(StreamType::kPreview, 0),
+          cros::mojom::Camera3DeviceOps::BufferType::GRALLOC, _, _, _, _, _, _))
       .Times(AtLeast(2))
       .WillOnce(Invoke(this, &StreamBufferManagerTest::RegisterBuffer))
       .WillOnce(Invoke(this, &StreamBufferManagerTest::RegisterBuffer));
@@ -387,10 +451,11 @@
       }))
       .WillOnce(Invoke(this, &StreamBufferManagerTest::ProcessCaptureRequest));
 
-  stream_buffer_manager_->SetUpStreamAndBuffers(
-      kDefaultCaptureFormat, /* partial_result_count */ 1,
+  stream_buffer_manager_->SetUpStreamsAndBuffers(
+      kDefaultCaptureFormat,
+      GetFakeStaticMetadata(/* partial_result_count */ 1),
       PrepareCaptureStream(/* max_buffers */ 1));
-  stream_buffer_manager_->StartCapture(cros::mojom::CameraMetadata::New());
+  stream_buffer_manager_->StartPreview(cros::mojom::CameraMetadata::New());
 
   // Wait until the MockVideoCaptureClient is deleted.
   DoLoop();
@@ -402,19 +467,20 @@
   GetMockVideoCaptureClient()->SetFrameCb(base::BindOnce(
       [](StreamBufferManagerTest* test) {
         // Frame 0 should be submitted.
-        EXPECT_NE(test->GetPartialResults().end(),
-                  test->GetPartialResults().find(0));
+        EXPECT_NE(test->GetPendingResults().end(),
+                  test->GetPendingResults().find(0));
         test->QuitCaptureLoop();
       },
       base::Unretained(this)));
   EXPECT_CALL(
       *GetMockCaptureInterface(),
-      DoRegisterBuffer(0, cros::mojom::Camera3DeviceOps::BufferType::GRALLOC, _,
-                       _, _, _, _, _))
+      DoRegisterBuffer(
+          StreamBufferManager::GetBufferIpcId(StreamType::kPreview, 0),
+          cros::mojom::Camera3DeviceOps::BufferType::GRALLOC, _, _, _, _, _, _))
       .Times(AtLeast(1))
-      .WillOnce(Invoke(this, &StreamBufferManagerTest::RegisterBuffer));
+      .WillRepeatedly(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);
@@ -432,10 +498,11 @@
       }))
       .WillOnce(Invoke(this, &StreamBufferManagerTest::ProcessCaptureRequest));
 
-  stream_buffer_manager_->SetUpStreamAndBuffers(
-      kDefaultCaptureFormat, /* partial_result_count */ 2,
+  stream_buffer_manager_->SetUpStreamsAndBuffers(
+      kDefaultCaptureFormat,
+      GetFakeStaticMetadata(/* partial_result_count */ 2),
       PrepareCaptureStream(/* max_buffers */ 1));
-  stream_buffer_manager_->StartCapture(cros::mojom::CameraMetadata::New());
+  stream_buffer_manager_->StartPreview(cros::mojom::CameraMetadata::New());
 
   // Wait until the MockVideoCaptureClient is deleted.
   DoLoop();
@@ -448,17 +515,18 @@
       [](StreamBufferManagerTest* test) {
         // Frame 0 should be dropped, and the frame callback should be called
         // with frame 1.
-        EXPECT_EQ(test->GetPartialResults().end(),
-                  test->GetPartialResults().find(0));
-        EXPECT_NE(test->GetPartialResults().end(),
-                  test->GetPartialResults().find(1));
+        EXPECT_EQ(test->GetPendingResults().end(),
+                  test->GetPendingResults().find(0));
+        EXPECT_NE(test->GetPendingResults().end(),
+                  test->GetPendingResults().find(1));
         test->QuitCaptureLoop();
       },
       base::Unretained(this)));
   EXPECT_CALL(
       *GetMockCaptureInterface(),
-      DoRegisterBuffer(0, cros::mojom::Camera3DeviceOps::BufferType::GRALLOC, _,
-                       _, _, _, _, _))
+      DoRegisterBuffer(
+          StreamBufferManager::GetBufferIpcId(StreamType::kPreview, 0),
+          cros::mojom::Camera3DeviceOps::BufferType::GRALLOC, _, _, _, _, _, _))
       .Times(AtLeast(2))
       .WillOnce(Invoke(this, &StreamBufferManagerTest::RegisterBuffer))
       .WillOnce(Invoke(this, &StreamBufferManagerTest::RegisterBuffer));
@@ -480,13 +548,50 @@
       }))
       .WillOnce(Invoke(this, &StreamBufferManagerTest::ProcessCaptureRequest));
 
-  stream_buffer_manager_->SetUpStreamAndBuffers(
-      kDefaultCaptureFormat, /* partial_result_count */ 1,
+  stream_buffer_manager_->SetUpStreamsAndBuffers(
+      kDefaultCaptureFormat,
+      GetFakeStaticMetadata(/* partial_result_count */ 1),
       PrepareCaptureStream(/* max_buffers */ 1));
-  stream_buffer_manager_->StartCapture(cros::mojom::CameraMetadata::New());
+  stream_buffer_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));
+  EXPECT_CALL(*GetMockCaptureInterface(), DoProcessCaptureRequest(_, _))
+      .Times(AtLeast(1))
+      .WillRepeatedly(
+          Invoke(this, &StreamBufferManagerTest::ProcessCaptureRequest));
+
+  stream_buffer_manager_->SetUpStreamsAndBuffers(
+      kDefaultCaptureFormat,
+      GetFakeStaticMetadata(/* partial_result_count */ 1),
+      PrepareCaptureStream(/* max_buffers */ 1));
+  stream_buffer_manager_->StartPreview(cros::mojom::CameraMetadata::New());
+  stream_buffer_manager_->TakePhoto(
+      GetFakeStaticMetadata(/* partial_result_count */ 1),
+      base::BindOnce([](StreamBufferManagerTest* test,
+                        mojom::BlobPtr blob) { test->QuitCaptureLoop(); },
+                     base::Unretained(this)));
+
+  // Wait until a captured frame is received by MockVideoCaptureClient.
+  DoLoop();
+}
+
 }  // namespace media
diff --git a/media/capture/video/mock_gpu_memory_buffer_manager.cc b/media/capture/video/mock_gpu_memory_buffer_manager.cc
index 3a37cfc..e69e1150 100644
--- a/media/capture/video/mock_gpu_memory_buffer_manager.cc
+++ b/media/capture/video/mock_gpu_memory_buffer_manager.cc
@@ -6,6 +6,10 @@
 
 #include <memory>
 
+#if defined(OS_CHROMEOS)
+#include "media/capture/video/chromeos/stream_buffer_manager.h"
+#endif
+
 using ::testing::Return;
 
 namespace media {
@@ -17,8 +21,9 @@
  public:
   FakeGpuMemoryBuffer(const gfx::Size& size, gfx::BufferFormat format)
       : size_(size), format_(format) {
-    // We use only NV12 in unit tests.
-    EXPECT_EQ(gfx::BufferFormat::YUV_420_BIPLANAR, format);
+    // We use only NV12 or R8 in unit tests.
+    EXPECT_TRUE(format == gfx::BufferFormat::YUV_420_BIPLANAR ||
+                format == gfx::BufferFormat::R_8);
 
     size_t y_plane_size = size_.width() * size_.height();
     size_t uv_plane_size = size_.width() * size_.height() / 2;
@@ -36,6 +41,13 @@
     handle_.native_pixmap_handle.planes.push_back(gfx::NativePixmapPlane(
         size_.width(), handle_.native_pixmap_handle.planes[0].size,
         uv_plane_size));
+
+    // For faking a valid JPEG blob buffer.
+    Camera3JpegBlob* header = reinterpret_cast<Camera3JpegBlob*>(
+        reinterpret_cast<uintptr_t>(data_.data()) + size_.width() -
+        sizeof(Camera3JpegBlob));
+    header->jpeg_blob_id = kCamera3JpegBlobId;
+    header->jpeg_size = size_.width();
 #endif
   }
 
diff --git a/media/capture/video/video_capture_device_unittest.cc b/media/capture/video/video_capture_device_unittest.cc
index a390e052..2114368 100644
--- a/media/capture/video/video_capture_device_unittest.cc
+++ b/media/capture/video/video_capture_device_unittest.cc
@@ -664,13 +664,6 @@
   if (!descriptor)
     return;
 
-#if defined(OS_CHROMEOS)
-  // TODO(jcliang): Remove this after we implement TakePhoto.
-  if (VideoCaptureDeviceFactoryChromeOS::ShouldEnable()) {
-    return;
-  }
-#endif
-
 #if defined(OS_ANDROID)
   // TODO(mcasas): fails on Lollipop devices, reconnect https://crbug.com/646840
   if (base::android::BuildInfo::GetInstance()->sdk_int() <
@@ -713,13 +706,6 @@
   if (!descriptor)
     return;
 
-#if defined(OS_CHROMEOS)
-  // TODO(jcliang): Remove this after we implement GetPhotoCapabilities.
-  if (VideoCaptureDeviceFactoryChromeOS::ShouldEnable()) {
-    return;
-  }
-#endif
-
 #if defined(OS_ANDROID)
   // TODO(mcasas): fails on Lollipop devices, reconnect https://crbug.com/646840
   if (base::android::BuildInfo::GetInstance()->sdk_int() <
@@ -745,6 +731,9 @@
       base::BindOnce(&MockImageCaptureClient::DoOnGetPhotoState,
                      image_capture_client_);
 
+  // On Chrome OS AllocateAndStart() is asynchronous, so wait until we get the
+  // first frame.
+  WaitForCapturedFrame();
   base::RunLoop run_loop;
   base::Closure quit_closure = BindToCurrentLoop(run_loop.QuitClosure());
   EXPECT_CALL(*image_capture_client_.get(), OnCorrectGetPhotoState())
diff --git a/services/identity/public/cpp/DEPS b/services/identity/public/cpp/DEPS
index b596ac3ee..81ceb59 100644
--- a/services/identity/public/cpp/DEPS
+++ b/services/identity/public/cpp/DEPS
@@ -2,6 +2,7 @@
   "+components/prefs/testing_pref_service.h",
   "+components/signin/core/browser/account_info.h",
   "+components/signin/core/browser/profile_management_switches.h",
+  "+google_apis/gaia/gaia_auth_util.h",
   "+google_apis/gaia/google_service_auth_error.h",
   "+google_apis/gaia/oauth2_token_service.h",
 ]
diff --git a/services/identity/public/cpp/identity_manager.cc b/services/identity/public/cpp/identity_manager.cc
index d84ef79d..9512f72 100644
--- a/services/identity/public/cpp/identity_manager.cc
+++ b/services/identity/public/cpp/identity_manager.cc
@@ -4,6 +4,7 @@
 
 #include "services/identity/public/cpp/identity_manager.h"
 #include "base/threading/sequenced_task_runner_handle.h"
+#include "google_apis/gaia/gaia_auth_util.h"
 
 namespace identity {
 
@@ -43,7 +44,7 @@
   // document the API to use here.
   DCHECK_EQ(signin_manager_->GetAuthenticatedAccountId(),
             primary_account_info_.account_id);
-#if DCHECK_IS_ON()
+
   // Note: If the primary account's refresh token gets revoked, then the account
   // gets removed from AccountTrackerService (via
   // AccountFetcherService::OnRefreshTokenRevoked), and so SigninManager's
@@ -54,10 +55,23 @@
               primary_account_info_.account_id);
     DCHECK_EQ(signin_manager_->GetAuthenticatedAccountInfo().gaia,
               primary_account_info_.gaia);
-    DCHECK_EQ(signin_manager_->GetAuthenticatedAccountInfo().email,
-              primary_account_info_.email);
+
+    // TODO(842670): As described in the bug, AccountTrackerService's email
+    // address can be updated after it is initially set on ChromeOS. Figure out
+    // right long-term solution for this problem.
+    if (signin_manager_->GetAuthenticatedAccountInfo().email !=
+        primary_account_info_.email) {
+      // This update should only be to move it from normalized form to the form
+      // in which the user entered the email when creating the account. The
+      // below check verifies that the normalized forms of the two email
+      // addresses are identical.
+      DCHECK(gaia::AreEmailsSame(
+          signin_manager_->GetAuthenticatedAccountInfo().email,
+          primary_account_info_.email));
+      primary_account_info_.email =
+          signin_manager_->GetAuthenticatedAccountInfo().email;
+    }
   }
-#endif  // DCHECK_IS_ON()
 #endif  // defined(OS_CHROMEOS)
   return primary_account_info_;
 }
diff --git a/services/identity/public/cpp/identity_manager_unittest.cc b/services/identity/public/cpp/identity_manager_unittest.cc
index caa51353..0617583 100644
--- a/services/identity/public/cpp/identity_manager_unittest.cc
+++ b/services/identity/public/cpp/identity_manager_unittest.cc
@@ -24,7 +24,11 @@
 #endif  // OS_CHROMEOS
 
 const char kTestGaiaId[] = "dummyId";
-const char kTestEmail[] = "me@dummy.com";
+const char kTestEmail[] = "me@gmail.com";
+
+#if defined(OS_CHROMEOS)
+const char kTestEmailWithPeriod[] = "m.e@gmail.com";
+#endif
 
 // Subclass of FakeProfileOAuth2TokenService with bespoke behavior.
 class CustomFakeProfileOAuth2TokenService
@@ -61,6 +65,14 @@
   base::OnceClosure on_access_token_invalidated_callback_;
 };
 
+class AccountTrackerServiceForTest : public AccountTrackerService {
+ public:
+  void SetAccountStateFromUserInfo(const std::string& account_id,
+                                   const base::DictionaryValue* user_info) {
+    AccountTrackerService::SetAccountStateFromUserInfo(account_id, user_info);
+  }
+};
+
 class TestSigninManagerObserver : public SigninManagerBase::Observer {
  public:
   explicit TestSigninManagerObserver(SigninManagerBase* signin_manager)
@@ -241,7 +253,7 @@
   identity_manager_diagnostics_observer() {
     return identity_manager_diagnostics_observer_.get();
   }
-  AccountTrackerService* account_tracker() { return &account_tracker_; }
+  AccountTrackerServiceForTest* account_tracker() { return &account_tracker_; }
   SigninManagerForTest* signin_manager() { return &signin_manager_; }
   CustomFakeProfileOAuth2TokenService* token_service() {
     return &token_service_;
@@ -268,7 +280,7 @@
  private:
   base::MessageLoop message_loop_;
   sync_preferences::TestingPrefServiceSyncable pref_service_;
-  AccountTrackerService account_tracker_;
+  AccountTrackerServiceForTest account_tracker_;
   TestSigninClient signin_client_;
   SigninManagerForTest signin_manager_;
   CustomFakeProfileOAuth2TokenService token_service_;
@@ -474,4 +486,34 @@
 }
 #endif
 
+#if defined(OS_CHROMEOS)
+// On ChromeOS, AccountTrackerService first receives the normalized email
+// address from GAIA and then later has it updated with the user's
+// originally-specified version of their email address (at the time of that
+// address' creation). This latter will differ if the user's originally-
+// specified address was not in normalized form (e.g., if it contained
+// periods). This test simulates such a flow in order to verify that
+// IdentityManager correctly reflects the updated version. See crbug.com/842041
+// and crbug.com/842670 for further details.
+TEST_F(IdentityManagerTest, IdentityManagerReflectsUpdatedEmailAddress) {
+  AccountInfo primary_account_info =
+      identity_manager()->GetPrimaryAccountInfo();
+  EXPECT_EQ(kTestGaiaId, primary_account_info.gaia);
+  EXPECT_EQ(kTestEmail, primary_account_info.email);
+
+  // Simulate the flow wherein the user's email address was updated
+  // to the originally-created non-normalized version.
+  base::DictionaryValue user_info;
+  user_info.SetString("id", kTestGaiaId);
+  user_info.SetString("email", kTestEmailWithPeriod);
+  account_tracker()->SetAccountStateFromUserInfo(
+      primary_account_info.account_id, &user_info);
+
+  // Verify that IdentityManager reflects the update.
+  primary_account_info = identity_manager()->GetPrimaryAccountInfo();
+  EXPECT_EQ(kTestGaiaId, primary_account_info.gaia);
+  EXPECT_EQ(kTestEmailWithPeriod, primary_account_info.email);
+}
+#endif
+
 }  // namespace identity
diff --git a/services/service_manager/embedder/main.cc b/services/service_manager/embedder/main.cc
index fdff39ef..a2208da 100644
--- a/services/service_manager/embedder/main.cc
+++ b/services/service_manager/embedder/main.cc
@@ -449,13 +449,6 @@
     case ProcessType::kEmbedder:
       if (delegate->IsEmbedderSubprocess())
         CommonSubprocessInit();
-      else {
-        // TODO(https://crbug.com/729596): Use this task runner to start
-        // ServiceManager.
-        scoped_refptr<base::SingleThreadTaskRunner> task_runner =
-            delegate->GetServiceManagerTaskRunnerForEmbedderProcess();
-      }
-
       exit_code = delegate->RunEmbedderProcess();
       break;
   }
diff --git a/services/service_manager/embedder/main_delegate.cc b/services/service_manager/embedder/main_delegate.cc
index aeef91e..11b7c3fa 100644
--- a/services/service_manager/embedder/main_delegate.cc
+++ b/services/service_manager/embedder/main_delegate.cc
@@ -10,14 +10,6 @@
 
 MainDelegate::~MainDelegate() = default;
 
-scoped_refptr<base::SingleThreadTaskRunner>
-MainDelegate::GetServiceManagerTaskRunnerForEmbedderProcess() {
-  // The default implementation is provided for compiling purpose on Windows and
-  // should never be called.
-  NOTREACHED();
-  return nullptr;
-}
-
 bool MainDelegate::IsEmbedderSubprocess() {
   return false;
 }
diff --git a/services/service_manager/embedder/main_delegate.h b/services/service_manager/embedder/main_delegate.h
index 33b1b47..0c6d11a 100644
--- a/services/service_manager/embedder/main_delegate.h
+++ b/services/service_manager/embedder/main_delegate.h
@@ -8,8 +8,6 @@
 #include <memory>
 
 #include "base/callback_forward.h"
-#include "base/memory/scoped_refptr.h"
-#include "base/single_thread_task_runner.h"
 #include "mojo/edk/embedder/configuration.h"
 #include "services/service_manager/background/background_service_manager.h"
 #include "services/service_manager/embedder/process_type.h"
@@ -49,11 +47,6 @@
   // failure.
   virtual int Initialize(const InitializeParams& params) = 0;
 
-  // Creates an thread and returns the SingleThreadTaskRunner on which
-  // ServiceManager should run.
-  virtual scoped_refptr<base::SingleThreadTaskRunner>
-  GetServiceManagerTaskRunnerForEmbedderProcess();
-
   // Indicates whether this (embedder) process should be treated as a subprocess
   // for the sake of some platform-specific environment initialization details.
   virtual bool IsEmbedderSubprocess();
diff --git a/services/ui/public/cpp/gpu/context_provider_command_buffer.cc b/services/ui/public/cpp/gpu/context_provider_command_buffer.cc
index 312df8f..d71d2b6e 100644
--- a/services/ui/public/cpp/gpu/context_provider_command_buffer.cc
+++ b/services/ui/public/cpp/gpu/context_provider_command_buffer.cc
@@ -268,6 +268,13 @@
       return bind_result_;
     }
 
+    std::string type_name =
+        command_buffer_metrics::ContextTypeToString(context_type_);
+    std::string unique_context_name =
+        base::StringPrintf("%s-%p", type_name.c_str(), raster_impl.get());
+    raster_impl->TraceBeginCHROMIUM("gpu_toplevel",
+                                    unique_context_name.c_str());
+
     impl_ = raster_impl.get();
     raster_interface_ = std::move(raster_impl);
     helper_ = std::move(raster_helper);
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG b/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
index ca76fcf0..be47b65 100644
--- a/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
+++ b/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
@@ -19,7 +19,6 @@
 
 # TODO(skobes): rebaseline
 crbug.com/811429 fast/text/font-format-support-cbdt-sbix-cff2-vertical.html [ Failure ]
-crbug.com/811429 inspector-protocol/layout-fonts/cross-platform-cbdt-sbix-cff2.js [ Failure Pass ]
 
 # Crashes/asserts/failures due to inline item reuse.
 crbug.com/636993 accessibility/removed-continuation-element-causes-crash.html [ Crash ]
@@ -104,9 +103,9 @@
 crbug.com/591099 animations/animation-ready-reject-script-forbidden.html [ Timeout ]
 crbug.com/591099 animations/cross-fade-list-style-image.html [ Failure ]
 crbug.com/591099 animations/interpolation/backdrop-filter-interpolation.html [ Timeout ]
-crbug.com/591099 animations/interpolation/line-height-interpolation.html [ Pass Timeout ]
+crbug.com/591099 animations/interpolation/line-height-interpolation.html [ Timeout ]
 crbug.com/591099 animations/interpolation/svg-stroke-dasharray-interpolation.html [ Timeout ]
-crbug.com/591099 animations/interpolation/webkit-clip-path-interpolation.html [ Pass Timeout ]
+crbug.com/591099 animations/interpolation/webkit-clip-path-interpolation.html [ Timeout ]
 crbug.com/591099 animations/rotate-transform-equivalent.html [ Failure ]
 crbug.com/714962 compositing/background-color/view-blending-base-background.html [ Failure ]
 crbug.com/591099 compositing/draws-content/canvas-background-layer.html [ Failure ]
@@ -230,14 +229,8 @@
 crbug.com/714962 external/wpt/WebCryptoAPI/import_export/test_rsa_importKey.https.html [ Pass Timeout ]
 crbug.com/591099 external/wpt/acid/acid3/numbered-tests.html [ Crash ]
 crbug.com/591099 external/wpt/acid/acid3/test.html [ Crash ]
-crbug.com/591099 external/wpt/console/console-counting-label-conversion.any.html [ Pass ]
-crbug.com/591099 external/wpt/console/console-counting-label-conversion.any.worker.html [ Pass ]
-crbug.com/591099 external/wpt/content-security-policy/navigate-to/meta-refresh-redirected-blocked.sub.html [ Failure Pass Timeout ]
-crbug.com/591099 external/wpt/content-security-policy/reporting/report-original-url.sub.html [ Pass Timeout ]
-crbug.com/591099 external/wpt/cookies/samesite/iframe.html [ Failure Pass ]
 crbug.com/591099 external/wpt/credential-management/federatedcredential-framed-get.sub.https.html [ Pass ]
 crbug.com/591099 external/wpt/credential-management/passwordcredential-framed-get.sub.https.html [ Pass ]
-crbug.com/591099 external/wpt/css/CSS2/floats/floats-wrap-top-below-bfc-001r.xht [ Failure Pass ]
 crbug.com/714962 external/wpt/css/CSS2/linebox/vertical-align-baseline-005a.xht [ Failure ]
 crbug.com/591099 external/wpt/css/CSS2/normal-flow/block-in-inline-insert-001e.xht [ Pass ]
 crbug.com/591099 external/wpt/css/CSS2/normal-flow/block-in-inline-insert-001h.xht [ Pass ]
@@ -245,51 +238,35 @@
 crbug.com/591099 external/wpt/css/CSS2/normal-flow/block-in-inline-nested-002.xht [ Pass ]
 crbug.com/591099 external/wpt/css/CSS2/normal-flow/block-in-inline-remove-006.xht [ Pass ]
 crbug.com/591099 external/wpt/css/CSS2/normal-flow/root-box-001.xht [ Failure ]
-crbug.com/591099 external/wpt/css/CSS2/text/white-space-collapsing-bidi-001.xht [ Pass ]
 crbug.com/591099 external/wpt/css/CSS2/text/white-space-mixed-003.xht [ Pass ]
 crbug.com/714962 external/wpt/css/css-backgrounds/background-attachment-local/attachment-local-clipping-color-5.html [ Failure ]
 crbug.com/714962 external/wpt/css/css-backgrounds/background-attachment-local/attachment-local-clipping-image-5.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-backgrounds/box-shadow-syntax-001.xht [ Failure ]
-crbug.com/591099 external/wpt/css/css-color/t425-hsla-onscreen-b.xht [ Pass ]
 crbug.com/591099 external/wpt/css/css-display/display-contents-details.html [ Crash ]
-crbug.com/591099 external/wpt/css/css-display/display-contents-dynamic-list-001-inline.html [ Failure Pass ]
+crbug.com/591099 external/wpt/css/css-display/display-contents-dynamic-list-001-inline.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-display/display-contents-dynamic-list-001-none.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-display/display-contents-dynamic-table-001-inline.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-display/display-contents-list-001.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-filter/filtered-block-is-container.html [ Pass ]
-crbug.com/591099 external/wpt/css/css-filter/filtered-inline-is-container.html [ Crash Pass ]
-crbug.com/591099 external/wpt/css/css-flexbox/flex-minimum-height-flex-items-008.xht [ Pass ]
+crbug.com/591099 external/wpt/css/css-filter/filtered-inline-is-container.html [ Crash ]
 crbug.com/591099 external/wpt/css/css-flexbox/percentage-heights-001.html [ Failure ]
 crbug.com/714962 external/wpt/css/css-fonts/font-features-across-space-1.html [ Pass ]
 crbug.com/714962 external/wpt/css/css-fonts/font-features-across-space-3.html [ Pass ]
-crbug.com/591099 external/wpt/css/css-fonts/font-variant-05.xht [ Pass ]
-crbug.com/591099 external/wpt/css/css-fonts/font-variant-alternates-14.html [ Pass ]
-crbug.com/591099 external/wpt/css/css-fonts/font-variant-alternates-16.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-fonts/font-variant-ligatures-11.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-fonts/matching/fixed-stretch-style-over-weight.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-fonts/matching/stretch-distance-over-weight-distance.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-fonts/matching/style-ranges-over-weight-direction.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-fonts/test_font_family_parsing.html [ Timeout ]
-crbug.com/591099 external/wpt/css/css-fonts/variations/at-font-face-descriptors.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-fonts/variations/font-parse-numeric-stretch-style-weight.html [ Failure Pass ]
 crbug.com/591099 external/wpt/css/css-grid/alignment/grid-self-alignment-non-static-positioned-items-009.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-grid/alignment/grid-self-alignment-non-static-positioned-items-010.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-grid/alignment/grid-self-alignment-non-static-positioned-items-011.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-007.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-008.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-009.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-010.html [ Failure Pass ]
+crbug.com/591099 external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-009.html [ Failure ]
+crbug.com/591099 external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-010.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-011.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-012.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-layout-api/fallback-layout-invalid-fragment.https.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-layout-api/layout-child-text.https.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-paint-api/geometry-border-image-001.https.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-paint-api/parse-input-arguments-009.https.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-paint-api/parse-input-arguments-019.https.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-paint-api/style-before-pseudo.https.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-position/position-relative-table-thead-left-absolute-child.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-position/position-sticky-writing-modes.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-pseudo/first-letter-001.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-rhythm/ [ Skip ]
 crbug.com/591099 external/wpt/css/css-scroll-anchoring/clipped-scrollers-skipped.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-scroll-anchoring/opt-out.html [ Failure ]
@@ -298,15 +275,14 @@
 crbug.com/714962 external/wpt/css/css-scroll-anchoring/wrapped-text.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-box/shape-outside-box-002.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-box/shape-outside-box-003.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-box/shape-outside-box-004.html [ Failure Pass ]
+crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-box/shape-outside-box-004.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-box/shape-outside-box-006.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-box/shape-outside-box-007.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-box/shape-outside-box-008.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-001.html [ Failure Pass ]
+crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-001.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-002.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-003.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-004.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-016.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-radial-gradient-001.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-radial-gradient-002.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-radial-gradient-003.html [ Failure ]
@@ -326,7 +302,7 @@
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-image/shape-image-014.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-image/shape-image-015.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-image/shape-image-016.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-image/shape-image-017.html [ Failure Pass ]
+crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-image/shape-image-017.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-image/shape-image-018.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-image/shape-image-019.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-image/shape-image-020.html [ Failure ]
@@ -338,7 +314,7 @@
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-013.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-014.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-015.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-016.html [ Failure Pass ]
+crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-016.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-017.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-018.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/circle/shape-outside-circle-019.html [ Failure ]
@@ -357,9 +333,9 @@
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-014.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-015.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-016.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-017.html [ Failure Pass ]
+crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-017.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-018.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-019.html [ Failure Pass ]
+crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-019.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-020.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-021.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/ellipse/shape-outside-ellipse-022.html [ Failure ]
@@ -383,23 +359,21 @@
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/polygon/shape-outside-polygon-010.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/polygon/shape-outside-polygon-011.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/polygon/shape-outside-polygon-012.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/polygon/shape-outside-polygon-013.html [ Failure Pass ]
+crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/polygon/shape-outside-polygon-013.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/polygon/shape-outside-polygon-014.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/polygon/shape-outside-polygon-015.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/polygon/shape-outside-polygon-016.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/polygon/shape-outside-polygon-017.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/values/shape-margin-001.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/values/shape-outside-circle-004.html [ Timeout ]
-crbug.com/591099 external/wpt/css/css-shapes/shape-outside/values/shape-outside-computed-shape-000.html [ Failure Pass ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/values/shape-outside-ellipse-004.html [ Timeout ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/values/shape-outside-inset-003.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/values/shape-outside-shape-arguments-000.html [ Pass ]
-crbug.com/591099 external/wpt/css/css-shapes/spec-examples/shape-outside-001.html [ Failure Pass ]
+crbug.com/591099 external/wpt/css/css-shapes/spec-examples/shape-outside-001.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/spec-examples/shape-outside-002.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/spec-examples/shape-outside-003.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/spec-examples/shape-outside-004.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/spec-examples/shape-outside-005.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-shapes/spec-examples/shape-outside-006.html [ Failure Pass ]
+crbug.com/591099 external/wpt/css/css-shapes/spec-examples/shape-outside-006.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/spec-examples/shape-outside-007.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/spec-examples/shape-outside-008.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/spec-examples/shape-outside-010.html [ Failure ]
@@ -414,7 +388,6 @@
 crbug.com/591099 external/wpt/css/css-shapes/spec-examples/shape-outside-019.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-sizing/intrinsic-percent-non-replaced-001.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-sizing/intrinsic-percent-non-replaced-003.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-tables/fixup-dynamic-anonymous-inline-table-001.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-tables/height-distribution/percentage-sizing-of-table-cell-children.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-tables/table-model-fixup-2.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-tables/width-distribution/td-with-subpixel-padding-vertical-rl.html [ Failure ]
@@ -425,12 +398,12 @@
 crbug.com/591099 external/wpt/css/css-text-decor/text-emphasis-position-above-right-002.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-text-decor/text-emphasis-position-below-left-001.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-text-decor/text-emphasis-position-below-left-002.xht [ Failure ]
-crbug.com/591099 external/wpt/css/css-text-decor/text-emphasis-position-below-right-001.xht [ Failure Pass ]
+crbug.com/591099 external/wpt/css/css-text-decor/text-emphasis-position-below-right-001.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-text-decor/text-emphasis-position-below-right-002.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-text-decor/text-emphasis-style-002.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-text-decor/text-emphasis-style-006.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-text-decor/text-emphasis-style-007.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-text-decor/text-emphasis-style-008.html [ Failure Pass ]
+crbug.com/591099 external/wpt/css/css-text-decor/text-emphasis-style-008.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-text-decor/text-emphasis-style-010.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-text-decor/text-emphasis-style-012.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-text-decor/text-emphasis-style-021.html [ Failure ]
@@ -442,7 +415,6 @@
 crbug.com/591099 external/wpt/css/css-text/i18n/css3-text-line-break-jazh-142.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-text/i18n/css3-text-line-break-opclns-255.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-text/i18n/css3-text-line-break-opclns-259.html [ Pass ]
-crbug.com/591099 external/wpt/css/css-text/letter-spacing/letter-spacing-control-chars-001.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-text/line-breaking/line-breaking-009.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-text/line-breaking/line-breaking-011.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-text/line-breaking/line-breaking-ic-002.html [ Pass ]
@@ -452,30 +424,10 @@
 crbug.com/591099 external/wpt/css/css-text/white-space/seg-break-transformation-011.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-text/white-space/seg-break-transformation-012.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-text/word-break/word-break-break-all-004.html [ Pass ]
-crbug.com/591099 external/wpt/css/css-text/word-break/word-break-normal-lo-000.html [ Pass ]
-crbug.com/714962 external/wpt/css/css-transforms/transform-abspos-006.html [ Failure Pass ]
+crbug.com/714962 external/wpt/css/css-transforms/transform-abspos-006.html [ Failure ]
 crbug.com/714962 external/wpt/css/css-transforms/transform-abspos-007.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-transforms/transform-origin-001.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-transforms/transform-rotate-002.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-transforms/transform-table-005.html [ Failure Pass ]
 crbug.com/591099 external/wpt/css/css-transforms/transform-transformed-tr-percent-height-child.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-transforms/transform3d-matrix3d-002.html [ Failure Pass ]
 crbug.com/591099 external/wpt/css/css-transforms/transform3d-perspective-008.html [ Pass ]
-crbug.com/591099 external/wpt/css/css-transforms/transform3d-translate3d-001.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-transitions/before-load-001.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-typed-om/interfaces.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-typed-om/the-stylepropertymap/properties/border-radius.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-typed-om/the-stylepropertymap/properties/font-stretch.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-typed-om/the-stylepropertymap/properties/line-break.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-typed-om/the-stylepropertymap/properties/line-height.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-typed-om/the-stylepropertymap/properties/page.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-typed-om/the-stylepropertymap/properties/perspective.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-typed-om/the-stylepropertymap/properties/text-decoration-skip.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-typed-om/the-stylepropertymap/properties/word-wrap.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-typed-om/the-stylepropertymap/properties/z-index.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-ui/box-sizing-025.html [ Pass ]
-crbug.com/591099 external/wpt/css/css-ui/outline-006.html [ Pass ]
-crbug.com/591099 external/wpt/css/css-ui/outline-016.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-ui/text-overflow-010.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-ui/text-overflow-015.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/abs-pos-non-replaced-icb-vlr-003.xht [ Pass ]
@@ -561,7 +513,7 @@
 crbug.com/591099 external/wpt/css/css-writing-modes/baseline-inline-non-replaced-004.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/block-flow-direction-vrl-009.xht [ Pass ]
 crbug.com/591099 external/wpt/css/css-writing-modes/block-flow-direction-vrl-026.xht [ Failure ]
-crbug.com/591099 external/wpt/css/css-writing-modes/clearance-calculations-vrl-002.xht [ Failure Pass ]
+crbug.com/591099 external/wpt/css/css-writing-modes/clearance-calculations-vrl-002.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/clearance-calculations-vrl-004.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/clearance-calculations-vrl-006.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/float-contiguous-vrl-002.xht [ Failure ]
@@ -569,7 +521,7 @@
 crbug.com/591099 external/wpt/css/css-writing-modes/float-contiguous-vrl-006.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/float-contiguous-vrl-008.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/float-vlr-013.xht [ Failure ]
-crbug.com/591099 external/wpt/css/css-writing-modes/float-vrl-002.xht [ Failure Pass ]
+crbug.com/591099 external/wpt/css/css-writing-modes/float-vrl-002.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/float-vrl-004.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/float-vrl-006.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/float-vrl-010.xht [ Failure ]
@@ -612,15 +564,13 @@
 crbug.com/714962 external/wpt/css/css-writing-modes/sizing-orthog-prct-htb-in-vrl-003.xht [ Failure ]
 crbug.com/714962 external/wpt/css/css-writing-modes/sizing-orthog-prct-htb-in-vrl-004.xht [ Failure ]
 crbug.com/714962 external/wpt/css/css-writing-modes/sizing-orthog-prct-htb-in-vrl-007.xht [ Failure ]
-crbug.com/714962 external/wpt/css/css-writing-modes/sizing-orthog-prct-htb-in-vrl-008.xht [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-writing-modes/sizing-orthog-vrl-in-htb-004.xht [ Pass ]
+crbug.com/714962 external/wpt/css/css-writing-modes/sizing-orthog-prct-htb-in-vrl-008.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/sizing-orthog-vrl-in-htb-013.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/text-baseline-vlr-007.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/text-baseline-vrl-006.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/text-combine-upright-decorations-001.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/text-combine-upright-layout-rules-001.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/text-orientation-016.xht [ Failure ]
-crbug.com/591099 external/wpt/css/css-writing-modes/vertical-alignment-003.xht [ Pass ]
 crbug.com/591099 external/wpt/css/css-writing-modes/vertical-alignment-005.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/vertical-alignment-007.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/vertical-alignment-vlr-023.xht [ Failure ]
@@ -630,38 +580,27 @@
 crbug.com/591099 external/wpt/css/css-writing-modes/vertical-alignment-vrl-024.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/vertical-alignment-vrl-026.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/wm-propagation-body-006.xht [ Failure ]
-crbug.com/591099 external/wpt/css/cssom-view/window-interface.xht [ Failure Pass ]
 crbug.com/591099 external/wpt/css/cssom/interfaces.html [ Pass Timeout ]
 crbug.com/591099 external/wpt/css/geometry/interfaces.worker.html [ Pass ]
 crbug.com/591099 external/wpt/css/selectors/focus-within-004.html [ Pass ]
-crbug.com/591099 external/wpt/css/selectors/selector-required-type-change-002.html [ Pass ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/dependent-builtin.html [ Pass ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-symbolic.html [ Pass ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-horiz-001b.xhtml [ Pass ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-vert-001a.xhtml [ Failure ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-baseline-multi-item-vert-001b.html [ Pass ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-002.html [ Failure ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-004.html [ Failure ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-003v.html [ Pass ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-005.html [ Pass ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-justify-content-horiz-005.xhtml [ Pass ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-min-height-auto-002c.html [ Pass ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-011.html [ Failure ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-012.html [ Failure ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-014.html [ Failure ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-015.html [ Failure ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-4.html [ Pass ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-6.html [ Pass ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/split-inner-inline-2.html [ Pass ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/writing-modes-3/text-combine-upright-break-inside-001.html [ Failure ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/writing-modes-3/text-combine-upright-compression-007.html [ Failure ]
 crbug.com/591099 external/wpt/dom/nodes/Element-classlist.html [ Timeout ]
-crbug.com/591099 external/wpt/dom/nodes/Element-insertAdjacentText.html [ Failure Pass ]
 crbug.com/591099 external/wpt/dom/nodes/Element-matches.html [ Timeout ]
 crbug.com/591099 external/wpt/dom/nodes/Element-webkitMatchesSelector.html [ Timeout ]
 crbug.com/591099 external/wpt/dom/nodes/Node-compareDocumentPosition.html [ Timeout ]
 crbug.com/591099 external/wpt/dom/nodes/Node-contains.html [ Timeout ]
-crbug.com/591099 external/wpt/dom/nodes/ParentNode-querySelector-All-xht.xht [ Failure Timeout ]
+crbug.com/591099 external/wpt/dom/nodes/ParentNode-querySelector-All-xht.xht [ Timeout ]
 crbug.com/591099 external/wpt/dom/nodes/ParentNode-querySelector-All.html [ Timeout ]
 crbug.com/591099 external/wpt/dom/ranges/Range-compareBoundaryPoints.html [ Timeout ]
 crbug.com/591099 external/wpt/dom/ranges/Range-comparePoint.html [ Timeout ]
@@ -689,7 +628,7 @@
 crbug.com/591099 external/wpt/editing/run/inserttext.html [ Timeout ]
 crbug.com/591099 external/wpt/editing/run/insertunorderedlist.html [ Timeout ]
 crbug.com/591099 external/wpt/editing/run/italic.html [ Timeout ]
-crbug.com/591099 external/wpt/editing/run/justifycenter.html [ Failure Timeout ]
+crbug.com/591099 external/wpt/editing/run/justifycenter.html [ Timeout ]
 crbug.com/591099 external/wpt/editing/run/justifyfull.html [ Timeout ]
 crbug.com/591099 external/wpt/editing/run/justifyleft.html [ Timeout ]
 crbug.com/591099 external/wpt/editing/run/justifyright.html [ Timeout ]
@@ -697,10 +636,9 @@
 crbug.com/591099 external/wpt/editing/run/outdent.html [ Timeout ]
 crbug.com/591099 external/wpt/editing/run/removeformat.html [ Timeout ]
 crbug.com/591099 external/wpt/editing/run/strikethrough.html [ Timeout ]
-crbug.com/591099 external/wpt/editing/run/subscript.html [ Failure Timeout ]
-crbug.com/591099 external/wpt/editing/run/superscript.html [ Timeout ]
+crbug.com/591099 external/wpt/editing/run/subscript.html [ Pass Timeout ]
+crbug.com/591099 external/wpt/editing/run/superscript.html [ Pass Timeout ]
 crbug.com/591099 external/wpt/editing/run/underline.html [ Timeout ]
-crbug.com/591099 external/wpt/editing/run/unlink.html [ Failure Pass ]
 crbug.com/591099 external/wpt/encoding/api-invalid-label.html [ Timeout ]
 crbug.com/591099 external/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-decode-cseucpkdfmtjapanese.html [ Timeout ]
 crbug.com/591099 external/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-decode-x-euc-jp.html [ Timeout ]
@@ -794,25 +732,12 @@
 crbug.com/591099 external/wpt/encoding/legacy-mb-tchinese/big5/big5-encode-href-errors-misc.html [ Timeout ]
 crbug.com/591099 external/wpt/encoding/legacy-mb-tchinese/big5/big5-encode-href.html [ Timeout ]
 crbug.com/591099 external/wpt/encoding/textdecoder-fatal-single-byte.html [ Timeout ]
-crbug.com/591099 external/wpt/fetch/api/request/request-disturbed.html [ Failure Pass ]
-crbug.com/591099 external/wpt/fetch/api/response/response-clone.html [ Failure Pass ]
-crbug.com/591099 external/wpt/fetch/data-urls/processing.any.html [ Failure Pass ]
-crbug.com/591099 external/wpt/fetch/http-cache/vary.html [ Failure Pass ]
-crbug.com/591099 external/wpt/fullscreen/api/element-request-fullscreen-timing-manual.html [ Failure Pass ]
-crbug.com/591099 external/wpt/geolocation-API/PositionOptions.https.html [ Failure ]
-crbug.com/591099 external/wpt/graphics-aam/graphics-object_on_html_element-manual.html [ Failure Pass ]
+crbug.com/591099 external/wpt/geolocation-API/PositionOptions.https.html [ Failure Pass ]
 crbug.com/591099 external/wpt/html-media-capture/capture_audio_cancel-manual.html [ Failure ]
 crbug.com/591099 external/wpt/html-media-capture/capture_image_cancel-manual.html [ Failure ]
 crbug.com/591099 external/wpt/html-media-capture/capture_video_cancel-manual.html [ Failure ]
-crbug.com/591099 external/wpt/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-abort/javascript-url-abort-return-value-string.tentative.html [ Failure Pass ]
-crbug.com/591099 external/wpt/html/browsers/browsing-the-web/unloading-documents/003.html [ Failure Pass ]
-crbug.com/591099 external/wpt/html/browsers/history/the-location-interface/location-stringifier.html [ Failure Pass ]
-crbug.com/591099 external/wpt/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_2.html [ Failure Pass ]
 crbug.com/591099 external/wpt/html/browsers/windows/noreferrer-window-name.html [ Timeout ]
 crbug.com/591099 external/wpt/html/dom/documents/dom-tree-accessors/Document.currentScript.html [ Pass ]
-crbug.com/591099 external/wpt/html/dom/dynamic-markup-insertion/document-write/write-active-document.html [ Failure Pass ]
-crbug.com/591099 external/wpt/html/dom/dynamic-markup-insertion/opening-the-input-stream/008.html [ Failure Pass ]
-crbug.com/591099 external/wpt/html/dom/dynamic-markup-insertion/opening-the-input-stream/015.html [ Failure Pass ]
 crbug.com/591099 external/wpt/html/dom/interfaces.https.html [ Timeout ]
 crbug.com/591099 external/wpt/html/editing/editing-0/autocapitalization/autocapitalize.html [ Pass Timeout ]
 crbug.com/591099 external/wpt/html/infrastructure/urls/resolving-urls/query-encoding/utf-16be.html [ Timeout ]
@@ -822,41 +747,26 @@
 crbug.com/591099 external/wpt/html/infrastructure/urls/resolving-urls/query-encoding/windows-1252.html [ Timeout ]
 crbug.com/591099 external/wpt/html/rendering/non-replaced-elements/flow-content-0/dialog.html [ Failure ]
 crbug.com/591099 external/wpt/html/rendering/non-replaced-elements/the-fieldset-element-0/legend-block-formatting-context.html [ Failure ]
-crbug.com/591099 external/wpt/html/rendering/non-replaced-elements/the-fieldset-element-0/legend.html [ Failure Pass ]
 crbug.com/591099 external/wpt/html/rendering/replaced-elements/svg-embedded-sizing/svg-in-img-auto.html [ Failure ]
 crbug.com/591099 external/wpt/html/rendering/replaced-elements/svg-embedded-sizing/svg-in-img-percentage.html [ Failure ]
 crbug.com/591099 external/wpt/html/rendering/replaced-elements/svg-inline-sizing/svg-inline.html [ Timeout ]
 crbug.com/591099 external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-webvtt-non-snap-to-lines.html [ Failure ]
-crbug.com/591099 external/wpt/html/semantics/embedded-content/the-iframe-element/iframe-load-event.html [ Failure Pass ]
-crbug.com/591099 external/wpt/html/semantics/forms/autofocus/first-when-later-but-before.html [ Failure Pass ]
 crbug.com/591099 external/wpt/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-menu.html [ Failure ]
 crbug.com/591099 external/wpt/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-skip-no-boxes.html [ Failure ]
 crbug.com/591099 external/wpt/html/semantics/interactive-elements/the-dialog-element/abspos-dialog-layout.html [ Failure ]
 crbug.com/591099 external/wpt/html/semantics/interactive-elements/the-dialog-element/centering.html [ Crash ]
-crbug.com/591099 external/wpt/html/semantics/scripting-1/the-script-element/fetch-src/failure.html [ Failure Pass ]
-crbug.com/591099 external/wpt/html/semantics/scripting-1/the-script-element/module/referrer-origin-when-cross-origin.sub.html [ Failure Pass ]
-crbug.com/591099 external/wpt/html/semantics/selectors/pseudo-classes/dir.html [ Failure Pass ]
-crbug.com/591099 external/wpt/html/semantics/selectors/pseudo-classes/enabled.html [ Failure Pass ]
-crbug.com/591099 external/wpt/html/syntax/parsing/html5lib_innerHTML_adoption01.html [ Failure Pass ]
-crbug.com/591099 external/wpt/html/syntax/parsing/html5lib_tests11.html?run_type=write [ Failure Pass ]
-crbug.com/591099 external/wpt/html/syntax/parsing/html5lib_tests11.html?run_type=write_single [ Pass ]
 crbug.com/591099 external/wpt/html/syntax/parsing/named-character-references.html [ Timeout ]
 crbug.com/591099 external/wpt/html/the-xhtml-syntax/parsing-xhtml-documents/xhtml-mathml-dtd-entity-1.htm [ Pass Timeout ]
 crbug.com/591099 external/wpt/html/the-xhtml-syntax/parsing-xhtml-documents/xhtml-mathml-dtd-entity-4.htm [ Pass Timeout ]
 crbug.com/591099 external/wpt/html/the-xhtml-syntax/parsing-xhtml-documents/xhtml-mathml-dtd-entity-7.htm [ Pass Timeout ]
-crbug.com/591099 external/wpt/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-2.html [ Failure Pass ]
-crbug.com/591099 external/wpt/infrastructure/reftest/reftest.https.html [ Failure Pass ]
 crbug.com/591099 external/wpt/longtask-timing/longtask-in-sibling-iframe.html [ Pass Timeout ]
-crbug.com/591099 external/wpt/media-source/mediasource-addsourcebuffer-mode.html [ Failure Pass ]
 crbug.com/591099 external/wpt/media-source/mediasource-getvideoplaybackquality.html [ Timeout ]
 crbug.com/591099 external/wpt/mimesniff/mime-types/parsing.any.html [ Timeout ]
 crbug.com/591099 external/wpt/mimesniff/mime-types/parsing.any.worker.html [ Timeout ]
-crbug.com/591099 external/wpt/mixed-content/video-tag/no-opt-in/cross-origin-http/top-level/swap-scheme-redirect/optionally-blockable/no-opt-in-allows.https.html [ Failure Pass ]
 crbug.com/591099 external/wpt/offscreen-canvas/fill-and-stroke-styles/2d.pattern.basic.nocontext.worker.html [ Crash ]
 crbug.com/591099 external/wpt/offscreen-canvas/the-offscreen-canvas/offscreencanvas.getcontext.worker.html [ Pass ]
 crbug.com/591099 external/wpt/payment-request/payment-allowed-by-feature-policy.https.sub.html [ Pass ]
 crbug.com/591099 external/wpt/payment-request/payment-disabled-by-feature-policy.https.sub.html [ Pass ]
-crbug.com/591099 external/wpt/pointerevents/extension/pointerevent_coalesced_events_attributes-manual.html [ Failure Pass ]
 crbug.com/591099 external/wpt/pointerevents/pointerevent_attributes_hoverable_pointers-manual.html [ Timeout ]
 crbug.com/591099 external/wpt/pointerevents/pointerevent_click_during_capture-manual.html [ Crash Timeout ]
 crbug.com/591099 external/wpt/pointerevents/pointerevent_pointerleave_pen-manual.html [ Failure ]
@@ -870,7 +780,6 @@
 crbug.com/591099 external/wpt/requestidlecallback/callback-invoked.html [ Pass ]
 crbug.com/591099 external/wpt/requestidlecallback/cancel-invoked.html [ Pass ]
 crbug.com/591099 external/wpt/requestidlecallback/idlharness.html [ Pass ]
-crbug.com/591099 external/wpt/secure-contexts/basic-shared-worker.html [ Failure Pass ]
 crbug.com/591099 external/wpt/selection/addRange-00.html [ Timeout ]
 crbug.com/591099 external/wpt/selection/addRange-04.html [ Timeout ]
 crbug.com/591099 external/wpt/selection/addRange-12.html [ Pass Timeout ]
@@ -886,81 +795,39 @@
 crbug.com/591099 external/wpt/selection/extend-00.html [ Timeout ]
 crbug.com/591099 external/wpt/selection/extend-20.html [ Timeout ]
 crbug.com/591099 external/wpt/selection/selectAllChildren.html [ Timeout ]
-crbug.com/591099 external/wpt/server-timing/service_worker_idl.html [ Failure Pass ]
-crbug.com/591099 external/wpt/service-workers/service-worker/svg-target-reftest.https.html [ Failure Pass ]
 crbug.com/591099 external/wpt/shadow-dom/DocumentOrShadowRoot-prototype-elementFromPoint.html [ Failure ]
-crbug.com/591099 external/wpt/shadow-dom/historical.html [ Failure Pass ]
-crbug.com/591099 external/wpt/streams/readable-streams/constructor.dedicatedworker.html [ Failure Pass ]
-crbug.com/591099 external/wpt/streams/writable-streams/constructor.html [ Failure Pass ]
 crbug.com/591099 external/wpt/svg/interfaces.html [ Timeout ]
 crbug.com/591099 external/wpt/svg/linking/reftests/href-filter-element.html [ Failure ]
-crbug.com/591099 external/wpt/svg/path/bearing/relative.svg [ Pass ]
-crbug.com/591099 external/wpt/svg/types/scripted/SVGLength-px.html [ Failure Pass ]
 crbug.com/591099 external/wpt/url/url-setters.html [ Pass Timeout ]
-crbug.com/591099 external/wpt/vibration/silent-ignore.html [ Failure Pass ]
 crbug.com/591099 external/wpt/wasm/wasm_local_iframe_test.html [ Failure ]
 crbug.com/591099 external/wpt/web-animations/animation-model/animation-types/interpolation-per-property.html [ Timeout ]
-crbug.com/591099 external/wpt/web-animations/interfaces/Animation/idlharness.html [ Failure Pass ]
-crbug.com/591099 external/wpt/web-animations/interfaces/KeyframeEffect/constructor.html [ Failure Pass ]
 crbug.com/591099 external/wpt/webaudio/idlharness.https.html [ Timeout ]
 crbug.com/591099 external/wpt/webmessaging/broadcastchannel/sandbox.html [ Failure ]
-crbug.com/591099 external/wpt/webrtc/RTCConfiguration-iceTransportPolicy.html [ Failure Pass ]
-crbug.com/591099 external/wpt/webrtc/RTCPeerConnection-addIceCandidate.html [ Failure Pass ]
-crbug.com/591099 external/wpt/webrtc/RTCPeerConnection-getDefaultIceServers.html [ Failure Pass ]
-crbug.com/591099 external/wpt/webrtc/RTCPeerConnection-iceConnectionState.html [ Failure Pass ]
-crbug.com/591099 external/wpt/webrtc/RTCRtpTransceiver-setDirection.html [ Failure Pass ]
 crbug.com/591099 external/wpt/webrtc/interfaces.html [ Pass Timeout ]
 crbug.com/591099 external/wpt/webrtc/protocol/video-codecs.html [ Failure ]
-crbug.com/591099 external/wpt/websockets/Create-Secure-url-with-space.any.worker.html [ Failure Pass ]
-crbug.com/591099 external/wpt/websockets/interfaces/WebSocket/close/close-connecting.html?wss [ Failure Pass ]
 crbug.com/591099 external/wpt/webstorage/storage_setitem.html [ Pass Timeout ]
 crbug.com/591099 external/wpt/webvr/idlharness.https.html [ Pass ]
-crbug.com/591099 external/wpt/webvtt/api/VTTCue/constructor.html [ Failure Pass ]
-crbug.com/591099 external/wpt/webvtt/parsing/file-parsing/tests/settings-line.html [ Failure Pass ]
-crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/2_cues_overlapping_completely_move_up.html [ Failure Pass ]
+crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/2_cues_overlapping_completely_move_up.html [ Failure ]
 crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/2_cues_overlapping_partially_move_up.html [ Failure ]
-crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/align_middle.html [ Pass ]
 crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/bidi/bidi_ruby.html [ Failure ]
-crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/bidi/u002E_u2028_u05D0.html [ Pass ]
-crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/bidi/u06E9_no_strong_dir.html [ Pass ]
-crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/dom_override_cue_text.html [ Pass ]
 crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/evil/9_cues_overlapping_completely.html [ Failure ]
 crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/evil/9_cues_overlapping_completely_all_cues_have_same_timestamp.html [ Failure ]
-crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/line_0_is_top.html [ Pass ]
-crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/line_integer_and_percent_mixed_overlap.html [ Pass ]
-crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue/color_hex.html [ Pass ]
 crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue/font_properties.html [ Failure ]
 crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue/font_shorthand.html [ Failure ]
-crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue/text-decoration_overline.html [ Pass ]
 crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/bold_object/bold_font_properties.html [ Failure ]
 crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/bold_object/bold_font_shorthand.html [ Failure ]
-crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/bold_object/bold_white-space_nowrap.html [ Pass ]
 crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/class_object/class_font_properties.html [ Failure ]
-crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/class_object/class_font_shorthand.html [ Failure Pass ]
+crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/class_object/class_font_shorthand.html [ Failure ]
 crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/font_properties.html [ Failure ]
 crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/font_shorthand.html [ Failure ]
 crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/italic_object/italic_font_properties.html [ Failure ]
 crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/italic_object/italic_font_shorthand.html [ Failure ]
-crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/italic_object/italic_text-decoration_line-through.html [ Pass ]
-crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/type_selector_root.html [ Pass ]
 crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/underline_object/underline_font_properties.html [ Failure ]
 crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/underline_object/underline_font_shorthand.html [ Failure ]
-crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/underline_object/underline_timestamp_past.html [ Pass ]
-crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/underline_object/underline_white-space_normal_wrapped.html [ Pass ]
-crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/voice_object/voice_animation_with_timestamp.html [ Pass ]
-crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/voice_object/voice_font_properties.html [ Failure Pass ]
+crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/voice_object/voice_font_properties.html [ Failure ]
 crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/voice_object/voice_font_shorthand.html [ Failure ]
-crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/white-space_pre-line_wrapped.html [ Pass ]
-crbug.com/591099 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/default_styles/underline_object_default_font-style.html [ Pass ]
 crbug.com/591099 external/wpt/workers/Worker_terminate_event_queue.htm [ Timeout ]
-crbug.com/591099 external/wpt/workers/constructors/Worker/DedicatedWorkerGlobalScope-members.worker.html [ Failure Pass ]
-crbug.com/591099 external/wpt/workers/interfaces/WorkerUtils/importScripts/002.worker.html [ Failure Pass ]
-crbug.com/591099 external/wpt/workers/semantics/encodings/001.html [ Failure Pass ]
-crbug.com/591099 external/wpt/xhr/overridemimetype-edge-cases.window.html [ Failure Pass ]
-crbug.com/591099 external/wpt/xhr/send-authentication-cors-setrequestheader-no-cred.htm [ Failure Pass ]
 crbug.com/591099 external/wpt/xhr/send-authentication-prompt-2-manual.htm [ Failure ]
-crbug.com/591099 external/wpt/xhr/send-content-type-charset.htm [ Failure Pass ]
-crbug.com/591099 external/wpt/xhr/send-no-response-event-order.htm [ Failure Pass ]
 crbug.com/591099 fast/backgrounds/background-clip-text.html [ Failure ]
 crbug.com/591099 fast/backgrounds/background-leakage-transforms.html [ Failure ]
 crbug.com/591099 fast/backgrounds/border-radius-split-background-image.html [ Failure ]
@@ -1132,7 +999,7 @@
 crbug.com/591099 fast/dom/Window/window-lookup-precedence.html [ Timeout ]
 crbug.com/591099 fast/dom/Window/window-postmessage-clone-deep-array.html [ Failure ]
 crbug.com/591099 fast/dom/domstring-attribute-reflection.html [ Timeout ]
-crbug.com/591099 fast/dom/element-attribute-js-null.html [ Pass Timeout ]
+crbug.com/591099 fast/dom/element-attribute-js-null.html [ Timeout ]
 crbug.com/714962 fast/dom/elementFromPoint-relative-to-viewport.html [ Failure ]
 crbug.com/714962 fast/dom/elementsFromPoint/elementsFromPoint-inline.html [ Failure ]
 crbug.com/714962 fast/dom/inert/inert-inlines.html [ Failure ]
@@ -1162,7 +1029,7 @@
 crbug.com/591099 fast/events/onclick-list-marker.html [ Failure ]
 crbug.com/591099 fast/events/pointerevents/mouse-pointer-capture-transition-events.html [ Timeout ]
 crbug.com/591099 fast/events/pointerevents/mouse-pointer-event-properties.html [ Timeout ]
-crbug.com/591099 fast/events/pointerevents/touch-capture.html [ Pass Timeout ]
+crbug.com/591099 fast/events/pointerevents/touch-capture.html [ Timeout ]
 crbug.com/591099 fast/events/select-element.html [ Timeout ]
 crbug.com/591099 fast/events/sequential-focus-navigation-starting-point.html [ Failure ]
 crbug.com/591099 fast/events/touch/compositor-touch-hit-rects.html [ Failure ]
@@ -1184,7 +1051,7 @@
 crbug.com/591099 fast/forms/selection-direction.html [ Timeout ]
 crbug.com/591099 fast/forms/suggestion-picker/date-suggestion-picker-appearance-zoom125.html [ Failure ]
 crbug.com/591099 fast/forms/suggestion-picker/date-suggestion-picker-appearance-zoom200.html [ Failure ]
-crbug.com/591099 fast/forms/text-control-intrinsic-widths.html [ Timeout ]
+crbug.com/591099 fast/forms/text-control-intrinsic-widths.html [ Pass Timeout ]
 crbug.com/591099 fast/forms/textarea/textarea-align.html [ Failure ]
 crbug.com/591099 fast/forms/textarea/textarea-metrics.html [ Timeout ]
 crbug.com/591099 fast/forms/time-multiple-fields/time-multiple-fields-stepup-stepdown-from-renderer.html [ Timeout ]
@@ -1520,15 +1387,15 @@
 crbug.com/591099 fast/text/place-rtl-ellipsis-in-inline-blocks-align-left.html [ Failure ]
 crbug.com/591099 fast/text/place-rtl-ellipsis-in-inline-blocks-align-right.html [ Failure ]
 crbug.com/591099 fast/text/place-rtl-ellipsis-in-inline-blocks.html [ Failure ]
-crbug.com/714962 fast/text/selection/flexbox-selection-nested.html [ Failure ]
+crbug.com/714962 fast/text/selection/flexbox-selection-nested.html [ Failure Pass ]
 crbug.com/591099 fast/text/selection/flexbox-selection.html [ Failure ]
 crbug.com/591099 fast/text/selection/justified-selection-at-edge.html [ Failure ]
 crbug.com/591099 fast/text/selection/khmer-selection.html [ Failure ]
 crbug.com/714962 fast/text/selection/pre-wrap-overflow-selection.html [ Failure ]
 crbug.com/591099 fast/text/selection/selection-hard-linebreak.html [ Failure ]
-crbug.com/591099 fast/text/selection/selection-rect-rounding.html [ Failure ]
+crbug.com/591099 fast/text/selection/selection-rect-rounding.html [ Failure Pass ]
 crbug.com/591099 fast/text/selection/selection-with-inline-padding.html [ Failure ]
-crbug.com/714962 fast/text/selection/shaping-selection-rect.html [ Failure ]
+crbug.com/714962 fast/text/selection/shaping-selection-rect.html [ Failure Pass ]
 crbug.com/591099 fast/text/text-combine-first-line-crash.html [ Crash ]
 crbug.com/714962 fast/text/unicode-fallback-font.html [ Failure ]
 crbug.com/591099 fast/text/whitespace/018.html [ Failure ]
@@ -1605,20 +1472,17 @@
 crbug.com/591099 http/tests/devtools/elements/highlight/highlight-css-shapes-outside.js [ Failure ]
 crbug.com/714962 http/tests/devtools/elements/inspect-pseudo-element.js [ Timeout ]
 crbug.com/591099 http/tests/devtools/elements/styles-2/paste-property.js [ Timeout ]
-crbug.com/591099 http/tests/devtools/elements/styles-3/styles-add-blank-property.js [ Pass Timeout ]
+crbug.com/591099 http/tests/devtools/elements/styles-3/styles-add-blank-property.js [ Timeout ]
 crbug.com/591099 http/tests/devtools/elements/styles-3/styles-add-new-rule-colon.js [ Pass Timeout ]
 crbug.com/591099 http/tests/devtools/elements/styles-3/styles-add-new-rule-tab.js [ Timeout ]
 crbug.com/591099 http/tests/devtools/elements/styles-3/styles-add-new-rule.js [ Timeout ]
 crbug.com/591099 http/tests/devtools/elements/styles-3/styles-change-node-while-editing.js [ Failure Pass ]
 crbug.com/591099 http/tests/devtools/elements/styles-3/styles-disable-inherited.js [ Failure ]
-crbug.com/591099 http/tests/devtools/elements/styles-4/styles-do-not-detach-sourcemap-on-edits.js [ Pass Timeout ]
 crbug.com/591099 http/tests/devtools/elements/styles-4/styles-formatting.js [ Timeout ]
 crbug.com/591099 http/tests/devtools/elements/styles-4/undo-add-new-rule.js [ Pass Timeout ]
 crbug.com/591099 http/tests/devtools/elements/styles-4/undo-add-property.js [ Pass Timeout ]
 crbug.com/591099 http/tests/devtools/network/network-datareceived.js [ Failure ]
 crbug.com/591099 http/tests/devtools/persistence/persistence-merge-editor-tabs.js [ Failure ]
-crbug.com/591099 http/tests/devtools/sources/debugger-breakpoints/dom-breakpoints.js [ Pass Timeout ]
-crbug.com/591099 http/tests/devtools/sources/debugger-frameworks/frameworks-skip-step-in.js [ Pass Timeout ]
 crbug.com/591099 http/tests/devtools/sources/debugger-ui/debugger-inline-values.js [ Failure Pass ]
 crbug.com/591099 http/tests/devtools/sources/debugger/source-frame-breakpoint-decorations.js [ Failure Pass ]
 crbug.com/591099 http/tests/devtools/text-autosizing-override.js [ Failure ]
@@ -1926,7 +1790,7 @@
 crbug.com/591099 paint/selection/text-selection-newline-vertical-lr.html [ Failure ]
 crbug.com/591099 paint/selection/text-selection-newline-vertical-rl.html [ Failure ]
 crbug.com/591099 paint/selection/text-selection-newline.html [ Failure ]
-crbug.com/714962 paint/selection/text-selection-update-style.html [ Failure ]
+crbug.com/714962 paint/selection/text-selection-update-style.html [ Failure Pass ]
 crbug.com/591099 paint/text/selection-no-clip-text.html [ Failure ]
 crbug.com/714962 paint/text/text-match-highlights-big-line-height.html [ Failure ]
 crbug.com/591099 payments/payment-request-in-iframe-nested-not-allowed.html [ Failure ]
@@ -1939,7 +1803,7 @@
 crbug.com/591099 storage/indexeddb/cursor-continue-validity.html [ Timeout ]
 crbug.com/591099 storage/indexeddb/cursor-key-order.html [ Timeout ]
 crbug.com/591099 storage/indexeddb/deleted-objects.html [ Pass Timeout ]
-crbug.com/591099 storage/indexeddb/exceptions.html [ Timeout ]
+crbug.com/591099 storage/indexeddb/exceptions.html [ Pass Timeout ]
 crbug.com/591099 storage/indexeddb/index-cursor.html [ Timeout ]
 crbug.com/591099 storage/indexeddb/keypath-basics.html [ Pass Timeout ]
 crbug.com/591099 storage/indexeddb/mozilla/cursors.html [ Timeout ]
@@ -1963,7 +1827,7 @@
 crbug.com/591099 svg/parser/whitespace-length-invalid-1.html [ Pass Timeout ]
 crbug.com/591099 svg/parser/whitespace-length-invalid-2.html [ Pass Timeout ]
 crbug.com/591099 svg/parser/whitespace-length-invalid-3.html [ Pass Timeout ]
-crbug.com/591099 svg/parser/whitespace-length-invalid-4.html [ Timeout ]
+crbug.com/591099 svg/parser/whitespace-length-invalid-4.html [ Pass Timeout ]
 crbug.com/591099 svg/parser/whitespace-number.html [ Timeout ]
 crbug.com/591099 svg/text/foreignObject-text-clipping-bug.xml [ Failure ]
 crbug.com/714962 svg/text/tspan-multiple-outline.svg [ Failure ]
@@ -2029,13 +1893,6 @@
 crbug.com/591099 virtual/gpu/fast/canvas/OffscreenCanvas-2d-pattern-in-worker.html [ Pass ]
 crbug.com/591099 virtual/gpu/fast/canvas/canvas-drawImage-video-imageSmoothingEnabled.html [ Pass ]
 crbug.com/591099 virtual/gpu/fast/canvas/canvas-imageSmoothingQuality.html [ Pass ]
-crbug.com/591099 virtual/incremental-shadow-dom/external/wpt/shadow-dom/DocumentOrShadowRoot-prototype-elementFromPoint.html [ Failure ]
-crbug.com/714962 virtual/incremental-shadow-dom/fast/dom/shadow/scrollbar.html [ Crash ]
-crbug.com/591099 virtual/incremental-shadow-dom/fast/dom/shadow/selections-in-shadow.html [ Pass Timeout ]
-crbug.com/591099 virtual/incremental-shadow-dom/html/details_summary/details-writing-mode-align-center.html [ Failure ]
-crbug.com/591099 virtual/incremental-shadow-dom/html/details_summary/details-writing-mode-align-left.html [ Failure ]
-crbug.com/591099 virtual/incremental-shadow-dom/html/details_summary/details-writing-mode-align-right.html [ Failure ]
-crbug.com/591099 virtual/incremental-shadow-dom/html/details_summary/details-writing-mode.html [ Failure ]
 crbug.com/591099 virtual/layout_ng/ [ Skip ]
 crbug.com/824918 virtual/layout_ng_experimental/ [ Skip ]
 crbug.com/836297 virtual/layout_ng_experimental/printing/webgl-oversized-printing.html [ Pass Timeout ]
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-gen-property-trees b/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-gen-property-trees
index 92a2c7d..cdcb3e92 100644
--- a/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-gen-property-trees
+++ b/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-gen-property-trees
@@ -92,7 +92,6 @@
 Bug(none) virtual/htxg-with-network-service/ [ Skip ]
 Bug(none) virtual/htxg/ [ Skip ]
 Bug(none) virtual/import-meta-url/ [ Skip ]
-Bug(none) virtual/incremental-shadow-dom/ [ Skip ]
 Bug(none) virtual/layout_ng/ [ Skip ]
 Bug(none) virtual/layout_ng_experimental/ [ Skip ]
 Bug(none) virtual/linux-subpixel/ [ Skip ]
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/enable-slimming-paint-v2 b/third_party/WebKit/LayoutTests/FlagExpectations/enable-slimming-paint-v2
index 78d13318..def8b8a 100644
--- a/third_party/WebKit/LayoutTests/FlagExpectations/enable-slimming-paint-v2
+++ b/third_party/WebKit/LayoutTests/FlagExpectations/enable-slimming-paint-v2
@@ -73,7 +73,6 @@
 Bug(none) virtual/exotic-color-space/ [ Skip ]
 Bug(none) virtual/gpu/ [ Skip ]
 Bug(none) virtual/gpu-rasterization/ [ Skip ]
-Bug(none) virtual/incremental-shadow-dom/ [ Skip ]
 Bug(none) virtual/layout_ng/ [ Skip ]
 Bug(none) virtual/layout_ng_experimental/ [ Skip ]
 Bug(none) virtual/linux-subpixel/ [ Skip ]
diff --git a/third_party/WebKit/LayoutTests/SlowTests b/third_party/WebKit/LayoutTests/SlowTests
index d17c8a3c..3af4c32 100644
--- a/third_party/WebKit/LayoutTests/SlowTests
+++ b/third_party/WebKit/LayoutTests/SlowTests
@@ -252,7 +252,6 @@
 crbug.com/658211 [ Win7 Debug ] fast/text/line-break-ascii.html [ Slow ]
 
 crbug.com/802029 fast/dom/shadow/focus-controller-recursion-crash.html [ Slow ]
-crbug.com/802029 virtual/incremental-shadow-dom/fast/dom/shadow/focus-controller-recursion-crash.html [ Slow ]
 crbug.com/697735 external/wpt/editing/run/backcolor.html [ Slow ]
 crbug.com/697735 external/wpt/editing/run/bold.html [ Slow ]
 crbug.com/697735 external/wpt/editing/run/createlink.html [ Slow ]
diff --git a/third_party/WebKit/LayoutTests/SmokeTests b/third_party/WebKit/LayoutTests/SmokeTests
index acd32a5..f44cbf2 100644
--- a/third_party/WebKit/LayoutTests/SmokeTests
+++ b/third_party/WebKit/LayoutTests/SmokeTests
@@ -539,6 +539,7 @@
 fast/forms/checkbox/checkbox-checked-state-affected-by-default-state.html
 fast/forms/color/color-setrangetext.html
 fast/forms/datalist/slider-appearance-with-ticks-crash.html
+fast/forms/date/date-chooseronly-defaultValue.html
 fast/forms/date/date-format-warning.html
 fast/forms/date-multiple-fields/date-multiple-fields-ax-value-changed-notification.html
 fast/forms/datetimelocal/datetimelocal-input-type.html
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index b5f6f63..4e60ca3 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -1729,27 +1729,6 @@
 crbug.com/591099 virtual/layout_ng_experimental/external/wpt/css/css-flexbox/order/order-with-row-reverse.html [ Failure ]
 # ====== LayoutNG-only failures until here ======
 
-# ====== IncrementalShadowDOM-only failures from here ======
-
-crbug.com/776656 virtual/incremental-shadow-dom/external/wpt/shadow-dom/untriaged/styles/test-003.html [ Failure ]
-
-crbug.com/776656 virtual/incremental-shadow-dom/media/controls/controls-cast-do-not-fade-out.html [ Pass Timeout ]
-
-# These tests are also failing without the flag
-crbug.com/832447 virtual/incremental-shadow-dom/media/controls/controls-page-zoom-in.html [ Failure Pass ]
-crbug.com/832447 virtual/incremental-shadow-dom/media/controls/controls-page-zoom-out.html [ Failure Pass ]
-crbug.com/783154 virtual/incremental-shadow-dom/media/controls/modern/doubletap-to-jump-forwards-too-short.html [ Skip ]
-crbug.com/783154 virtual/incremental-shadow-dom/media/controls/modern/doubletap-to-jump-backwards.html [ Skip ]
-crbug.com/793771 virtual/incremental-shadow-dom/media/controls/modern/scrubbing.html [ Skip ]
-crbug.com/783154 virtual/incremental-shadow-dom/media/controls/modern/doubletap-to-toggle-fullscreen.html [ Skip ]
-crbug.com/831720 virtual/incremental-shadow-dom/media/controls/modern/tap-to-hide-controls.html [ Pass Failure ]
-crbug.com/789921 virtual/incremental-shadow-dom/media/controls/repaint-on-resize.html [ Failure Pass ]
-crbug.com/824775 virtual/incremental-shadow-dom/media/controls/video-controls-with-cast-rendering.html [ Pass Failure ]
-crbug.com/722825 virtual/incremental-shadow-dom/media/controls/video-enter-exit-fullscreen-while-hovering-shows-controls.html [ Timeout Pass Failure ]
-crbug.com/746128 virtual/incremental-shadow-dom/media/controls/video-enter-exit-fullscreen-without-hovering-doesnt-show-controls.html [ Failure ]
-
-# ====== IncrementalShadowDOM-only failures until here ======
-
 crbug.com/840238 http/tests/devtools/elements/shadow/shadow-distribution.js [ Failure ]
 
 crbug.com/667560 [ Debug ] http/tests/devtools/elements/styles-3/styles-change-node-while-editing.js [ Pass Failure ]
@@ -2825,6 +2804,52 @@
 crbug.com/832071 virtual/navigation-mojo-response/external/wpt/service-workers/service-worker/worker-client-id.https.html [ Failure ]
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 external/wpt/fetch/corb/img-html-correctly-labeled.sub.html [ Failure ]
+crbug.com/626703 external/wpt/fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub.html [ Failure ]
+crbug.com/626703 virtual/outofblink-cors/external/wpt/fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub.html [ Failure ]
+crbug.com/626703 virtual/outofblink-cors/external/wpt/fetch/corb/img-html-correctly-labeled.sub.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-element-userSpaceOnUse-002.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-polygon-005.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-polygon-007.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-circle-008.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-polygon-012.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-circle-003.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path-svg-content/mask-objectboundingbox-content-clip.svg [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-circle-001.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-ellipse-003.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-ellipse-001.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-circle-002.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path-svg-content/mask-userspaceonuse-content-clip.svg [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-ellipse-004.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-polygon-011.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-polygon-013.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-polygon-002.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path-svg-content/mask-userspaceonuse-content-clip-transform.svg [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-element-userSpaceOnUse-003.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-polygon-009.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-circle-005.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-ellipse-002.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-polygon-003.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-polygon-001.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-ellipse-005.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-ellipse-006.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path-svg-content/clip-path-dom-href.svg [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path-svg-content/clip-path-css-transform-002.svg [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-circle-004.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-polygon-010.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-008.svg [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-circle-006.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-circle-003.svg [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/test-mask.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-polygon-006.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-circle-004.svg [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path-svg-content/clip-path-recursion-002.svg [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-polygon-008.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path-svg-content/mask-objectboundingbox-content-clip-transform.svg [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-ellipse-008.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-ellipse-007.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path/clip-path-element-userSpaceOnUse-004.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-circle-005.svg [ Failure ]
 crbug.com/626703 [ Mac10.12 ] external/wpt/html/semantics/scripting-1/the-script-element/execution-timing/078.html [ Timeout ]
 crbug.com/626703 [ Mac10.13 ] external/wpt/pointerevents/pointerevent_touch-action-svg-test_touch-manual.html [ Skip ]
 crbug.com/626703 [ Retina ] external/wpt/pointerevents/pointerevent_touch-action-svg-test_touch-manual.html [ Skip ]
@@ -4693,7 +4718,6 @@
 crbug.com/832842 [ Win7 ] virtual/webrtc-wpt-unified-plan/external/wpt/webrtc/RTCDTMFSender-ontonechange.https.html [ Failure ]
 crbug.com/833655 [ Linux ] media/controls/closed-captions-dynamic-update.html [ Skip ]
 crbug.com/833655 [ Linux ] virtual/new-remote-playback-pipeline/media/controls/closed-captions-dynamic-update.html [ Skip ]
-crbug.com/833655 [ Linux ] virtual/incremental-shadow-dom/media/controls/closed-captions-dynamic-update.html [ Skip ]
 crbug.com/833655 [ Linux ] virtual/video-surface-layer/media/controls/closed-captions-dynamic-update.html [ Skip ]
 crbug.com/833658 [ Linux Win Mac ] media/video-controls-focus-movement-on-hide.html [ Pass Failure ]
 crbug.com/833100 [ Mac ] external/wpt/battery-status/battery-full-manual.https.html [ Failure ]
diff --git a/third_party/WebKit/LayoutTests/VirtualTestSuites b/third_party/WebKit/LayoutTests/VirtualTestSuites
index 6f8b1e29..97710c7a 100644
--- a/third_party/WebKit/LayoutTests/VirtualTestSuites
+++ b/third_party/WebKit/LayoutTests/VirtualTestSuites
@@ -603,41 +603,6 @@
     "args": ["--enable-blink-features=FractionalMouseEvent"]
   },
   {
-    "prefix": "incremental-shadow-dom",
-    "base": "external/wpt/shadow-dom",
-    "args": ["--enable-blink-features=SlotInFlatTree,IncrementalShadowDOM"]
-  },
-  {
-    "prefix": "incremental-shadow-dom",
-    "base": "fast/dom/shadow",
-    "args": ["--enable-blink-features=SlotInFlatTree,IncrementalShadowDOM"]
-  },
-  {
-    "prefix": "incremental-shadow-dom",
-    "base": "shadow-dom",
-    "args": ["--enable-blink-features=SlotInFlatTree,IncrementalShadowDOM"]
-  },
-  {
-    "prefix": "incremental-shadow-dom",
-    "base": "html/details_summary",
-    "args": ["--enable-blink-features=SlotInFlatTree,IncrementalShadowDOM"]
-  },
-  {
-    "prefix": "incremental-shadow-dom",
-    "base": "http/tests/devtools/elements/shadow",
-    "args": ["--enable-blink-features=SlotInFlatTree,IncrementalShadowDOM"]
-  },
-  {
-    "prefix": "incremental-shadow-dom",
-    "base": "media/controls",
-    "args": ["--enable-blink-features=SlotInFlatTree,IncrementalShadowDOM"]
-  },
-  {
-    "prefix": "incremental-shadow-dom",
-    "base": "external/wpt/css/css-scoping",
-    "args": ["--enable-blink-features=SlotInFlatTree,IncrementalShadowDOM"]
-  },
-  {
     "prefix": "unified-autoplay",
     "base": "external/wpt/feature-policy",
     "args": ["--autoplay-policy=document-user-activation-required"]
diff --git a/third_party/WebKit/LayoutTests/W3CImportExpectations b/third_party/WebKit/LayoutTests/W3CImportExpectations
index d39cd16..4aa0b0d8 100644
--- a/third_party/WebKit/LayoutTests/W3CImportExpectations
+++ b/third_party/WebKit/LayoutTests/W3CImportExpectations
@@ -86,7 +86,6 @@
 external/wpt/css/css-display/run-in [ Skip ]
 external/wpt/css/css-exclusions [ Skip ]
 external/wpt/css/css-gcpm [ Skip ]
-external/wpt/css/css-masking [ Skip ]
 external/wpt/css/css-page [ Skip ]
 external/wpt/css/css-regions [ Skip ]
 external/wpt/css/css-round-display [ Skip ]
diff --git a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json
index a6f9c8d4..169d4010 100644
--- a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json
+++ b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json
@@ -45841,6 +45841,1818 @@
      {}
     ]
    ],
+   "css/css-masking/clip-path-svg-content/clip-path-clip-nested-twice.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-clip-nested-twice.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-clip-rule-001.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-clip-rule-001.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-hole-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-clip-rule-002.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-clip-rule-002.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-clip-rule-003.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-clip-rule-003.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-hole-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-clip-rule-004.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-clip-rule-004.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-clip-rule-005.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-clip-rule-005.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-clip-rule-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-clip-rule-006.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-clip-rule-006.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-clip-rule-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-clip-rule-007.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-clip-rule-007.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-clip-rule-003-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-clip-rule-008.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-clip-rule-008.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-clip-rule-004-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-clip-rule-009.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-clip-rule-009.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-clip-rule-010.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-clip-rule-010.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-clip-rule-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-clip.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-clip.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-content-clip-001.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-content-clip-001.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-content-clip-002.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-content-clip-002.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-content-clip-003.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-content-clip-003.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-content-invisible.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-content-invisible.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-invisible-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-content-syling.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-content-syling.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-content-use-001.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-content-use-001.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-content-use-002.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-content-use-002.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-content-use-003.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-content-use-003.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-content-use-004.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-content-use-004.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-content-use-005.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-content-use-005.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-content-use-006.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-content-use-006.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-content-use-007.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-content-use-007.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-003-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-css-transform-001.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-css-transform-001.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-css-transform-002.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-css-transform-002.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-css-transform-003.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-css-transform-003.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-003-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-css-transform-004.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-css-transform-004.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-003-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-dom-child-changes.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-dom-child-changes.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-003-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-dom-clippathunits.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-dom-clippathunits.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-003-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-dom-href.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-dom-href.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-003-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-dom-id.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-dom-id.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-003-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-invalid.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-invalid.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-invisible-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-negative-scale.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-negative-scale.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-negative-scale-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-no-content-001.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-no-content-001.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-invisible-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-no-content-002.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-no-content-002.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-invisible-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-no-content-003.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-no-content-003.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-invisible-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-no-content-004.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-no-content-004.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-invisible-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-001.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-001.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-002.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-002.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-003.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-003.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-circle-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-004.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-004.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-on-g-001.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-on-g-001.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-on-g-002.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-on-g-002.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-on-g-003.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-on-g-003.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-on-g-004.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-on-g-004.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-on-g-005.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-on-g-005.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-on-marker-001.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-on-marker-001.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-on-marker-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-on-marker-002.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-on-marker-002.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-on-marker-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-on-marker-003.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-on-marker-003.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-on-marker-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-on-svg-001.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-on-svg-001.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-on-svg-002.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-on-svg-002.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-on-use-001.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-on-use-001.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-on-use-002.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-on-use-002.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-precision-001.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-precision-001.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-precision-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-recursion-001.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-recursion-001.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-invisible-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-recursion-002.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-recursion-002.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-invisible-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-shape-circle-001.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-shape-circle-001.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-circle-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-shape-circle-002.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-shape-circle-002.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-circle-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-shape-circle-003.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-shape-circle-003.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-circle-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-shape-circle-004.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-shape-circle-004.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-circle-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-shape-circle-005.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-shape-circle-005.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-circle-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-shape-ellipse-001.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-shape-ellipse-001.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-ellipse-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-shape-ellipse-002.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-shape-ellipse-002.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-ellipse-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-shape-inset-001.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-shape-inset-001.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-shape-inset-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-shape-inset-002.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-shape-inset-002.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-shape-inset-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-shape-polygon-001.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-shape-polygon-001.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-shape-polygon-002.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-shape-polygon-002.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-hole-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-shape-polygon-003.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-shape-polygon-003.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-text-001.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-text-001.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-text-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-text-002.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-text-002.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-text-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-text-003.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-text-003.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-text-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-text-004.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-text-004.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-text-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-text-005.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-text-005.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-text-003-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-userspaceonuse-001.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-userspaceonuse-001.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-with-opacity.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-with-opacity.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/clip-path-with-transform.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/clip-path-with-transform.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/mask-nested-clip-path-001.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/mask-nested-clip-path-001.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/mask-nested-clip-path-002.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/mask-nested-clip-path-002.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/mask-nested-clip-path-003.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/mask-nested-clip-path-003.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/mask-nested-clip-path-004.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/mask-nested-clip-path-004.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/mask-nested-clip-path-005.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/mask-nested-clip-path-005.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/mask-nested-clip-path-006.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/mask-nested-clip-path-006.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/mask-nested-clip-path-007.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/mask-nested-clip-path-007.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/mask-nested-clip-path-008.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/mask-nested-clip-path-008.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/mask-nested-clip-path-009.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/mask-nested-clip-path-009.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/mask-nested-clip-path-010.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/mask-nested-clip-path-010.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-003-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/mask-nested-clip-path-panning-001.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/mask-nested-clip-path-panning-001.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/mask-nested-clip-path-panning-002.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/mask-nested-clip-path-panning-002.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/mask-objectboundingbox-content-clip-transform.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/mask-objectboundingbox-content-clip-transform.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/mask-content-clip-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/mask-objectboundingbox-content-clip.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/mask-objectboundingbox-content-clip.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/mask-content-clip-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/mask-userspaceonuse-content-clip-transform.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/mask-userspaceonuse-content-clip-transform.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/mask-content-clip-002-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/mask-userspaceonuse-content-clip.svg": [
+    [
+     "/css/css-masking/clip-path-svg-content/mask-userspaceonuse-content-clip.svg",
+     [
+      [
+       "/css/css-masking/clip-path-svg-content/reference/mask-content-clip-001-ref.svg",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-circle-001.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-circle-001.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-circle-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-circle-002.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-circle-002.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-circle-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-circle-003.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-circle-003.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-circle-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-circle-004.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-circle-004.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-circle-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-circle-005.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-circle-005.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-circle-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-circle-006.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-circle-006.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-circle-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-circle-007.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-circle-007.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-circle-2-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-circle-008.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-circle-008.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-circle-3-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-element-userSpaceOnUse-001.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-element-userSpaceOnUse-001.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-rectangle-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-element-userSpaceOnUse-002.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-element-userSpaceOnUse-002.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-rectangle-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-element-userSpaceOnUse-003.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-element-userSpaceOnUse-003.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-ref-right-green-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-element-userSpaceOnUse-004.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-element-userSpaceOnUse-004.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-ref-bottom-green-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-ellipse-001.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-ellipse-001.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-ellipse-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-ellipse-002.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-ellipse-002.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-ellipse-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-ellipse-003.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-ellipse-003.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-circle-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-ellipse-004.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-ellipse-004.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-ellipse-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-ellipse-005.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-ellipse-005.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-ellipse-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-ellipse-006.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-ellipse-006.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-circle-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-ellipse-007.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-ellipse-007.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-ellipse-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-ellipse-008.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-ellipse-008.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-ellipse-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-polygon-001.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-polygon-001.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-rectangle-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-polygon-002.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-polygon-002.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-rectangle-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-polygon-003.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-polygon-003.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-rectangle-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-polygon-004.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-polygon-004.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-rectangle-border-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-polygon-005.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-polygon-005.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-rectangle-border-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-polygon-006.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-polygon-006.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-square-001-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-polygon-007.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-polygon-007.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-stripes-001-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-polygon-008.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-polygon-008.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-stripes-002-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-polygon-009.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-polygon-009.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-square-002-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-polygon-010.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-polygon-010.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-stripes-001-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-polygon-011.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-polygon-011.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-stripes-001-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-polygon-012.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-polygon-012.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-stripes-001-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/clip-path-polygon-013.html": [
+    [
+     "/css/css-masking/clip-path/clip-path-polygon-013.html",
+     [
+      [
+       "/css/css-masking/clip-path/reference/clip-path-stripes-003-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-rule/clip-rule-001.html": [
+    [
+     "/css/css-masking/clip-rule/clip-rule-001.html",
+     [
+      [
+       "/css/css-masking/clip-rule/reference/clip-rule-rectangle-border-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip-rule/clip-rule-002.html": [
+    [
+     "/css/css-masking/clip-rule/clip-rule-002.html",
+     [
+      [
+       "/css/css-masking/clip-rule/reference/clip-rule-rectangle-border-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip/clip-absolute-positioned-001.html": [
+    [
+     "/css/css-masking/clip/clip-absolute-positioned-001.html",
+     [
+      [
+       "/css/css-masking/clip/reference/clip-absolute-positioned-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip/clip-absolute-positioned-002.html": [
+    [
+     "/css/css-masking/clip/clip-absolute-positioned-002.html",
+     [
+      [
+       "/css/css-masking/clip/reference/clip-absolute-positioned-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip/clip-negative-values-001.html": [
+    [
+     "/css/css-masking/clip/clip-negative-values-001.html",
+     [
+      [
+       "/css/css-masking/clip/reference/clip-full-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip/clip-negative-values-002.html": [
+    [
+     "/css/css-masking/clip/clip-negative-values-002.html",
+     [
+      [
+       "/css/css-masking/clip/reference/clip-full-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip/clip-negative-values-003.html": [
+    [
+     "/css/css-masking/clip/clip-negative-values-003.html",
+     [
+      [
+       "/css/css-masking/clip/reference/clip-vertical-stripe-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip/clip-negative-values-004.html": [
+    [
+     "/css/css-masking/clip/clip-negative-values-004.html",
+     [
+      [
+       "/css/css-masking/clip/reference/clip-horizontal-stripe-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip/clip-no-clipping-001.html": [
+    [
+     "/css/css-masking/clip/clip-no-clipping-001.html",
+     [
+      [
+       "/css/css-masking/clip/reference/clip-no-clipping-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip/clip-no-clipping-002.html": [
+    [
+     "/css/css-masking/clip/clip-no-clipping-002.html",
+     [
+      [
+       "/css/css-masking/clip/reference/clip-no-clipping-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip/clip-not-absolute-positioned-001.html": [
+    [
+     "/css/css-masking/clip/clip-not-absolute-positioned-001.html",
+     [
+      [
+       "/css/css-masking/clip/reference/clip-no-clipping-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip/clip-not-absolute-positioned-002.html": [
+    [
+     "/css/css-masking/clip/clip-not-absolute-positioned-002.html",
+     [
+      [
+       "/css/css-masking/clip/reference/clip-no-clipping-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip/clip-not-absolute-positioned-003.html": [
+    [
+     "/css/css-masking/clip/clip-not-absolute-positioned-003.html",
+     [
+      [
+       "/css/css-masking/clip/reference/clip-no-clipping-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip/clip-not-absolute-positioned-004.html": [
+    [
+     "/css/css-masking/clip/clip-not-absolute-positioned-004.html",
+     [
+      [
+       "/css/css-masking/clip/reference/clip-no-clipping-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip/clip-rect-auto-001.html": [
+    [
+     "/css/css-masking/clip/clip-rect-auto-001.html",
+     [
+      [
+       "/css/css-masking/clip/reference/clip-overflow-hidden-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip/clip-rect-auto-002.html": [
+    [
+     "/css/css-masking/clip/clip-rect-auto-002.html",
+     [
+      [
+       "/css/css-masking/clip/reference/clip-no-clipping-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip/clip-rect-auto-003.html": [
+    [
+     "/css/css-masking/clip/clip-rect-auto-003.html",
+     [
+      [
+       "/css/css-masking/clip/reference/clip-rect-top-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip/clip-rect-auto-004.html": [
+    [
+     "/css/css-masking/clip/clip-rect-auto-004.html",
+     [
+      [
+       "/css/css-masking/clip/reference/clip-rect-right-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip/clip-rect-auto-005.html": [
+    [
+     "/css/css-masking/clip/clip-rect-auto-005.html",
+     [
+      [
+       "/css/css-masking/clip/reference/clip-rect-bottom-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip/clip-rect-auto-006.html": [
+    [
+     "/css/css-masking/clip/clip-rect-auto-006.html",
+     [
+      [
+       "/css/css-masking/clip/reference/clip-rect-left-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip/clip-rect-comma-001.html": [
+    [
+     "/css/css-masking/clip/clip-rect-comma-001.html",
+     [
+      [
+       "/css/css-masking/clip/reference/clip-absolute-positioned-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip/clip-rect-comma-002.html": [
+    [
+     "/css/css-masking/clip/clip-rect-comma-002.html",
+     [
+      [
+       "/css/css-masking/clip/reference/clip-no-clipping-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip/clip-rect-comma-003.html": [
+    [
+     "/css/css-masking/clip/clip-rect-comma-003.html",
+     [
+      [
+       "/css/css-masking/clip/reference/clip-no-clipping-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/clip/clip-rect-comma-004.html": [
+    [
+     "/css/css-masking/clip/clip-rect-comma-004.html",
+     [
+      [
+       "/css/css-masking/clip/reference/clip-no-clipping-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-masking/test-mask.html": [
+    [
+     "/css/css-masking/test-mask.html",
+     [
+      [
+       "/css/css-masking/test-mask-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-multicol/float-and-block.html": [
     [
      "/css/css-multicol/float-and-block.html",
@@ -89902,7 +91714,7 @@
      "/fetch/corb/img-html-correctly-labeled.sub.html",
      [
       [
-       "/fetch/corb/img-png-mislabeled-as-html.sub-expected.html",
+       "/fetch/corb/img-png-mislabeled-as-html.sub-ref.html",
        "=="
       ]
      ],
@@ -89914,7 +91726,7 @@
      "/fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub.html",
      [
       [
-       "/fetch/corb/img-png-mislabeled-as-html.sub-expected.html",
+       "/fetch/corb/img-png-mislabeled-as-html.sub-ref.html",
        "=="
       ]
      ],
@@ -89926,7 +91738,7 @@
      "/fetch/corb/img-png-mislabeled-as-html.sub.html",
      [
       [
-       "/fetch/corb/img-png-mislabeled-as-html.sub-expected.html",
+       "/fetch/corb/img-png-mislabeled-as-html.sub-ref.html",
        "=="
       ]
      ],
@@ -93650,7 +95462,7 @@
      "/svg/extensibility/foreignObject/stacking-context.html",
      [
       [
-       "/svg/extensibility/foreignObject/stacking-context-expected.html",
+       "/svg/extensibility/foreignObject/stacking-context-ref.html",
        "=="
       ]
      ],
@@ -112101,7 +113913,7 @@
      {}
     ]
    ],
-   "css/css-fonts/support/fonts/font-feature-settings-rendering-2-expected.html": [
+   "css/css-fonts/support/fonts/font-feature-settings-rendering-2-ref.html": [
     [
      {}
     ]
@@ -120571,6 +122383,276 @@
      {}
     ]
    ],
+   "css/css-masking/OWNERS": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/reference/clip-path-circle-001-ref.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/reference/clip-path-clip-rule-001-ref.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/reference/clip-path-clip-rule-002-ref.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/reference/clip-path-clip-rule-003-ref.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/reference/clip-path-ellipse-001-ref.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/reference/clip-path-invisible-ref.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/reference/clip-path-negative-scale-ref.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/reference/clip-path-on-marker-001-ref.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/reference/clip-path-on-marker-002-ref.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/reference/clip-path-precision-001-ref.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/reference/clip-path-shape-inset-001-ref.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/reference/clip-path-square-001-ref.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/reference/clip-path-square-003-ref.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/reference/clip-path-square-hole-001-ref.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/reference/clip-path-text-001-ref.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/reference/clip-path-text-002-ref.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/reference/clip-path-text-003-ref.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/reference/mask-content-clip-001-ref.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/reference/mask-content-clip-002-ref.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-001-ref.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-002-ref.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-003-ref.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/reference/clip-path-circle-2-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/reference/clip-path-circle-3-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/reference/clip-path-circle-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/reference/clip-path-ellipse-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/reference/clip-path-rectangle-border-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/reference/clip-path-rectangle-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/reference/clip-path-ref-bottom-green-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/reference/clip-path-ref-right-green-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/reference/clip-path-square-001-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/reference/clip-path-square-002-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/reference/clip-path-stripes-001-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/reference/clip-path-stripes-002-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/reference/clip-path-stripes-003-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-path/svg-clipPath.svg": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip-rule/reference/clip-rule-rectangle-border-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip/reference/clip-absolute-positioned-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip/reference/clip-full-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip/reference/clip-horizontal-stripe-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip/reference/clip-no-clipping-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip/reference/clip-overflow-hidden-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip/reference/clip-rect-bottom-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip/reference/clip-rect-left-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip/reference/clip-rect-right-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip/reference/clip-rect-top-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/clip/reference/clip-vertical-stripe-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/parsing/clip-path-invalid-expected.txt": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/parsing/clip-path-valid-expected.txt": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/parsing/clip-valid-expected.txt": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/parsing/resources/parsing-testcommon.js": [
+    [
+     {}
+    ]
+   ],
+   "css/css-masking/test-mask-ref.html": [
+    [
+     {}
+    ]
+   ],
    "css/css-multicol/OWNERS": [
     [
      {}
@@ -140206,17 +142288,17 @@
      {}
     ]
    ],
-   "fetch/corb/img-html-correctly-labeled.sub-expected.html": [
+   "fetch/corb/img-html-correctly-labeled.sub-ref.html": [
     [
      {}
     ]
    ],
-   "fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub-expected.html": [
+   "fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub-ref.html": [
     [
      {}
     ]
    ],
-   "fetch/corb/img-png-mislabeled-as-html.sub-expected.html": [
+   "fetch/corb/img-png-mislabeled-as-html.sub-ref.html": [
     [
      {}
     ]
@@ -163966,7 +166048,7 @@
      {}
     ]
    ],
-   "svg/extensibility/foreignObject/stacking-context-expected.html": [
+   "svg/extensibility/foreignObject/stacking-context-ref.html": [
     [
      {}
     ]
@@ -185681,6 +187763,42 @@
      {}
     ]
    ],
+   "css/css-masking/parsing/clip-invalid.html": [
+    [
+     "/css/css-masking/parsing/clip-invalid.html",
+     {}
+    ]
+   ],
+   "css/css-masking/parsing/clip-path-invalid.html": [
+    [
+     "/css/css-masking/parsing/clip-path-invalid.html",
+     {}
+    ]
+   ],
+   "css/css-masking/parsing/clip-path-valid.html": [
+    [
+     "/css/css-masking/parsing/clip-path-valid.html",
+     {}
+    ]
+   ],
+   "css/css-masking/parsing/clip-rule-invalid.html": [
+    [
+     "/css/css-masking/parsing/clip-rule-invalid.html",
+     {}
+    ]
+   ],
+   "css/css-masking/parsing/clip-rule-valid.html": [
+    [
+     "/css/css-masking/parsing/clip-rule-valid.html",
+     {}
+    ]
+   ],
+   "css/css-masking/parsing/clip-valid.html": [
+    [
+     "/css/css-masking/parsing/clip-valid.html",
+     {}
+    ]
+   ],
    "css/css-multicol/extremely-tall-multicol-with-extremely-tall-child-crash.html": [
     [
      "/css/css-multicol/extremely-tall-multicol-with-extremely-tall-child-crash.html",
@@ -287778,7 +289896,7 @@
    "69e4080710f24b062203f56aa6ae2d991d4fa19a",
    "support"
   ],
-  "css/css-fonts/support/fonts/font-feature-settings-rendering-2-expected.html": [
+  "css/css-fonts/support/fonts/font-feature-settings-rendering-2-ref.html": [
    "883083cdde66caca05384d3d6930070454e94fad",
    "support"
   ],
@@ -296878,6 +298996,850 @@
    "f6260209571bdd53be52c698f072c121e3702dd1",
    "support"
   ],
+  "css/css-masking/OWNERS": [
+   "a290aef2ba2b950b1a235ab06969d414c057804b",
+   "support"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-clip-nested-twice.svg": [
+   "a7452833b72b304a2e34b4b77bcd805baea3cc6f",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-clip-rule-001.svg": [
+   "ce248504ec0c90294d44bd6266aaeca7067534bf",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-clip-rule-002.svg": [
+   "ab01d9ef6db7594d183089f82e8d4684f48b8d1c",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-clip-rule-003.svg": [
+   "38ead39641ede378acce6cbfc1087ecebd74d603",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-clip-rule-004.svg": [
+   "154df85c39eae233f7e2cd20b3b053fe448aa663",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-clip-rule-005.svg": [
+   "3df257a3f68645c29d2081795947091134409638",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-clip-rule-006.svg": [
+   "cdb92ea93f1b217d3020ab5d620081d75bdf8d36",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-clip-rule-007.svg": [
+   "5a9a15a0a4cee3333093a5d300276e5b2ea218fc",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-clip-rule-008.svg": [
+   "db29166ae04ce88353ede0398898d23fa84a8bf1",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-clip-rule-009.svg": [
+   "f0a1cc1bcaabf5195ae6ce1f4569c9533963a06d",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-clip-rule-010.svg": [
+   "dceed2e0610a76567abcae964bb22590565b8792",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-clip.svg": [
+   "8bc1442a628a3268b1b994d1b112a77e1ab28881",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-content-clip-001.svg": [
+   "0ec1183523155b2846f19e56e89f57a2f5570b18",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-content-clip-002.svg": [
+   "aed2d98746324c9360a663a1b5d9f0c2fba68b78",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-content-clip-003.svg": [
+   "60f41530a6fd4ffb0c3d5bd575122573534f0a39",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-content-invisible.svg": [
+   "0a825d868c35096b39509862811876cf66b9da57",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-content-syling.svg": [
+   "8b63d5f5926f2bd700cca3e7f97a3a7b8188ee3f",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-content-use-001.svg": [
+   "4c3114a6c4b8758f0b0ab8e2f754418aa095581b",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-content-use-002.svg": [
+   "58dc648140cffcfe50a5cb4a940a61c02c7d2625",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-content-use-003.svg": [
+   "9359cf6e060fa9da76d29827e4c78197f60b0129",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-content-use-004.svg": [
+   "ea1de7faf5914e574f778cd714a3565146a39ff6",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-content-use-005.svg": [
+   "72461a371fbc8a517ba38dbb27e807e3ab4b185b",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-content-use-006.svg": [
+   "b806a7aa9739485223f59e28a73a86dc5352908c",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-content-use-007.svg": [
+   "1420f79ef281f480ced4b4f4bf6fa354ab7424b5",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-css-transform-001.svg": [
+   "c1c9b70c551999a6e935276b347a928d338b3391",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-css-transform-002.svg": [
+   "e5b198546145887d78bba04acd7a98a55f73c806",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-css-transform-003.svg": [
+   "36ed8914d7a4d6942064e4504bf0c8d2ccce46ca",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-css-transform-004.svg": [
+   "ed481130384e1d1e3a234f8491a111bda04d52c3",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-dom-child-changes.svg": [
+   "d5e2bba3c840f04a78e0d0364a0d981ddabe1e70",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-dom-clippathunits.svg": [
+   "4270adc66eb4da788b62016bd91d291ebfeee2ef",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-dom-href.svg": [
+   "89f9e14f50d33b96078f3d12afd71f77e4ae3fc8",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-dom-id.svg": [
+   "66940a7882775de37028e02a65c4a0ed50098065",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-invalid.svg": [
+   "4b7f3c0b017bbc38d4d7ceab946a75d55c6e6e04",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-negative-scale.svg": [
+   "ad299e235fe06dd5cfbdf7d80172cd190a722ba5",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-no-content-001.svg": [
+   "20e8af353c5c62df75c8a0f69bda3848817a2749",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-no-content-002.svg": [
+   "1e42a0073384eaf6fceea546000da730d82be41b",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-no-content-003.svg": [
+   "c774984e5fdb1ca6d3428090702782e2db3dd038",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-no-content-004.svg": [
+   "6aba7282aec61132e2200dc5ed89fd5a437f646d",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-001.svg": [
+   "98f53cd5d9cf67a30c7b194f80e76a4270129537",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-002.svg": [
+   "7ffa43c3aebf24ed510fe269b682f3f21b501438",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-003.svg": [
+   "42e773f6d0baee0f71c0ff92f05df7fe5aaaf9ee",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-004.svg": [
+   "bedd78a6ff2d8bc4137ecb525508b9c07bfc3b06",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-on-g-001.svg": [
+   "3d13f0f0b9f7359b6bc2323bbd7042029d1a5e77",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-on-g-002.svg": [
+   "25f7c7b574289a1e82d945aef0c387120733e6d1",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-on-g-003.svg": [
+   "3098e4c24c7e2e20c5f94c71b8ef55c0440adf2c",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-on-g-004.svg": [
+   "91d2dbd92a60f55ec337ea7a08964d251142e509",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-on-g-005.svg": [
+   "8459ab6ed343d06785369da3de772935df4c7b79",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-on-marker-001.svg": [
+   "789438dce514d0f6bcc9627a031edb495501b88b",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-on-marker-002.svg": [
+   "295fad4d6f3c1868dd2e707181e45fed5c454def",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-on-marker-003.svg": [
+   "759402ae51b202787627ffbe5652116bd4319a3e",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-on-svg-001.svg": [
+   "d9dd59fa17af2396f1408749812cf6e41b3ece61",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-on-svg-002.svg": [
+   "3ca8568b6ba4a18820d3ac923c7cc808e9859941",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-on-use-001.svg": [
+   "7a6d030a2f844e8e9c313f20e3e8bdcb11c4ccb8",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-on-use-002.svg": [
+   "f6ff3af34693d8bd2df6fda5b15eed6ea9278b65",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-precision-001.svg": [
+   "3da4a56cf2510a1e128bfc5de32b8a88412d1ee2",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-recursion-001.svg": [
+   "0d2421d2b2391586a5162cd2a356e31449730aca",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-recursion-002.svg": [
+   "0ac35571730beb537bebb15759a4e3677eea982c",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-shape-circle-001.svg": [
+   "f18e6c736b7e91ee60e38eab7e84200d431b9880",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-shape-circle-002.svg": [
+   "2750070425d5ec825858d271886e740e71880e79",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-shape-circle-003.svg": [
+   "14c1e7d67a03c3222a63e05b1db2b1c8b29d1673",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-shape-circle-004.svg": [
+   "a8bb3fc5d70086d0f1379b017b3ba96f4a58f6d4",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-shape-circle-005.svg": [
+   "1c37f9d13b5dee4ddfa80582c8aca3b3946a578f",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-shape-ellipse-001.svg": [
+   "2275f7bf79cfd417bc11ae4c0517beda8c5a40c0",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-shape-ellipse-002.svg": [
+   "80d7a049559752b4e146199c14aa692a23112f6d",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-shape-inset-001.svg": [
+   "260d90a8edafb0cbda5c69755f9d14bf8376d747",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-shape-inset-002.svg": [
+   "f0534f5a6ee942328d0710c4bcb131d659740e7d",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-shape-polygon-001.svg": [
+   "cbeeb54cb63bfa1da0bba5fb26c4ff9f7f4bcd90",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-shape-polygon-002.svg": [
+   "692b0324161e8872b614aa0de29d8290f103ed41",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-shape-polygon-003.svg": [
+   "21334ee3e5802c72538726da5e42db7c83a8da41",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-text-001.svg": [
+   "31d569ba0ac4a72f1498cdbb597c27b7d451e967",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-text-002.svg": [
+   "b7dbcecac84677a6331572f0cd2a4e1926538c4e",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-text-003.svg": [
+   "9b8e903d06f82293d604ecbb255d30b6d8820282",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-text-004.svg": [
+   "679b6c1946a3798728ddb8c6d567bb7558e6de79",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-text-005.svg": [
+   "3da30f6a2eb75d3032cf8027e57c26ffd0f946f4",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-userspaceonuse-001.svg": [
+   "ff91c540ee524c339b9c69874e3e0f912e4d15d1",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-with-opacity.svg": [
+   "a3959ad66d1b74469fc7b4b73c1a2cefd80cc810",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/clip-path-with-transform.svg": [
+   "a2a683cc89424f2f60ebf0a762518d91b2dd6ac0",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/mask-nested-clip-path-001.svg": [
+   "39b1783daa3d091586747f4a9f18ae1fc1af236e",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/mask-nested-clip-path-002.svg": [
+   "5b93b0de9a5a28ebf574f23722cf8bbf45829dd9",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/mask-nested-clip-path-003.svg": [
+   "598d4d18d9437f9bae530f5f77e3b296b806e3e4",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/mask-nested-clip-path-004.svg": [
+   "600dfce1fd126e00702097ebf79e4e150f15b1db",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/mask-nested-clip-path-005.svg": [
+   "ab8579a2b60dfba4a29460dd4ddf9681002a8a1f",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/mask-nested-clip-path-006.svg": [
+   "62ba658dff98f8b71b8185b15fbddefa07700299",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/mask-nested-clip-path-007.svg": [
+   "3b9a20ab5566ec40dc99d5af8a67bda616d5ad9d",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/mask-nested-clip-path-008.svg": [
+   "feb9ec1908e017629c3f40cc1ed0ec609a9fd8d1",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/mask-nested-clip-path-009.svg": [
+   "12c713ec92dc7fef6ffb7fff6ad10adce1760355",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/mask-nested-clip-path-010.svg": [
+   "dc226ee10fdd9ae7c1201e3fe65d29403c6fb828",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/mask-nested-clip-path-panning-001.svg": [
+   "340e4e4e626e52a28424909ee9da804935ec7b05",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/mask-nested-clip-path-panning-002.svg": [
+   "536d3a166682f5b36740a4181db1784e721bd390",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/mask-objectboundingbox-content-clip-transform.svg": [
+   "4fe02b38e0ff7636bfa86802e5075ad652ee92ba",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/mask-objectboundingbox-content-clip.svg": [
+   "6dc16e4a8c4e1c3a6de1822c08188876872f2aea",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/mask-userspaceonuse-content-clip-transform.svg": [
+   "0ce13d44cf0410dfc0380b25c335ff9ea35b538c",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/mask-userspaceonuse-content-clip.svg": [
+   "187ed25dcd2b4b505cde1e51bd1407a1c9f0603b",
+   "reftest"
+  ],
+  "css/css-masking/clip-path-svg-content/reference/clip-path-circle-001-ref.svg": [
+   "b9da5a465d0b367b31dff70912abc2f7ea162733",
+   "support"
+  ],
+  "css/css-masking/clip-path-svg-content/reference/clip-path-clip-rule-001-ref.svg": [
+   "a307256c432995b9f99e94d3b39de4a3289eeefe",
+   "support"
+  ],
+  "css/css-masking/clip-path-svg-content/reference/clip-path-clip-rule-002-ref.svg": [
+   "6c6cf2f76863a771e587b04306bfe53f0734a5c5",
+   "support"
+  ],
+  "css/css-masking/clip-path-svg-content/reference/clip-path-clip-rule-003-ref.svg": [
+   "c696dbb5f4083a0a94503ef74402895b003e854a",
+   "support"
+  ],
+  "css/css-masking/clip-path-svg-content/reference/clip-path-ellipse-001-ref.svg": [
+   "961df7b47dadb13652f7de7633d72f2476ed9178",
+   "support"
+  ],
+  "css/css-masking/clip-path-svg-content/reference/clip-path-invisible-ref.svg": [
+   "cac19d54abf575d370b09f786c8dbf322dd00c86",
+   "support"
+  ],
+  "css/css-masking/clip-path-svg-content/reference/clip-path-negative-scale-ref.svg": [
+   "c43b548be62a04f295c7e3fb86f247f8552faeb5",
+   "support"
+  ],
+  "css/css-masking/clip-path-svg-content/reference/clip-path-on-marker-001-ref.svg": [
+   "0dd757e78d40e826cc3faa00c9e952df66b78909",
+   "support"
+  ],
+  "css/css-masking/clip-path-svg-content/reference/clip-path-on-marker-002-ref.svg": [
+   "e4a88a9c050716e4955c0c14293fa834a9ca45b3",
+   "support"
+  ],
+  "css/css-masking/clip-path-svg-content/reference/clip-path-precision-001-ref.svg": [
+   "33d8a321ba00fab29a06d5cb7f8ead8192f8b31e",
+   "support"
+  ],
+  "css/css-masking/clip-path-svg-content/reference/clip-path-shape-inset-001-ref.svg": [
+   "8dc9ad7c125d1ed233d6d8c3bea267d29aee8fcc",
+   "support"
+  ],
+  "css/css-masking/clip-path-svg-content/reference/clip-path-square-001-ref.svg": [
+   "37b3fae4fa6ee15f1eabf9b0bc8a0eec64bb39f3",
+   "support"
+  ],
+  "css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg": [
+   "ef66e3155c340f293da70c7b82c28c9840ea51e7",
+   "support"
+  ],
+  "css/css-masking/clip-path-svg-content/reference/clip-path-square-003-ref.svg": [
+   "063312496b41ff78e5575e7ac797ad64ffdff953",
+   "support"
+  ],
+  "css/css-masking/clip-path-svg-content/reference/clip-path-square-hole-001-ref.svg": [
+   "9b14e04d66bd22ba825be06f61dfe82d0ff66bf4",
+   "support"
+  ],
+  "css/css-masking/clip-path-svg-content/reference/clip-path-text-001-ref.svg": [
+   "f546ac2c0151ebabc5809e3adfc0f69624be05a9",
+   "support"
+  ],
+  "css/css-masking/clip-path-svg-content/reference/clip-path-text-002-ref.svg": [
+   "d2176780046e91451bd6133853fd7cb627e559ca",
+   "support"
+  ],
+  "css/css-masking/clip-path-svg-content/reference/clip-path-text-003-ref.svg": [
+   "a455295170d3bacf72458784ebf8fc024df550cc",
+   "support"
+  ],
+  "css/css-masking/clip-path-svg-content/reference/mask-content-clip-001-ref.svg": [
+   "3b25631d52662baf24181f484c16cfe5785f871f",
+   "support"
+  ],
+  "css/css-masking/clip-path-svg-content/reference/mask-content-clip-002-ref.svg": [
+   "0ebbe7b3ca9870288df510d0b0aa08a7d7944f17",
+   "support"
+  ],
+  "css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-001-ref.svg": [
+   "042a4399e47f143a758e1a20066910549892c20e",
+   "support"
+  ],
+  "css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-002-ref.svg": [
+   "848f11e6d616b636484dfd00dd024ac4422c9b3b",
+   "support"
+  ],
+  "css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-003-ref.svg": [
+   "64c30657edc5f4b73229645799e6670b0294807b",
+   "support"
+  ],
+  "css/css-masking/clip-path/clip-path-circle-001.html": [
+   "f265a132d8ce0920518764cf1551f309875d81cb",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-circle-002.html": [
+   "1e9b296e1e6e938da880c43d6b8be08824629209",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-circle-003.html": [
+   "74e58ac4e352136c6901163b56f857dac5474439",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-circle-004.html": [
+   "398f295d0db75f0cd7672734950623a0ce63f129",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-circle-005.html": [
+   "258b14ac7e70b362f0c12977662e4db99a94af06",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-circle-006.html": [
+   "b982c6a6f1e90878bc129e215b2d29d9e620ac63",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-circle-007.html": [
+   "f077e1687ee241daf43052931e11c71d5085b52d",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-circle-008.html": [
+   "d9cd99534c2d0bef45b84b97c601998247d8e368",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-element-userSpaceOnUse-001.html": [
+   "6761d741d8bcb5f23c9cc111f89ce9446ffb45d1",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-element-userSpaceOnUse-002.html": [
+   "3ac284a03a5a9c338619b708d6343765ae4701d8",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-element-userSpaceOnUse-003.html": [
+   "5e616c40da278c5b3fdc4351483498da11bf1a25",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-element-userSpaceOnUse-004.html": [
+   "fca0837d527c68252cf8ad80ecad913450a289cc",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-ellipse-001.html": [
+   "4f19b1da771cc0074aa2303ca81a8e6af02f5b22",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-ellipse-002.html": [
+   "791d2a01d12c79c3bef71f4e207e8767943c1870",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-ellipse-003.html": [
+   "248724f9bcd53e388e30d12961b99206ab7ee706",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-ellipse-004.html": [
+   "b0d3c13374cf774b00a9474a4e4bafcc56da9635",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-ellipse-005.html": [
+   "08b41c5ee596f09c0e9cc1aa9e61eb11bd5dfaf1",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-ellipse-006.html": [
+   "a66ed2871c901b19e46ebeba3d78e4f08c5402e3",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-ellipse-007.html": [
+   "3f628fc165a225c518bff87a30189e19fddbafc0",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-ellipse-008.html": [
+   "f795eb9e2412806879c4fde92d8ae2ccd68c5c7c",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-polygon-001.html": [
+   "66cf22ce5aa64b4c6d5337c4cc775c3300f0bda0",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-polygon-002.html": [
+   "091255fdbd5f9e5ea18831dd6ea57261518dc42a",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-polygon-003.html": [
+   "b698a007a60fe93706a295fd216da07026126807",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-polygon-004.html": [
+   "5a3a5a5e8a5df1be12e067c714b01f282abd04f0",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-polygon-005.html": [
+   "ac6675dc4258c731d189420e6783f6bc7c4add06",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-polygon-006.html": [
+   "7be2b64d3ff2110f1861bf40b2d2d13d7b96f345",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-polygon-007.html": [
+   "ddc6324b3525300586770ede014f97c4ab8b8386",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-polygon-008.html": [
+   "06b643c2dd5883ab3349c2a11eebbbd30d711483",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-polygon-009.html": [
+   "9826f51c7f113758f43697dc9964d99ea995e1ca",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-polygon-010.html": [
+   "918b6a39fe8448911d29ba7fd16069aaa1b19427",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-polygon-011.html": [
+   "20895dbf1b965265831463c3739e0c8c0c3664dc",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-polygon-012.html": [
+   "03169910ccb6f712bb26e4481fbc890f48318d6e",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/clip-path-polygon-013.html": [
+   "c03b6730ce9cf542261dc335bdb3712946913dc0",
+   "reftest"
+  ],
+  "css/css-masking/clip-path/reference/clip-path-circle-2-ref.html": [
+   "a6945323fcd78d3b53eb67a9d001d3ad117a2387",
+   "support"
+  ],
+  "css/css-masking/clip-path/reference/clip-path-circle-3-ref.html": [
+   "aeb1dbd540caa1c2a6e1295812cddd4f4cd939f5",
+   "support"
+  ],
+  "css/css-masking/clip-path/reference/clip-path-circle-ref.html": [
+   "235cc287098d47f3a952d5034f6ef6a7e5f3bb47",
+   "support"
+  ],
+  "css/css-masking/clip-path/reference/clip-path-ellipse-ref.html": [
+   "9bc3bca512a62ab2a603670753995e12c7d211ce",
+   "support"
+  ],
+  "css/css-masking/clip-path/reference/clip-path-rectangle-border-ref.html": [
+   "bd28c14af111682131f88319feac46e6ec80cdfa",
+   "support"
+  ],
+  "css/css-masking/clip-path/reference/clip-path-rectangle-ref.html": [
+   "f987b164a1a3a9c314cb98ba9385fbfae768413f",
+   "support"
+  ],
+  "css/css-masking/clip-path/reference/clip-path-ref-bottom-green-ref.html": [
+   "331ffd9de7831f66e4f7d5cec70300efdc9865eb",
+   "support"
+  ],
+  "css/css-masking/clip-path/reference/clip-path-ref-right-green-ref.html": [
+   "2dee4048cb2b75be993fe782b97954813388373a",
+   "support"
+  ],
+  "css/css-masking/clip-path/reference/clip-path-square-001-ref.html": [
+   "1c18969e30afa1f8205f43786fd3e8ef55d4c45f",
+   "support"
+  ],
+  "css/css-masking/clip-path/reference/clip-path-square-002-ref.html": [
+   "e61874c2bd128b82e7c785415b06cde38114ec2c",
+   "support"
+  ],
+  "css/css-masking/clip-path/reference/clip-path-stripes-001-ref.html": [
+   "6590a66b97be0c29be9ba54986b44bad14a2a012",
+   "support"
+  ],
+  "css/css-masking/clip-path/reference/clip-path-stripes-002-ref.html": [
+   "3a7a2b5c5fdf8e18bec35462c7cd9a4be29948ad",
+   "support"
+  ],
+  "css/css-masking/clip-path/reference/clip-path-stripes-003-ref.html": [
+   "c41a5497d762b23668e27b7a64ff0b23572bbd67",
+   "support"
+  ],
+  "css/css-masking/clip-path/svg-clipPath.svg": [
+   "c74b3448c10c2d388603164f33a5d8d82143cf43",
+   "support"
+  ],
+  "css/css-masking/clip-rule/clip-rule-001.html": [
+   "554a1bf622c76182e3f8161f67fdebdf42b2fb8e",
+   "reftest"
+  ],
+  "css/css-masking/clip-rule/clip-rule-002.html": [
+   "2f726b6718c8631d88e6a8e87f05745ab6504abf",
+   "reftest"
+  ],
+  "css/css-masking/clip-rule/reference/clip-rule-rectangle-border-ref.html": [
+   "bd28c14af111682131f88319feac46e6ec80cdfa",
+   "support"
+  ],
+  "css/css-masking/clip/clip-absolute-positioned-001.html": [
+   "fa18dcfe8b94fcf68b9bf3617311dac78f9c7ca5",
+   "reftest"
+  ],
+  "css/css-masking/clip/clip-absolute-positioned-002.html": [
+   "fe0ccd92a8e243bd6ba667c46cc32b9b550140db",
+   "reftest"
+  ],
+  "css/css-masking/clip/clip-negative-values-001.html": [
+   "4f1a669631c9d8e22f16bf986bcb7bcf376f1be9",
+   "reftest"
+  ],
+  "css/css-masking/clip/clip-negative-values-002.html": [
+   "eaf22e70dff9ac9b643ac2c3df3849f4ec7cc908",
+   "reftest"
+  ],
+  "css/css-masking/clip/clip-negative-values-003.html": [
+   "ff884020449565c339220911c72083156ef55962",
+   "reftest"
+  ],
+  "css/css-masking/clip/clip-negative-values-004.html": [
+   "da216aa7845ee91d25de2601f7313baf578417b7",
+   "reftest"
+  ],
+  "css/css-masking/clip/clip-no-clipping-001.html": [
+   "fd733a5733f0bc5ef7739a29a1621b34111df0a4",
+   "reftest"
+  ],
+  "css/css-masking/clip/clip-no-clipping-002.html": [
+   "7bad941ede4edb393212f69259c0d7592106112b",
+   "reftest"
+  ],
+  "css/css-masking/clip/clip-not-absolute-positioned-001.html": [
+   "efb0b493a0df74360579a63e951a33802ecccc3b",
+   "reftest"
+  ],
+  "css/css-masking/clip/clip-not-absolute-positioned-002.html": [
+   "13bd62f26b325e8bf0c4f963b02ad7002904b4c8",
+   "reftest"
+  ],
+  "css/css-masking/clip/clip-not-absolute-positioned-003.html": [
+   "7a7e2e3f811576d9926b54a68710722c6dcc0adf",
+   "reftest"
+  ],
+  "css/css-masking/clip/clip-not-absolute-positioned-004.html": [
+   "c650a01374b2a03cd5f0b6e7cf20dae84264683a",
+   "reftest"
+  ],
+  "css/css-masking/clip/clip-rect-auto-001.html": [
+   "e736cd6a808b66b8eefc9f7713d958834b32c1b0",
+   "reftest"
+  ],
+  "css/css-masking/clip/clip-rect-auto-002.html": [
+   "7038536497473e6c5ff97526892d306d778b0868",
+   "reftest"
+  ],
+  "css/css-masking/clip/clip-rect-auto-003.html": [
+   "56dfab1ff6eb157af35bb8bb460ae41f53cf7c1d",
+   "reftest"
+  ],
+  "css/css-masking/clip/clip-rect-auto-004.html": [
+   "dd4a89326385ff7cdccd0386d11178f43057351c",
+   "reftest"
+  ],
+  "css/css-masking/clip/clip-rect-auto-005.html": [
+   "eb2905f422574d19d82e65b92803e5dde34d071f",
+   "reftest"
+  ],
+  "css/css-masking/clip/clip-rect-auto-006.html": [
+   "781b539b814d7d90ebc71678fc89ead8829126ac",
+   "reftest"
+  ],
+  "css/css-masking/clip/clip-rect-comma-001.html": [
+   "de39f7695ae80d7cdeea922f2395d89873fb2eeb",
+   "reftest"
+  ],
+  "css/css-masking/clip/clip-rect-comma-002.html": [
+   "70d6467b21e7a4b82665231413624b100e1b0926",
+   "reftest"
+  ],
+  "css/css-masking/clip/clip-rect-comma-003.html": [
+   "b54ab663c2743a75664960ac7673ed2be7be5fac",
+   "reftest"
+  ],
+  "css/css-masking/clip/clip-rect-comma-004.html": [
+   "d7aa6a378311712bf6bb7f9e4c6810124284fd40",
+   "reftest"
+  ],
+  "css/css-masking/clip/reference/clip-absolute-positioned-ref.html": [
+   "7448ec0ee3184b6f95d562c0e360a8844fde9b97",
+   "support"
+  ],
+  "css/css-masking/clip/reference/clip-full-ref.html": [
+   "ff2bf578e9433f9dfa2072c66f448e86fd73a9d1",
+   "support"
+  ],
+  "css/css-masking/clip/reference/clip-horizontal-stripe-ref.html": [
+   "beb646fbeacdcb1339925b6d58a51bfbb1ede256",
+   "support"
+  ],
+  "css/css-masking/clip/reference/clip-no-clipping-ref.html": [
+   "baee9f5ef67d55ae03fe66a00b2d9c972723bd32",
+   "support"
+  ],
+  "css/css-masking/clip/reference/clip-overflow-hidden-ref.html": [
+   "98f3e392ba4c54ddd0214ea0bce6a6847e63fca5",
+   "support"
+  ],
+  "css/css-masking/clip/reference/clip-rect-bottom-ref.html": [
+   "6a33e33e28c707e2542f7dcef46092d497ce4c6a",
+   "support"
+  ],
+  "css/css-masking/clip/reference/clip-rect-left-ref.html": [
+   "313232e967cef8035aa04e7e6c95ee1ded867b69",
+   "support"
+  ],
+  "css/css-masking/clip/reference/clip-rect-right-ref.html": [
+   "7b0acf621bb41ade4c5620383bc4e63a4b2dd0ea",
+   "support"
+  ],
+  "css/css-masking/clip/reference/clip-rect-top-ref.html": [
+   "700d9d78427c2f60e62859073e8144b4fc096686",
+   "support"
+  ],
+  "css/css-masking/clip/reference/clip-vertical-stripe-ref.html": [
+   "8853e79d6e9c3d262ebb38c569e97932f3b27cd4",
+   "support"
+  ],
+  "css/css-masking/parsing/clip-invalid.html": [
+   "fad5d0257532a5c1572d41f83d045c92a6fe0d61",
+   "testharness"
+  ],
+  "css/css-masking/parsing/clip-path-invalid-expected.txt": [
+   "e115915861f0d4bf48f30612e9c21e2b1c386bf8",
+   "support"
+  ],
+  "css/css-masking/parsing/clip-path-invalid.html": [
+   "791ea3c564f629ed8d679499f5483e122ad9f602",
+   "testharness"
+  ],
+  "css/css-masking/parsing/clip-path-valid-expected.txt": [
+   "36e3917ed8c4c7b3fb08bd7836654b05d2e4824b",
+   "support"
+  ],
+  "css/css-masking/parsing/clip-path-valid.html": [
+   "4ac5715cdba374d3b702157ffce4a0a45009995a",
+   "testharness"
+  ],
+  "css/css-masking/parsing/clip-rule-invalid.html": [
+   "5a7673c56ae29a10133ed1395ae1e61ee2a7dd81",
+   "testharness"
+  ],
+  "css/css-masking/parsing/clip-rule-valid.html": [
+   "9236b88f0ac03139210fe6f5fd5f9fa08a613c09",
+   "testharness"
+  ],
+  "css/css-masking/parsing/clip-valid-expected.txt": [
+   "ba9cff26a57bd2bed5c2fb04c92ba4e5ab86b729",
+   "support"
+  ],
+  "css/css-masking/parsing/clip-valid.html": [
+   "e7805a38a0634d760bdc31e4c331da1a56582bc9",
+   "testharness"
+  ],
+  "css/css-masking/parsing/resources/parsing-testcommon.js": [
+   "14f32b772f27a9bc75fe90e2ea1d8e4fb3649e95",
+   "support"
+  ],
+  "css/css-masking/test-mask-ref.html": [
+   "6307ecf282b941dbe1475bdb603208f4140b2f26",
+   "support"
+  ],
+  "css/css-masking/test-mask.html": [
+   "13f1c2ad253ea55e13781544bbfad4900a3a58c1",
+   "reftest"
+  ],
   "css/css-multicol/OWNERS": [
    "1cf1640b76a341546126c66b30f35f6f32aef798",
    "support"
@@ -337162,32 +340124,32 @@
    "c786c1df10c1edad9b25be4ec112250864c328e5",
    "support"
   ],
-  "fetch/corb/img-html-correctly-labeled.sub-expected.html": [
+  "fetch/corb/img-html-correctly-labeled.sub-ref.html": [
    "a252054121e7f50a3bcb949ae5a40f278c842c04",
    "support"
   ],
   "fetch/corb/img-html-correctly-labeled.sub.html": [
-   "699a8b2c8bb1f089f3ef1827bf8cfe1873849bf8",
+   "0c080d27dfcc89fc1f7e92be063f4d2d95fa9d6c",
    "reftest"
   ],
   "fetch/corb/img-mime-types-coverage.tentative.sub.html": [
    "02da8b9ee9430d552c4cb71fb759e05e939ec05e",
    "testharness"
   ],
-  "fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub-expected.html": [
+  "fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub-ref.html": [
    "1980633a4167993d90636be2ebba2aa8d72299b7",
    "support"
   ],
   "fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub.html": [
-   "7169e4ebe99797d15f006f6787e84093780fbaa6",
+   "2005cb1cd7a313d8ca7ee89c32e5bc54c39b2da5",
    "reftest"
   ],
-  "fetch/corb/img-png-mislabeled-as-html.sub-expected.html": [
+  "fetch/corb/img-png-mislabeled-as-html.sub-ref.html": [
    "730878950e0b7a4097d42e7eaaae79304fe05106",
    "support"
   ],
   "fetch/corb/img-png-mislabeled-as-html.sub.html": [
-   "a7775fb534d38a5d5b5827a27f0c16e1268f4d0b",
+   "cafbaa0cf543dc360a6261258e88339b2ad1dcb3",
    "reftest"
   ],
   "fetch/corb/preload-image-png-mislabeled-as-html-nosniff.tentative.sub.html": [
@@ -383274,12 +386236,12 @@
    "974affbb2c135c9aaa7a3f27687157b5e1250a9f",
    "testharness"
   ],
-  "svg/extensibility/foreignObject/stacking-context-expected.html": [
+  "svg/extensibility/foreignObject/stacking-context-ref.html": [
    "6ea850b74b6a03554305ed95df45079bdbcb15dd",
    "support"
   ],
   "svg/extensibility/foreignObject/stacking-context.html": [
-   "ef9026d2b8b27a258c3921139c46692292377dfe",
+   "7e3601ac1fcac50bf989f3767cc81ad84bc99276",
    "reftest"
   ],
   "svg/extensibility/interfaces/foreignObject-graphics.svg": [
@@ -387343,7 +390305,7 @@
    "support"
   ],
   "webrtc/RTCPeerConnection-setDescription-transceiver.html": [
-   "4d0d9a168327e62eefc0d4398874fd944c50b43c",
+   "a21fe04592ad6941aa4277535d6482519b67ae74",
    "testharness"
   ],
   "webrtc/RTCPeerConnection-setLocalDescription-answer-expected.txt": [
@@ -393435,7 +396397,7 @@
    "support"
   ],
   "worklets/resources/service-worker-interception-tests.js": [
-   "e23072b8b5695b7669d855197285b8ec3534b6e7",
+   "42ebe0dd946d39dcf3c211581a15d0f648a67a08",
    "support"
   ],
   "worklets/resources/service-worker.js": [
diff --git a/third_party/WebKit/LayoutTests/external/wpt/cookie-store/idlharness.tentative.html b/third_party/WebKit/LayoutTests/external/wpt/cookie-store/idlharness.tentative.html
index e313b04..9835427 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/cookie-store/idlharness.tentative.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/cookie-store/idlharness.tentative.html
@@ -43,6 +43,11 @@
   ] });
 
   idl_array.add_untested_idls(
+    `dictionary ExtendableEventInit {};`);
+  idl_array.add_untested_idls(
+    `[Global=ExtendableEvent, Exposed=ServiceWorker]
+     interface ExtendableEvent : Event {};`);
+  idl_array.add_untested_idls(
     `[Global=ServiceWorker, Exposed=ServiceWorker]
      interface ServiceWorkerGlobalScope {};`);
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/cookie-store/idlharness_serviceworker.js b/third_party/WebKit/LayoutTests/external/wpt/cookie-store/idlharness_serviceworker.js
index e2e838d..ba80413 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/cookie-store/idlharness_serviceworker.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/cookie-store/idlharness_serviceworker.js
@@ -16,8 +16,12 @@
   idl_array.add_untested_idls(
     `[Global=Event, Exposed=ServiceWorker]
      interface Event {};`);
+  idl_array.add_untested_idls(
+    `[Global=ExtendableEvent, Exposed=ServiceWorker]
+     interface ExtendableEvent : Event {};`);
   idl_array.add_untested_idls('dictionary EventHandler {};');
   idl_array.add_untested_idls('dictionary EventInit {};');
+  idl_array.add_untested_idls('dictionary ExtendableEventInit {};');
   idl_array.add_untested_idls(
     `[Global=EventTarget, Exposed=ServiceWorker]
      interface EventTarget {};`);
@@ -32,7 +36,8 @@
 
   idl_array.add_objects({
     CookieStore: [self.cookieStore],
-    CookieChangeEvent: [new CookieChangeEvent('change')],
+    ExtendableCookieChangeEvent: [
+        new ExtendableCookieChangeEvent('cookiechange')],
   });
   idl_array.test();
 }, 'Interface test');
diff --git a/third_party/WebKit/LayoutTests/external/wpt/cookie-store/serviceworker_cookieStore_subscriptions.js b/third_party/WebKit/LayoutTests/external/wpt/cookie-store/serviceworker_cookieStore_subscriptions.js
new file mode 100644
index 0000000..3f1b0ff
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/cookie-store/serviceworker_cookieStore_subscriptions.js
@@ -0,0 +1,113 @@
+self.GLOBAL = {
+  isWindow: function() { return false; },
+  isWorker: function() { return true; },
+};
+importScripts("/resources/testharness.js");
+
+self.addEventListener('install', (event) => {
+  event.waitUntil((async () => {
+    // The subscribeToChanges calls are not done in parallel on purpose. Having
+    // multiple in-flight requests introduces failure modes aside from the
+    // cookie change logic that this test aims to cover.
+    await cookieStore.subscribeToChanges([
+      { name: 'cookie-name1', matchType: 'equals', url: '/scope/path1' }]);
+    await cookieStore.subscribeToChanges([
+      { },  // Test the default values for subscription properties.
+      { name: 'cookie-prefix', matchType: 'startsWith' },
+    ]);
+  })());
+});
+
+// Workaround because add_cleanup doesn't support async functions yet.
+// See https://github.com/w3c/web-platform-tests/issues/6075
+async function async_cleanup(cleanup_function) {
+  try {
+    await cleanup_function();
+  } catch (e) {
+    // Errors in cleanup functions shouldn't result in test failures.
+  }
+}
+
+// Resolves when the service worker receives the 'activate' event.
+const kServiceWorkerActivatedPromise = new Promise(resolve => {
+  self.addEventListener('activate', event => { resolve(); });
+});
+
+// sort() comparator that uses the < operator.
+//
+// This is intended to be used for sorting strings. Using < is preferred to
+// localeCompare() because the latter has some implementation-dependent
+// behavior.
+function CompareStrings(a, b) {
+  return a < b ? -1 : (b < a ? 1 : 0);
+}
+
+promise_test(async testCase => {
+  await kServiceWorkerActivatedPromise;
+
+  const subscriptions = await cookieStore.getChangeSubscriptions();
+  assert_equals(subscriptions.length, 3);
+
+  subscriptions.sort((a, b) => CompareStrings(`${a.name}`, `${b.name}`));
+
+  assert_equals(subscriptions[0].name, 'cookie-name1');
+  assert_equals('equals', subscriptions[0].matchType);
+
+  assert_equals(subscriptions[1].name, 'cookie-prefix');
+  assert_equals('startsWith', subscriptions[1].matchType);
+
+  assert_false('name' in subscriptions[2]);
+  assert_equals('startsWith', subscriptions[2].matchType);
+}, 'getChangeSubscriptions returns subscriptions passed to subscribeToChanges');
+
+promise_test(async testCase => {
+  promise_rejects(
+      testCase, new TypeError(),
+      cookieStore.subscribeToChanges([{ name: 'cookie-name2' }]));
+}, 'subscribeToChanges rejects when called outside the install handler');
+
+
+// Accumulates cookiechange events dispatched to the service worker.
+let g_cookie_changes = [];
+
+// Resolved when a cookiechange event is received. Rearmed by
+// ResetCookieChangeReceivedPromise().
+let g_cookie_change_received_promise = null;
+let g_cookie_change_received_promise_resolver = null;
+self.addEventListener('cookiechange', (event) => {
+  g_cookie_changes.push(event);
+  if (g_cookie_change_received_promise_resolver)
+    g_cookie_change_received_promise_resolver();
+});
+function RearmCookieChangeReceivedPromise() {
+  g_cookie_change_received_promise = new Promise((resolve) => {
+    g_cookie_change_received_promise_resolver = resolve;
+  });
+}
+RearmCookieChangeReceivedPromise();
+
+promise_test(async testCase => {
+  await kServiceWorkerActivatedPromise;
+
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  await g_cookie_change_received_promise;
+
+  assert_equals(g_cookie_changes.length, 1);
+  const event = g_cookie_changes[0]
+  assert_equals(event.type, 'cookiechange');
+  assert_equals(event.changed.length, 1);
+  assert_equals(event.changed[0].name, 'cookie-name');
+  assert_equals(event.changed[0].value, 'cookie-value');
+  assert_equals(event.deleted.length, 0);
+  assert_true(event instanceof ExtendableCookieChangeEvent);
+  assert_true(event instanceof ExtendableEvent);
+
+  await async_cleanup(() => {
+    cookieStore.delete('cookie-name');
+    g_cookie_changes = [];
+    RearmCookieChangeReceivedPromise();
+  });
+}, 'cookiechange dispatched with cookie change that matches subscription');
+
+done();
diff --git a/third_party/WebKit/LayoutTests/external/wpt/cookie-store/serviceworker_cookieStore_subscriptions.tentative.https.html b/third_party/WebKit/LayoutTests/external/wpt/cookie-store/serviceworker_cookieStore_subscriptions.tentative.https.html
new file mode 100644
index 0000000..723de23
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/cookie-store/serviceworker_cookieStore_subscriptions.tentative.https.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Async Cookies: cookie change events in ServiceWorker</title>
+<link rel="help" href="https://github.com/WICG/cookie-store">
+<link rel="author" href="pwnall@chromium.org" title="Victor Costan">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+'use strict';
+
+(async () => {
+  const scope = 'scope';
+
+  let registration = await navigator.serviceWorker.getRegistration(scope);
+  if (registration)
+    await registration.unregister();
+  registration = await navigator.serviceWorker.register(
+      'serviceworker_cookieStore_subscriptions.js', {scope});
+
+  fetch_tests_from_worker(registration.installing);
+})();
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/cookie-store/serviceworker_cookieStore_subscriptions_basic.js b/third_party/WebKit/LayoutTests/external/wpt/cookie-store/serviceworker_cookieStore_subscriptions_basic.js
new file mode 100644
index 0000000..68edc0e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/cookie-store/serviceworker_cookieStore_subscriptions_basic.js
@@ -0,0 +1,63 @@
+self.GLOBAL = {
+  isWindow: function() { return false; },
+  isWorker: function() { return true; },
+};
+importScripts("/resources/testharness.js");
+
+self.addEventListener('install', (event) => {
+  event.waitUntil((async () => {
+    cookieStore.subscribeToChanges([
+      { name: 'cookie-name', matchType: 'equals', url: '/scope/path' }]);
+  })());
+});
+
+// Workaround because add_cleanup doesn't support async functions yet.
+// See https://github.com/w3c/web-platform-tests/issues/6075
+async function async_cleanup(cleanup_function) {
+  try {
+    await cleanup_function();
+  } catch (e) {
+    // Errors in cleanup functions shouldn't result in test failures.
+  }
+}
+
+// Resolves when the service worker receives the 'activate' event.
+const kServiceWorkerActivatedPromise = new Promise(resolve => {
+  self.addEventListener('activate', event => { resolve(); });
+});
+
+promise_test(async testCase => {
+  await kServiceWorkerActivatedPromise;
+
+  const subscriptions = await cookieStore.getChangeSubscriptions();
+  assert_equals(subscriptions.length, 1);
+
+  assert_equals(subscriptions[0].name, 'cookie-name');
+  assert_equals('equals', subscriptions[0].matchType);
+}, 'getChangeSubscriptions returns a subscription passed to subscribeToChanges');
+
+
+promise_test(async testCase => {
+  await kServiceWorkerActivatedPromise;
+
+  cookie_change_received_promise = new Promise((resolve) => {
+    self.addEventListener('cookiechange', (event) => {
+      resolve(event);
+    });
+  });
+
+  await cookieStore.set('cookie-name', 'cookie-value');
+
+  const event = await cookie_change_received_promise;
+  assert_equals(event.type, 'cookiechange');
+  assert_equals(event.changed.length, 1);
+  assert_equals(event.changed[0].name, 'cookie-name');
+  assert_equals(event.changed[0].value, 'cookie-value');
+  assert_equals(event.deleted.length, 0);
+  assert_true(event instanceof ExtendableCookieChangeEvent);
+  assert_true(event instanceof ExtendableEvent);
+
+  await async_cleanup(() => { cookieStore.delete('cookie-name'); });
+}, 'cookiechange dispatched with cookie change that matches subscription');
+
+done();
diff --git a/third_party/WebKit/LayoutTests/external/wpt/cookie-store/serviceworker_cookieStore_subscriptions_basic.tentative.https.html b/third_party/WebKit/LayoutTests/external/wpt/cookie-store/serviceworker_cookieStore_subscriptions_basic.tentative.https.html
new file mode 100644
index 0000000..525c3b3
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/cookie-store/serviceworker_cookieStore_subscriptions_basic.tentative.https.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Async Cookies: cookie change events in ServiceWorker</title>
+<link rel="help" href="https://github.com/WICG/cookie-store">
+<link rel="author" href="pwnall@chromium.org" title="Victor Costan">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+'use strict';
+
+(async () => {
+  const scope = 'scope';
+
+  let registration = await navigator.serviceWorker.getRegistration(scope);
+  if (registration)
+    await registration.unregister();
+  registration = await navigator.serviceWorker.register(
+      'serviceworker_cookieStore_subscriptions_basic.js', {scope});
+
+  fetch_tests_from_worker(registration.installing);
+})();
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-fonts/support/fonts/font-feature-settings-rendering-2-expected.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-fonts/support/fonts/font-feature-settings-rendering-2-ref.html
similarity index 100%
rename from third_party/WebKit/LayoutTests/external/wpt/css/css-fonts/support/fonts/font-feature-settings-rendering-2-expected.html
rename to third_party/WebKit/LayoutTests/external/wpt/css/css-fonts/support/fonts/font-feature-settings-rendering-2-ref.html
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/OWNERS
new file mode 100644
index 0000000..5f3e240
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/OWNERS
@@ -0,0 +1,3 @@
+# TEAM: paint-dev@chromium.org
+# COMPONENT: Blink>Paint
+# WPT-NOTIFY: true
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-nested-twice.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-nested-twice.svg
new file mode 100644
index 0000000..269e8fee
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-nested-twice.svg
@@ -0,0 +1,23 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath element nested twice</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Check deep referencing of content of one clipPath
+	element to another clipPath element. A green square should be visible.
+	</desc>
+</g>
+<clipPath id="clip3">
+	<rect x="50" y="50" width="100" height="100"/>
+</clipPath>
+<clipPath id="clip2" clip-path="url(#clip3)">
+	<circle cx="100" cy="100" r="75"/>
+</clipPath>
+<clipPath id="clip1" clip-path="url(#clip2)">
+	<circle cx="100" cy="100" r="100"/>
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-001.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-001.svg
new file mode 100644
index 0000000..e5b972f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-001.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clip-rule property - evenodd</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-rule"/>
+	<html:link rel="match" href="reference/clip-path-square-hole-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Check if the clip-rule 'evenodd' applies to a content
+	polygon element of clipPath element. A green square with a
+	rectangular hole should be visible.</desc>
+</g>
+<clipPath id="clip1">
+	<polygon points="25 25, 175 25, 175 175, 25 175, 25 50, 150 50, 150 150, 50 150, 50 50, 25 50" clip-rule="evenodd" />
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-002.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-002.svg
new file mode 100644
index 0000000..7729f79
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-002.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clip-rule property - nonzero</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-rule"/>
+	<html:link rel="match" href="reference/clip-path-square-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Check if the clip-rule 'nonzero' applies to a content
+	polygon element of clipPath element. A green square should be
+	visible.</desc>
+</g>
+<clipPath id="clip1">
+	<polygon points="25 25, 175 25, 175 175, 25 175, 25 50, 150 50, 150 150, 50 150, 50 50, 25 50" clip-rule="nonzero" />
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-003.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-003.svg
new file mode 100644
index 0000000..c4f2bf4
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-003.svg
@@ -0,0 +1,21 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clip-rule property - evenodd nested clip</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-rule"/>
+	<html:link rel="match" href="reference/clip-path-square-hole-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Check if the clip-rule 'evenodd' applies to a content
+	polygon element of clipPath element. A green square with a
+	rectangular hole should be visible.</desc>
+</g>
+<clipPath id="clip2">
+	<rect x="25" y="25" width="150" height="150"/>
+</clipPath>
+<clipPath id="clip1" clip-path="url(#clip2)">
+	<polygon points="0 0, 200 0, 200 200, 0 200, 0 50, 150 50, 150 150, 50 150, 50 50, 0 50" clip-rule="evenodd" />
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-004.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-004.svg
new file mode 100644
index 0000000..2ea61860
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-004.svg
@@ -0,0 +1,21 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clip-rule property - nonzero nested clip</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-rule"/>
+	<html:link rel="match" href="reference/clip-path-square-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Check that clip-rule 'nonzero' applies to
+	clipPath element and a second clipPath element can be applied to
+	the first one. A green square should be visible.</desc>
+</g>
+<clipPath id="clip2">
+	<rect x="25" y="25" width="150" height="150"/>
+</clipPath>
+<clipPath id="clip1" clip-path="url(#clip2)">
+	<polygon points="0 0, 200 0, 200 200, 0 200, 0 50, 150 50, 150 150, 50 150, 50 50, 0 50" clip-rule="nonzero" />
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-005.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-005.svg
new file mode 100644
index 0000000..ca49258
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-005.svg
@@ -0,0 +1,19 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath clip-rule evenodd nonzero</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-rule"/>
+	<html:link rel="match" href="reference/clip-path-clip-rule-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Test two different clip-rules on two different content
+	elements. You should see two green rectangles. The one on the top left
+	should have a hole, the shifted one shouldn't.</desc>
+</g>
+<clipPath id="clip1">
+	<polygon points="0 0, 150 0, 150 150, 0 150, 0 25, 125 25, 125 125, 25 125, 25 25, 0 25" clip-rule="evenodd"/>
+	<polygon points="50 50, 200 50, 200 200, 50 200, 50 75, 175 75, 175 175, 75 175, 75 75, 50 75" clip-rule="nonzero"/>
+</clipPath>
+<rect x="0" y="0" height="200" width="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-006.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-006.svg
new file mode 100644
index 0000000..da9d1010
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-006.svg
@@ -0,0 +1,20 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath clip-rule nonzero nonzero</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-rule"/>
+	<html:link rel="match" href="reference/clip-path-clip-rule-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Test two equal clip-rules 'nonzero' on two different
+	content elements. You should see two solid green rectangles. The first one
+	on the top left, the second one slightly shifted to the bottom right.
+	</desc>
+</g>
+<clipPath id="clip1">
+	<polygon points="0 0, 150 0, 150 150, 0 150, 0 25, 125 25, 125 125, 25 125, 25 25, 0 25" clip-rule="nonzero"/>
+	<polygon points="50 50, 200 50, 200 200, 50 200, 50 75, 175 75, 175 175, 75 175, 75 75, 50 75" clip-rule="nonzero"/>
+</clipPath>
+<rect x="0" y="0" height="200" width="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-007.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-007.svg
new file mode 100644
index 0000000..d1edb6c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-007.svg
@@ -0,0 +1,19 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath clip-rule nonzero evenodd</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-rule"/>
+	<html:link rel="match" href="reference/clip-path-clip-rule-003-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Test two different clip-rules on two different content
+	elements. You should see two green rectangles. The one on the top left
+	shouldn't have a hole, the shifted one should have.</desc>
+</g>
+<clipPath id="clip1">
+	<polygon points="0 0, 150 0, 150 150, 0 150, 0 25, 125 25, 125 125, 25 125, 25 25, 0 25" clip-rule="nonzero"/>
+	<polygon points="50 50, 200 50, 200 200, 50 200, 50 75, 175 75, 175 175, 75 175, 75 75, 50 75" clip-rule="evenodd"/>
+</clipPath>
+<rect x="0" y="0" height="200" width="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-008.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-008.svg
new file mode 100644
index 0000000..66ad9b5c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-008.svg
@@ -0,0 +1,20 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath clip-rule evenodd evenodd</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-rule"/>
+	<html:link rel="match" href="reference/clip-path-clip-rule-004-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Test two equal clip-rules 'evenodd' on two different
+	content elements. You should see two green rectangles with a hole. The
+	first one on the top left, the second one slightly shifted to the bottom
+	right.</desc>
+</g>
+<clipPath id="clip1">
+	<polygon points="0 0, 150 0, 150 150, 0 150, 0 25, 125 25, 125 125, 25 125, 25 25, 0 25" clip-rule="evenodd"/>
+	<polygon points="50 50, 200 50, 200 200, 50 200, 50 75, 175 75, 175 175, 75 175, 75 75, 50 75" clip-rule="evenodd"/>
+</clipPath>
+<rect x="0" y="0" height="200" width="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-009.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-009.svg
new file mode 100644
index 0000000..69d70b4e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-009.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath fill-rule</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-rule"/>
+	<html:link rel="match" href="reference/clip-path-square-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">The fill-rule must not affect the winding rule for
+	clipping. A green square should be visible.</desc>
+</g>
+<clipPath id="clip1">
+	<!-- fill-rule must not affect the winding rule for clipping -->
+	<polygon points="25 25, 175 25, 175 175, 25 175, 25 50, 150 50, 150 150, 50 150, 50 50, 25 50" fill-rule="evenodd"/>
+</clipPath>
+<rect x="0" y="0" height="200" width="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-010.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-010.svg
new file mode 100644
index 0000000..594e309
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip-rule-010.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath clip-rule inheritance</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-rule"/>
+	<html:link rel="match" href="reference/clip-path-clip-rule-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">inheritance and overriding of inheritance. The one on
+	the top left should have a hole, the shifted one shouldn't.</desc>
+</g>
+<clipPath id="clip1" clip-rule="evenodd">
+	<polygon points="0 0, 150 0, 150 150, 0 150, 0 25, 125 25, 125 125, 25 125, 25 25, 0 25"/>
+	<polygon points="50 50, 200 50, 200 200, 50 200, 50 75, 175 75, 175 175, 75 175, 75 75, 50 75" clip-rule="nonzero"/>
+</clipPath>
+<rect x="0" y="0" height="200" width="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip.svg
new file mode 100644
index 0000000..530fd01
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-clip.svg
@@ -0,0 +1,19 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath references clipPath</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A clipPath element references another
+	clipPath element. A green square should be visible.</desc>
+</g>
+<clipPath id="clip2">
+	<rect x="50" y="50" width="100" height="100"/>
+</clipPath>
+<clipPath id="clip1" clip-path="url(#clip2)">
+	<circle cx="100" cy="100" r="100"/>
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-clip-001.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-clip-001.svg
new file mode 100644
index 0000000..9991f16
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-clip-001.svg
@@ -0,0 +1,21 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: content of clipPath clipped 1</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Content element of clipPath references second
+	clipPath element and should be clipped by it. A green square should
+	be visible.
+	</desc>
+</g>
+<clipPath id="clip2">
+	<rect x="50" y="50" width="100" height="100"/>
+</clipPath>
+<clipPath id="clip1">
+	<circle cx="100" cy="100" r="100" clip-path="url(#clip2)"/>
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-clip-002.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-clip-002.svg
new file mode 100644
index 0000000..7677e3f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-clip-002.svg
@@ -0,0 +1,21 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: content of clipPath clipped 2</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">One content element of clipPath references second
+	clipPath element and should be clipped by it. Second content element
+	isn't clipped. A green square should be visible.</desc>
+</g>
+<clipPath id="clip2">
+	<rect x="50" y="50" width="100" height="100"/>
+</clipPath>
+<clipPath id="clip1">
+	<circle cx="100" cy="100" r="50"/>
+	<circle cx="100" cy="100" r="75" clip-path="url(#clip2)"/>
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-clip-003.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-clip-003.svg
new file mode 100644
index 0000000..fd1b291
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-clip-003.svg
@@ -0,0 +1,24 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: content of clipPath clipped 3</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Both content elements of clipPath reference
+	different other clipPath elements and should be clipped by them. A
+	green square should be visible.</desc>
+</g>
+<clipPath id="clip2">
+	<rect x="50" y="50" width="100" height="100"/>
+</clipPath>
+<clipPath id="clip3">
+	<rect x="50" y="50" width="100" height="100"/>
+</clipPath>
+<clipPath id="clip1">
+	<circle cx="100" cy="100" r="75" clip-path="url(#clip2)"/>
+	<circle cx="100" cy="100" r="75" clip-path="url(#clip3)"/>
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-invisible.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-invisible.svg
new file mode 100644
index 0000000..0696d4b0
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-invisible.svg
@@ -0,0 +1,19 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Content of clipPath with visibility: hidden</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-invisible-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">From the spec: "If a child element is made invisible
+	by display or visibility it does not contribute to the clipping path."
+	clipPath without content hides the clipped element. Nothing should be
+	visible.
+	</desc>
+</g>
+<clipPath id="clip1">
+	<rect width="100" height="100" style="visibility: hidden;"/>
+</clipPath>
+<rect height="200" width="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-syling.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-syling.svg
new file mode 100644
index 0000000..bfa9d06
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-syling.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath content styling</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Style properties on content elements of clipPath
+	must be ignored. A green square should be visible.</desc>
+</g>
+<clipPath id="clip1" clip-path="url(#clip1)">
+	<rect x="50" y="50" width="100" height="100" stroke="black" stroke-wdith="10" stroke-dasharray="10 10" fill="none"/>
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-use-001.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-use-001.svg
new file mode 100644
index 0000000..b70cdde
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-use-001.svg
@@ -0,0 +1,20 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml"
+	 xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="testmeta">
+	<title>CSS Masking: clipPath reference content with use 1</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Reference content clip shape with use element from
+	defs section. A green square should be visible.</desc>
+</g>
+<defs>
+	<rect id="circle" x="50" y="50" width="100" height="100"/>
+</defs>
+<clipPath id="clip1">
+	<use xlink:href="#circle"/>
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-use-002.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-use-002.svg
new file mode 100644
index 0000000..0596d21c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-use-002.svg
@@ -0,0 +1,24 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml"
+	 xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="testmeta">
+	<title>CSS Masking: clipPath reference content with use 2</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Reference content clip shape with use element from
+	defs section. Afterwards, the clipPath element gets clipped.
+	A green square should be visible.</desc>
+</g>
+<defs>
+	<rect id="rect" x="50" y="50" width="150" height="150"/>
+</defs>
+<clipPath id="clip2">
+	<rect width="150" height="150"/>
+</clipPath>
+<clipPath id="clip1" clip-path="url(#clip2)">
+	<use xlink:href="#rect"/>
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-use-003.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-use-003.svg
new file mode 100644
index 0000000..7935467
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-use-003.svg
@@ -0,0 +1,26 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml"
+	 xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="testmeta">
+	<title>CSS Masking: clipPath reference content with use 3</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Reference content clip shape with use element from
+	defs section. Afterwards, the clipPath element gets clipped.
+	The second clipPath element references the content element with
+	use as well. A green square should be visible.</desc>
+</g>
+<defs>
+	<rect id="rect1" x="50" y="50" width="150" height="150"/>
+	<rect id="rect2" width="150" height="150"/>
+</defs>
+<clipPath id="clip2">
+	<use xlink:href="#rect2"/>
+</clipPath>
+<clipPath id="clip1" clip-path="url(#clip2)">
+	<use xlink:href="#rect1"/>
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-use-004.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-use-004.svg
new file mode 100644
index 0000000..8e2e7a6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-use-004.svg
@@ -0,0 +1,21 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml"
+	 xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="testmeta">
+	<title>CSS Masking: clipPath reference content with use 4</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Reference content clip shape with use element from
+	defs section. Furthermore, the referenced shape gets transformed. A
+	green square should be visible.</desc>
+</g>
+<defs>
+	<rect id="rect" width="100" height="100"/>
+</defs>
+<clipPath id="clip1">
+	<use xlink:href="#rect" transform="translate(50, 50)"/>
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-use-005.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-use-005.svg
new file mode 100644
index 0000000..c7d5ec8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-use-005.svg
@@ -0,0 +1,25 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml"
+	 xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="testmeta">
+	<title>CSS Masking: clipPath reference content with use 5</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">The clipPath elements reference the same content
+	clip shape with use. One use reference gets transformed. The one
+	clipPath element gets clipped by the other one. A green square should
+	be visible.</desc>
+</g>
+<defs>
+	<rect id="rect" width="150" height="150"/>
+</defs>
+<clipPath id="clip2">
+	<use xlink:href="#rect"/>
+</clipPath>
+<clipPath id="clip1" clip-path="url(#clip2)">
+	<use xlink:href="#rect" transform="translate(50, 50)"/>
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-use-006.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-use-006.svg
new file mode 100644
index 0000000..6f9d7e06
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-use-006.svg
@@ -0,0 +1,26 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml"
+	 xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="testmeta">
+	<title>CSS Masking: clipPath reference content with use 6</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">The clipPath elements reference two different
+	content clip shapes with use. One use reference gets transformed.
+	The one clipPath element gets clipped by the other one. A green square
+	should be visible.</desc>
+</g>
+<defs>
+	<rect id="rect1" width="150" height="150"/>
+	<rect id="rect2" width="150" height="150"/>
+</defs>
+<clipPath id="clip2">
+	<use xlink:href="#rect1"/>
+</clipPath>
+<clipPath id="clip1" clip-path="url(#clip2)">
+	<use xlink:href="#rect2" transform="translate(50, 50)"/>
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-use-007.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-use-007.svg
new file mode 100644
index 0000000..7cf0fa0
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-content-use-007.svg
@@ -0,0 +1,25 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml"
+	 xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="testmeta">
+	<title>CSS Masking: clipPath reference content with use 7</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-003-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">The clipPath element has a content use
+	element which references another use element which references a shape.
+	A green square should be visible.</desc>
+</g>
+<defs>
+	<rect width="200" height="200" id="rect"/>
+	<use id="use" xlink:href="#rect"/>
+</defs>
+<clipPath id="clip1">
+	<use xlink:href="#use" />
+</clipPath>
+
+<rect width="400" height="400" fill="red" clip-path="url(#clip1)"/>
+<rect width="200" height="200" fill="green" />
+</svg>
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-css-transform-001.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-css-transform-001.svg
new file mode 100644
index 0000000..35b21f5f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-css-transform-001.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath with CSS Transforms</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">CSS Transforms must apply on the clipPath
+	element. A green square should be visible.</desc>
+</g>
+<clipPath id="clip1" style="transform: scale(10) translate(5px, 5px);">
+	<rect width="10" height="10"/>
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-css-transform-002.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-css-transform-002.svg
new file mode 100644
index 0000000..cc21cb7
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-css-transform-002.svg
@@ -0,0 +1,20 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath with CSS Transforms and 2nd content element</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">CSS Transforms must apply on the clipPath
+	element. This example adds a second content element since that may
+	cause masking in some implementations. A green square should be
+	visible.</desc>
+</g>
+<clipPath id="clip1" style="transform: scale(10) translate(2px, 2px);">
+	<rect width="10" height="10"/>
+	<!-- Second rect may cause masking -->
+	<rect width="5" height="4"/>
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-css-transform-003.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-css-transform-003.svg
new file mode 100644
index 0000000..8f2713d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-css-transform-003.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath with CSS Transforms on content element</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-003-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">CSS Transforms must apply on content element of the
+	clipPath element. A green square should be visible.</desc>
+</g>
+<clipPath id="clip1">
+	<rect width="400" height="400" style="transform: scale(.5);"/>
+</clipPath>
+<rect width="400" height="400" fill="red" clip-path="url(#clip1)"/>
+<rect width="200" height="200" fill="green"/>
+</svg>
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-css-transform-004.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-css-transform-004.svg
new file mode 100644
index 0000000..d290fe1
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-css-transform-004.svg
@@ -0,0 +1,19 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath with CSS Transforms on both content elements</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-003-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">CSS Transforms must apply on both content elements of
+	the clipPath element. A green square should be visible.</desc>
+</g>
+<clipPath id="clip1">
+	<rect width="400" height="400" style="transform: scale(.5)"/>
+	<!-- Second rect may cause masking. -->
+	<rect width="400" height="400" style="transform: scale(.5)"/>
+</clipPath>
+<rect width="400" height="400" fill="red" clip-path="url(#clip1)"/>
+<rect width="200" height="200" fill="green"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-dom-child-changes.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-dom-child-changes.svg
new file mode 100644
index 0000000..611fb3f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-dom-child-changes.svg
@@ -0,0 +1,26 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Dynamic transform on clipPath content element</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-003-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A transformation is applied on the content element of
+	clipPath. A green square should be visible.</desc>
+</g>
+<clipPath id="clip1">
+	<rect width="400" height="400"/>
+</clipPath>
+
+<g clip-path="url(#clip1)">
+	<rect width="400" height="400" fill="red"/>
+	<rect width="200" height="200" fill="green"/>
+</g>
+
+<script>
+var clip = document.getElementById("clip1");
+var rect = clip.firstChild.nextSibling;
+rect.setAttribute("transform", "scale(0.5)");
+</script>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-dom-clippathunits.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-dom-clippathunits.svg
new file mode 100644
index 0000000..edfccbd
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-dom-clippathunits.svg
@@ -0,0 +1,26 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Dynamic change of clipPathUnits on clipPath</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-003-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">The clipPathUnits attribute on the clipPath
+	element gets changed dynamically from objectBoundingBox to userSpaceOnUse.
+	This reduces the clipping area from a size much bigger than the document to
+	the size of the green rectangle. A green square should be visible.</desc>
+</g>
+<clipPath id="clip1" clipPathUnits="objectBoundingBox">
+	<rect width="200" height="200"/>
+</clipPath>
+
+<rect width="400" height="400" fill="red" clip-path="url(#clip1)"/>
+<rect width="200" height="200" fill="green"/>
+
+<script>
+var clip = document.getElementById("clip1");
+var enumeration = clip.clipPathUnits;
+enumeration.baseVal = 1; // Switch to userSpaceOnUse!
+</script>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-dom-href.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-dom-href.svg
new file mode 100644
index 0000000..112c132e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-dom-href.svg
@@ -0,0 +1,25 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Dynamic reference of clipPath element</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-003-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">The clip-path property gets applied to the later
+	clipped rectangle dynamically. A green square should be visible.</desc>
+</g>
+
+<clipPath id="clip1">
+	<rect width="200" height="200"/>
+</clipPath>
+
+<g clip-path="url(#noclip)">
+	<rect width="400" height="400" fill="red"/>
+	<rect width="200" height="200" fill="green"/>
+</g>
+
+<script>
+document.getElementsByTagName("g")[0].setAttribute("clip-path", "url(#clip1)");
+</script>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-dom-id.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-dom-id.svg
new file mode 100644
index 0000000..e8ad7dae
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-dom-id.svg
@@ -0,0 +1,23 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Dynamic change of clipPath id</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-003-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">The id of a clipPath element is changed
+	dynamically. This makes the previous invalid clip path reference
+	of the group valid. A green square should be visible.</desc>
+</g>
+<clipPath id="oldclip" clipPathUnits="userSpaceOnUse">
+	<rect width="200" height="200"/>
+</clipPath>
+<g clip-path="url(#newclip)">
+	<rect width="400" height="400" fill="red"/>
+	<rect width="200" height="200" fill="green"/>
+</g>
+<script>
+document.getElementsByTagName("clipPath")[0].setAttribute("id", "newclip");
+</script>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-invalid.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-invalid.svg
new file mode 100644
index 0000000..85aa3a825
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-invalid.svg
@@ -0,0 +1,21 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath invalid content element</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-invisible-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">clipPath elements get invalid if the content
+	element is not a basic shape or a reference to a basic shape.
+	Invalid clipPath elements let the clipped element disappear.
+	Nothing should be visible.</desc>
+</g>
+<clipPath id="clip1">
+	<!-- nothing should be visible, containers are not allowed in clipPath -->
+	<g>
+	<rect width="100" height="100"/>
+	</g>
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-negative-scale.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-negative-scale.svg
new file mode 100644
index 0000000..33d6a78
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-negative-scale.svg
@@ -0,0 +1,40 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml"
+	 xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="testmeta">
+	<title>CSS Masking: clipPath negative scale</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-negative-scale-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Negative scale factors on clipped elements should
+	be handled correctly by clipPath elements. First clipped, then
+	scaled.</desc>
+</g>
+<defs>
+<g id="img" transform="translate(10,10)">
+	<rect width="200" height="200" fill="red"/>
+	<rect width="100" height="100" fill="green"/>
+	<rect width="50" height="50" fill="blue"/>
+</g>
+</defs>
+
+<clipPath id="clip">
+	<rect x="10" y="10" height="90" width="90"/>
+</clipPath>
+
+<g transform="translate(200, 200)">
+<g transform="matrix(1 0 0 1 -100 -100)" clip-path="url(#clip)">
+	<use xlink:href="#img"/>
+</g>
+<g transform="matrix(-1 0 0 -1 -100 -100)" clip-path="url(#clip)">
+	<use xlink:href="#img"/>
+</g>
+<g transform="matrix(-1 0 0 1 -100 -100)" clip-path="url(#clip)">
+	<use xlink:href="#img"/>
+</g>
+<g transform="matrix(1 0 0 -1 -100 -100)" clip-path="url(#clip)">
+	<use xlink:href="#img"/>
+</g>
+</g>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-no-content-001.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-no-content-001.svg
new file mode 100644
index 0000000..f7943c6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-no-content-001.svg
@@ -0,0 +1,15 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath without content</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-invisible-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">clipPath element without content make the clipped
+	element disappear. Nothing should be visible.</desc>
+</g>
+<clipPath id="clip1">
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-no-content-002.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-no-content-002.svg
new file mode 100644
index 0000000..65df617
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-no-content-002.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath without content 2</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-invisible-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">clipPath element where the clip shape does not
+	intersect with the clipped element make the clipped element disappear.
+	Nothing should be visible.</desc>
+</g>
+<clipPath id="clip1">
+	<circle cx="400" cy="400" r="100"/>
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-no-content-003.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-no-content-003.svg
new file mode 100644
index 0000000..afd8f70
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-no-content-003.svg
@@ -0,0 +1,20 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath and clipPath without intersection</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-invisible-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">If a clipPath element get clipped and there is no
+	intersection with the second clipPath element, the originally clipped
+	element disappears. Nothing should be visible.</desc>
+</g>
+<clipPath id="clip2">
+	<circle cx="400" cy="400" r="100"/>
+</clipPath>
+<clipPath id="clip1" clip-path="url(#clip2)">
+	<rect width="200" height="200"/>
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-no-content-004.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-no-content-004.svg
new file mode 100644
index 0000000..92b27af
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-no-content-004.svg
@@ -0,0 +1,23 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath with invalid/empty content</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-invisible-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">If a clipPath element has an invalid or empty
+	basic shape the clipped element disappears. Nothing should be visible.
+	</desc>
+</g>
+<!--It tests that an empty clip path clips the referencing graphic. Bug 15289.-->
+<clipPath id="nothing">
+</clipPath>
+<clipPath id="emptyrect">
+	<rect width="0" height="0"/>
+</clipPath>
+
+<rect width="200" height="200" fill="red" clip-path="url(#nothing)"/>
+<rect width="200" height="200" fill="red" clip-path="url(#emptyrect)"/>
+</svg>
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-001.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-001.svg
new file mode 100644
index 0000000..d5ef03a
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-001.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath with objectBoundingBox</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">clipPathUnits="objectBoundingBox" changes the
+	behavior of non-percentage values. The dimension of the clipped
+	element is equal to one unit for the content of clipPath.
+	You should see a green square.</desc>
+</g>
+<clipPath id="clip1" clipPathUnits="objectBoundingBox">
+	<rect x="0.25" y="0.25" width="0.5" height="0.5"/>
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-002.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-002.svg
new file mode 100644
index 0000000..8180ddf9
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-002.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml" width="400" height="400">
+<g id="testmeta">
+	<title>CSS Masking: clipPath with objectBoundingBox and percentage</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">clipPathUnits="objectBoundingBox" changes the
+	behavior of percentage values. The behavior is specified by SVG.
+	You should see a green square.</desc>
+</g>
+<clipPath id="clip1" clipPathUnits="objectBoundingBox">
+	<rect x="0.0625%" y="0.0625%" width="0.125%" height="0.125%"/>
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-003.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-003.svg
new file mode 100644
index 0000000..8a7aa0f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-003.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath with objectBoundingBox and scaled clipped element</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-circle-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">The clipped element has a transform. The transform
+	should apply "after" the clipping. You should see a full green circle.
+	</desc>
+</g>
+<clipPath id="clip1" clipPathUnits="objectBoundingBox">
+	<circle cx="0.25" cy="0.25" r="0.25"/>
+</clipPath>
+<rect width="10" height="10" fill="green" transform="scale(20 20)" clip-path="url(#clip1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-004.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-004.svg
new file mode 100644
index 0000000..215d253
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-objectboundingbox-004.svg
@@ -0,0 +1,20 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Nested clipPath with different clipPathUnits</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">The first clipPath element has
+	clipPathUnits="objectBoundingBox", the second userSpaceOnUse (default).
+	Both should be handled accordingly. You should see a green square.</desc>
+</g>
+<clipPath id="clip2">
+	<rect x="50" y="50" width="150" height="150"/>
+</clipPath>
+<clipPath id="clip1" clipPathUnits="objectBoundingBox" clip-path="url(#clip2)">
+	<rect width="0.75" height="0.75"/>
+</clipPath>
+<rect height="200" width="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-g-001.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-g-001.svg
new file mode 100644
index 0000000..6d837af6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-g-001.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath on g element</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A clipPath element on a g element should clip the
+	group with it's content. You should see a green square.</desc>
+</g>
+<clipPath id="clip1">
+	<rect x="50" y="50" width="100" height="100"/>
+</clipPath>
+<g clip-path="url(#clip1)">
+	<rect width="200" height="200" fill="green"/>
+</g>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-g-002.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-g-002.svg
new file mode 100644
index 0000000..a14287cb
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-g-002.svg
@@ -0,0 +1,19 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath on g element and child</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A clipPath element on a g element and it's child
+	element should clip the child first, then the group with it's content.
+	You should see a green square.</desc>
+</g>
+<clipPath id="clip1">
+	<rect x="50" y="50" width="100" height="100"/>
+</clipPath>
+<g clip-path="url(#clip1)">
+	<rect width="200" height="200" fill="green" clip-path="url(clip1)"/>
+</g>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-g-003.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-g-003.svg
new file mode 100644
index 0000000..b3eead8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-g-003.svg
@@ -0,0 +1,19 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath on child of g element</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A clipPath element on a child of g element should
+	just clip the child and not the group. You should see a green square.
+	</desc>
+</g>
+<clipPath id="clip1">
+	<rect x="50" y="50" width="100" height="100"/>
+</clipPath>
+<g>
+	<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</g>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-g-004.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-g-004.svg
new file mode 100644
index 0000000..32eaf003
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-g-004.svg
@@ -0,0 +1,22 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Two different clipPaths on g element and child</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A clipPath element on a child and a differnt
+	clipPath element on g element should clip their targets independent of
+	each other but the child first. You should see a green square.</desc>
+</g>
+<clipPath id="clip2">
+	<rect width="150" height="150"/>
+</clipPath>
+<clipPath id="clip1">
+	<rect x="50" y="50" width="150" height="150"/>
+</clipPath>
+<g clip-path="url(#clip1)">
+	<rect width="200" height="200" fill="green" clip-path="url(#clip2)"/>
+</g>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-g-005.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-g-005.svg
new file mode 100644
index 0000000..c621d91
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-g-005.svg
@@ -0,0 +1,25 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Two different clipPaths with objectBoundingBox on g element and child</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A clipPath element on a child and a differnt
+	clipPath element on g element should clip their targets independent of
+	each other but the child first. When both have
+	clipPathUnits="objectBoundingBox", then the bounding box of each element is
+	taken. Note that clipping on the child influences the bounding box of the
+	group. You should see a green square.</desc>
+</g>
+<clipPath id="clip2" clipPathUnits="objectBoundingBox">
+	<rect width="0.75" height="0.75"/>
+</clipPath>
+<clipPath id="clip1" clipPathUnits="objectBoundingBox">
+	<rect x="0.25" y="0.25" width="0.75" height="0.75"/>
+</clipPath>
+<g clip-path="url(#clip1)">
+	<rect width="200" height="200" fill="green" clip-path="url(#clip2)"/>
+</g>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-marker-001.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-marker-001.svg
new file mode 100644
index 0000000..ae5df003
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-marker-001.svg
@@ -0,0 +1,21 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath on element with marker</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-on-marker-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A clipPath element with marker is clipped as a
+	whole. Note that a marker does not influence the bounding box of an
+	element. You should see a green square with a blur square in it on the top
+	left.</desc>
+</g>
+<clipPath id="clip1" clipPathUnits="objectBoundingBox">
+	<rect width="1" height="1"/>
+</clipPath>
+<marker id="marker1" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="100" markerHeight="100">
+	<rect width="10" height="10" fill="blue"/>
+</marker>
+<path d="M50,50 L150,50 L150,150 L50,150 z" marker-start="url(#marker1)" clip-path="url(#clip1)" fill="green"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-marker-002.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-marker-002.svg
new file mode 100644
index 0000000..4f2ffa4
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-marker-002.svg
@@ -0,0 +1,22 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath on element with marker</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-on-marker-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A clipPath element with marker is clipped as a
+	whole. Note that a marker does not influence the bounding box of an
+	element. The clipping region includes the whole canvas. Nothing gets
+	clipped. You should see a green square with a blur square on top of it,
+	slightly shifted to the top left.</desc>
+</g>
+<clipPath id="clip1" clipPathUnits="userSpaceOnUse">
+	<rect width="100%" height="100%"/>
+</clipPath>
+<marker id="marker1" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="100" markerHeight="100">
+	<rect width="10" height="10" fill="blue"/>
+</marker>
+<path d="M50,50 L150,50 L150,150 L50,150 z" marker-start="url(#marker1)" clip-path="url(#clip1)" fill="green"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-marker-003.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-marker-003.svg
new file mode 100644
index 0000000..45fc0fb
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-marker-003.svg
@@ -0,0 +1,21 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath with objectBoundingBox on element with marker</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-on-marker-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A clipPath element with marker is clipped as a
+	whole. Note that a marker does not influence the bounding box of an
+	element. You should see a green square with a blur square in it on the top
+	left.</desc>
+</g>
+<clipPath id="clip1" clipPathUnits="objectBoundingBox">
+	<rect width="1" height="1"/>
+</clipPath>
+<marker id="marker1" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="100" markerHeight="100">
+	<rect width="10" height="10" fill="blue"/>
+</marker>
+<path d="M50,50 L150,50 L150,150 L50,150 z" marker-end="url(#marker1)" clip-path="url(#clip1)" fill="green"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-svg-001.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-svg-001.svg
new file mode 100644
index 0000000..e92ce1d1
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-svg-001.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml" clip-path="url(#clip1)">
+<g id="testmeta">
+	<title>CSS Masking: clipPath on root &lt;svg></title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A clipPath element can be applied to a root
+	&lt;svg> element. You should see a green square.</desc>
+</g>
+<clipPath id="clip1">
+	<rect x="50" y="50" width="100" height="100"/>
+</clipPath>
+<rect width="200" height="200" fill="green"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-svg-002.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-svg-002.svg
new file mode 100644
index 0000000..94067d4
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-svg-002.svg
@@ -0,0 +1,20 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml" clip-path="url(#clip1)">
+<g id="testmeta">
+	<title>CSS Masking: Clipped clipPath on root &lt;svg></title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A clipPath element can be applied to a root
+	&lt;svg> element. This clipPath element can be clipped itself. You
+	should see a green square.</desc>
+</g>
+<clipPath id="clip2">
+	<rect x="25" y="25" width="150" height="150"/>
+</clipPath>
+<clipPath id="clip1">
+	<rect x="50" y="50" width="100" height="100"/>
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip2)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-use-001.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-use-001.svg
new file mode 100644
index 0000000..8a2cf81c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-use-001.svg
@@ -0,0 +1,20 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml"
+	 xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="testmeta">
+	<title>CSS Masking: clipPath on use</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A use element can be clipped by an clipPath
+	element as well. You should see a green square.</desc>
+</g>
+<defs>
+	<rect id="rect" width="200" height="200" fill="green"/>
+</defs>
+<clipPath id="clip1">
+	<rect x="50" y="50" width="100" height="100"/>
+</clipPath>
+<use xlink:href="#rect" clip-path="url(#clip1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-use-002.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-use-002.svg
new file mode 100644
index 0000000..22e5ab7
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-on-use-002.svg
@@ -0,0 +1,24 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml"
+	 xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="testmeta">
+	<title>CSS Masking: Clipped clipPath on use</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A use element can be clipped by an clipPath
+	element. The clipPath element can be clipped as well. You should see a
+	green square.</desc>
+</g>
+<defs>
+	<rect id="rect" width="200" height="200" fill="green" clip-path="url(#clip2)"/>
+</defs>
+<clipPath id="clip2">
+	<rect width="150" height="150"/>
+</clipPath>
+<clipPath id="clip1">
+	<rect x="50" y="50" width="150" height="150"/>
+</clipPath>
+<use xlink:href="#rect" clip-path="url(#clip1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-precision-001.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-precision-001.svg
new file mode 100644
index 0000000..aa8e5ba7
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-precision-001.svg
@@ -0,0 +1,44 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml"
+	 xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="testmeta">
+	<title>CSS Masking: clipPath precision</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-masks"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#MaskElement"/>
+	<html:link rel="match" href="reference/clip-path-precision-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Various comma values should no influence the precision
+	of the clipPath element. The test should not show any red outlines
+	around the boxes.</desc>
+</g>
+<defs>
+	<g id="paths">
+	<path d="M10 10 h20 v20 h-20 v-20 m1 1 v18 h18 v-18 h-18"/>
+	<rect x="19" y="19" width="2" height="2" />
+	<rect x="5.51" y="0.51" width="2" height="32.3" fill="white"/>
+	<rect x="35.4" y="0.51" width="2" height="32.3" fill="white"/>
+	</g>
+</defs>
+<mask id="mask">
+<use xlink:href="#paths" fill="white" x="60" />
+</mask>
+
+<clipPath id="clipper">
+	<path d="M40 10 h20 v20 h-20 v-20 m1 1 v18 h18 v-18 h-18"/>
+	<rect x="49" y="19" width="2" height="2" />
+	<rect x="35.51" y="0.51" width="2" height="32.3" />
+	<rect x="65.4" y="0.51" width="2" height="32.3" />
+</clipPath>
+
+<use xlink:href="#paths" fill="green" />
+<g clip-path="url(#clipper)">
+	<rect fill="red" x="38" y="8" width="24" height="24" />
+	<use xlink:href="#paths" fill="green" x="30" />
+</g>
+<g mask="url(#mask)">
+	<rect fill="red" x="68" y="8" width="24" height="24" />
+	<use xlink:href="#paths" fill="green" x="60" />
+</g>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-recursion-001.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-recursion-001.svg
new file mode 100644
index 0000000..5d4b7c2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-recursion-001.svg
@@ -0,0 +1,32 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath recursion 1</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-masks"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#MaskElement"/>
+	<html:link rel="match" href="reference/clip-path-invisible-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A clipPath recursion counts as invalid clipping
+	path and makes the element disappear. You should see nothing.</desc>
+</g>
+<defs>
+<clipPath id="clip0">
+	<rect width="1" height="1" clip-path="url(#clip)" />
+</clipPath>
+
+<clipPath id="clip2">
+	<rect width="100" height="100" clip-path="url(#clip0)"/>
+</clipPath>
+
+<clipPath id="clip">
+	<rect width="1" height="1" clip-path="url(#clip2)"/>
+</clipPath>
+
+<mask id="mask1" x="0" y="0" width="1" height="1" maskContentUnits="objectBoundingBox">
+	<rect width="1" height="1" clip-path="url(#clip)" />
+</mask>
+</defs>
+<circle r="500" mask="url(#mask1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-recursion-002.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-recursion-002.svg
new file mode 100644
index 0000000..ae10c06
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-recursion-002.svg
@@ -0,0 +1,24 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml"
+	 xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="testmeta">
+	<title>CSS Masking: clipPath recursion 2</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-invisible-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A clipPath recursion counts as invalid clipping
+	path and makes the element disappear. You should see nothing.</desc>
+</g>
+<defs>
+	<rect x="50" y="150" width="50" height="50" id="rect" fill="none" clip-path="url(#clipPath_1)"/>
+</defs>
+<clipPath id="clipPath_0">
+	<rect x="50" width="50" height="50" clip-path="url(#clipPath_0)"/>
+</clipPath>
+<clipPath id="clipPath_1">
+	<use xlink:href="#rect"/>
+</clipPath>
+<rect x="50" width="100" height="100" fill="red" clip-path="url(#clipPath_0)"/>
+<rect x="50" y="150" width="100" height="100" fill="red" clip-path="url(#clipPath_1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-circle-001.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-circle-001.svg
new file mode 100644
index 0000000..873947943
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-circle-001.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Basic shape circle() on SVG rectangle</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-path"/>
+	<html:link rel="match" href="reference/clip-path-circle-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A basic shape function circle() applied to an SVG
+	rectangle. You should see a full green circle.</desc>
+</g>
+<rect width="100" height="100" fill="green" style="clip-path: circle()"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-circle-002.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-circle-002.svg
new file mode 100644
index 0000000..dca307d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-circle-002.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Basic shape circle() on SVG rectangle with absolute values</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-path"/>
+	<html:link rel="match" href="reference/clip-path-circle-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A basic shape function circle() with absolute values
+	applied to an SVG rectangle. You should see a full green circle.</desc>
+</g>
+<rect width="400" height="400" fill="green" style="clip-path: circle(50px at 50px 50px)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-circle-003.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-circle-003.svg
new file mode 100644
index 0000000..f0110ee
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-circle-003.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Basic shape circle() on SVG rectangle with fill-box</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-path"/>
+	<html:link rel="match" href="reference/clip-path-circle-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A basic shape function circle() applied to an SVG
+	rectangle. The specified keyword fill-box defines the reference box
+	(here objectBoundingBox). You should see a full green circle.</desc>
+</g>
+<rect width="100" height="100" fill="green" stroke="green" stroke-width="10" style="clip-path: circle() fill-box;"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-circle-004.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-circle-004.svg
new file mode 100644
index 0000000..c3db6d6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-circle-004.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Basic shape circle() on SVG rectangle with stroke-box</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-path"/>
+	<html:link rel="match" href="reference/clip-path-circle-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A basic shape function circle() applied to an SVG
+	rectangle. The specified keyword stroke-box defines the reference box
+	stroking bounding box. You should see a full green circle.</desc>
+</g>
+<rect x="10" y="10" width="80" height="80" fill="green" stroke="green" stroke-width="20" style="clip-path: circle() stroke-box;"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-circle-005.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-circle-005.svg
new file mode 100644
index 0000000..90a57e7c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-circle-005.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml" width="100" height="100">
+<g id="testmeta">
+	<title>CSS Masking: Basic shape circle() on SVG rectangle with view-box</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-path"/>
+	<html:link rel="match" href="reference/clip-path-circle-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A basic shape function circle() applied to an SVG
+	rectangle. The specified keyword view-box defines the viewport as reference
+	box. You should see a full green circle.</desc>
+</g>
+<rect width="200" height="200" fill="green" style="clip-path: circle() view-box;"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-ellipse-001.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-ellipse-001.svg
new file mode 100644
index 0000000..511a1976
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-ellipse-001.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Basic shape ellipse() on SVG rectangle</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-path"/>
+	<html:link rel="match" href="reference/clip-path-ellipse-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A basic shape function ellipse() applied to an SVG
+	rectangle. You should see a full green ellipse.</desc>
+</g>
+<rect width="200" height="150" fill="green" style="clip-path: ellipse()"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-ellipse-002.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-ellipse-002.svg
new file mode 100644
index 0000000..08bdc3e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-ellipse-002.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Basic shape ellipse() on SVG rectangle with absolute values</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-path"/>
+	<html:link rel="match" href="reference/clip-path-ellipse-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A basic shape function ellipse() with absolute values
+	applied to an SVG rectangle. You should see a full green ellipse.</desc>
+</g>
+<rect width="200" height="200" fill="green" style="clip-path: ellipse(100px 75px at 100px 75px)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-inset-001.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-inset-001.svg
new file mode 100644
index 0000000..8176766
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-inset-001.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Basic shape inset() on SVG rectangle with percentag values</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-path"/>
+	<html:link rel="match" href="reference/clip-path-shape-inset-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A basic shape function inset() applied to an SVG
+	rectangle. Percentage values are relative to a reference box. If not
+	reference box was specified the objectBoundingBox is used. The inset used
+	per side is specified by the first 10% argument. The second 10% specify the
+	border radius. You should see a green square with rounded corners.</desc>
+</g>
+<rect width="200" height="200" fill="green" style="clip-path: inset(10% round 10%)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-inset-002.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-inset-002.svg
new file mode 100644
index 0000000..2793eda
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-inset-002.svg
@@ -0,0 +1,15 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Basic shape inset() on SVG rectangle with absolute values</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-path"/>
+	<html:link rel="match" href="reference/clip-path-shape-inset-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A basic shape function inset() applied to an SVG
+	rectangle. The inset used per side is specified by the first 20px argument.
+	The second 20px specify the border radius. You should see a green square
+	with rounded corners.</desc>
+</g>
+<rect width="200" height="200" fill="green" style="clip-path: inset(20px round 20px)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-polygon-001.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-polygon-001.svg
new file mode 100644
index 0000000..f474b126
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-polygon-001.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Basic shape polygon() on SVG rectangle with nonzero</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-path"/>
+	<html:link rel="match" href="reference/clip-path-square-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A basic shape function polygon() with absolute values
+	applied to an SVG rectangle. The clip-rule is specified with nonzero. You
+	should see a green square.</desc>
+</g>
+<rect width="200" height="200" fill="green" style="clip-path: polygon(nonzero, 25px 25px, 175px 25px, 175px 175px, 25px 175px, 25px 50px, 150px 50px, 150px 150px, 50px 150px, 50px 50px, 25px 50px)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-polygon-002.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-polygon-002.svg
new file mode 100644
index 0000000..0fd8b1b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-polygon-002.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Basic shape polygon() on SVG rectangle with evenodd</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-path"/>
+	<html:link rel="match" href="reference/clip-path-square-hole-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A basic shape function polygon() with absolute values
+	applied to an SVG rectangle. The clip-rule is specified with evenodd. You
+	should see a green square with an rectangular hole.</desc>
+</g>
+<rect width="200" height="200" fill="green" style="clip-path: polygon(evenodd, 25px 25px, 175px 25px, 175px 175px, 25px 175px, 25px 50px, 150px 50px, 150px 150px, 50px 150px, 50px 50px, 25px 50px)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-polygon-003.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-polygon-003.svg
new file mode 100644
index 0000000..91ee3d0
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-shape-polygon-003.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Basic shape polygon() on SVG rectangle with no clip rule</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-path"/>
+	<html:link rel="match" href="reference/clip-path-square-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A basic shape function polygon() with absolute values
+	applied to an SVG rectangle. The clip-rule is not specified and defaults to
+	nonzero. You should see a green square.</desc>
+</g>
+<rect width="200" height="200" fill="green" style="clip-path: polygon(25px 25px, 175px 25px, 175px 175px, 25px 175px, 25px 50px, 150px 50px, 150px 150px, 50px 150px, 50px 50px, 25px 50px)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-text-001.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-text-001.svg
new file mode 100644
index 0000000..35f9fa0
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-text-001.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath with text</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-text-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A clipPath element can contain text. You should
+	see a green text "CLIP" and nothing else.</desc>
+</g>
+<clipPath id="clip1">
+	<text x="20" y="150" style="font-size: 60px; font-weight: bold;">CLIP</text>
+</clipPath>
+<rect height="200" width="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-text-002.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-text-002.svg
new file mode 100644
index 0000000..d6ae147
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-text-002.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath and text with styling</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-text-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A clipPath element can contain text. Text styling
+	should not influence the clipping path. You should see a green text "CLIP"
+	and nothing else.</desc>
+</g>
+<clipPath id="clip1">
+	<text x="20" y="150" style="font-size:60px; font-weight:bold;" stroke="red" stroke-width="10">CLIP</text>
+</clipPath>
+<rect height="200" width="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-text-003.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-text-003.svg
new file mode 100644
index 0000000..ec7a613
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-text-003.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath with text and a polygon</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-text-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A clipPath element can contain text. Text can be
+	combined with other clipping shapes like polygons. You should see a green
+	text "CLIP" through the hole of a green rectangle.</desc>
+</g>
+<clipPath id="clip1">
+	<text x="20" y="150" style="font-size:60px; font-weight:bold;" stroke="red" stroke-width="10">CLIP</text>
+	<polygon points="0 0, 200 0, 200 200, 0 200, 0 50, 150 50, 150 150, 50 150, 50 50, 0 50" clip-rule="evenodd" />
+</clipPath>
+<rect height="200" width="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-text-004.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-text-004.svg
new file mode 100644
index 0000000..7adbfa2d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-text-004.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath and tspan element with styling</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-text-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A clipPath element can contain text in a
+	tspan element. Text styling on text element or tspan element should not influence
+	the clipping path. You should see a green text "CLIP" and nothing else.
+	</desc>
+</g>
+<clipPath id="clip1">
+	<text x="20" y="150" style="font-size:60px; font-weight:bold;"><tspan stroke="red" fill="none">CLIP</tspan></text>
+</clipPath>
+<rect height="200" width="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-text-005.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-text-005.svg
new file mode 100644
index 0000000..152f401
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-text-005.svg
@@ -0,0 +1,21 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Clipped clipPath with text</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-text-003-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A clipPath element can contain text. The
+	clipPath element can be clipped itself with the text. You should see
+	fragments of a green text "CLIP" and nothing else.
+	</desc>
+</g>
+<clipPath id="clip2">
+	<rect x="50" y="50" width="100" height="100"/>
+</clipPath>
+<clipPath id="clip1" clip-path="url(#clip2)">
+	<text x="20" y="150" style="font-size:60px; font-weight:bold;">CLIP</text>
+</clipPath>
+<rect height="200" width="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-userspaceonuse-001.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-userspaceonuse-001.svg
new file mode 100644
index 0000000..1a3d769
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-userspaceonuse-001.svg
@@ -0,0 +1,20 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Two clipPath elements with different clipPathUnits</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">A clipPath element with clipPathUnits
+	userSpaceOnUse gets clipped by a clipPath element with cliPathUnits
+	objectBoundingBox. You should see a green square.</desc>
+</g>
+<clipPath id="clip2" clipPathUnits="objectBoundingBox">
+	<rect width="0.75" height="0.75"/>
+</clipPath>
+<clipPath id="clip1" clip-path="url(#clip2)">
+	<rect x="50" y="50" width="150" height="150"/>
+</clipPath>
+<rect height="200" width="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-with-opacity.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-with-opacity.svg
new file mode 100644
index 0000000..ac944be
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-with-opacity.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath child with opacity style</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">The opacit on the child of the clipPath should
+	not influence the clipping path. You should see a green square.</desc>
+</g>
+<clipPath id="clip">
+	<rect x="50" y="50" width="100" height="100" opacity=".4"/>
+	<rect x="50" y="50" width="100" height="100"/>
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-with-transform.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-with-transform.svg
new file mode 100644
index 0000000..b3c3b25d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/clip-path-with-transform.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath with transformed child</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="match" href="reference/clip-path-square-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">The child of the clipPath element has a transform
+	that influences the size of the clipping path. You should see a green
+	square.</desc>
+</g>
+<clipPath id="clip1">
+	<rect width="10" height="10" transform="scale(10) translate(5, 5)"/>
+</clipPath>
+<rect width="400" height="400" fill="green" clip-path="url(#clip1)"/>
+</svg>
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-001.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-001.svg
new file mode 100644
index 0000000..0dc0f7c0
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-001.svg
@@ -0,0 +1,40 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath and mask nesting 1</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-masks"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#MaskElement"/>
+	<html:link rel="match" href="reference/mask-nested-clip-path-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Multiple references between clipPath and
+	mask elements. You should see a green square with 4 equally
+	sized and positioned white squares in it.</desc>
+</g>
+<clipPath id="clip0" clipPathUnits="userSpaceOnUse">
+	<rect x="60" y="60" width="30" height="30"/>
+</clipPath>
+
+<clipPath id="clip1" clipPathUnits="objectBoundingBox">
+	<rect x="0.1" y="0.1" width="0.3" height="0.3"/>
+	<rect x="0.1" y="0.6" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip2" clipPathUnits="userSpaceOnUse">
+	<rect x="60" y="10" width="30" height="30" transform="scale(0.01 0.01)"/>
+	<rect x="0" y="0" width="100" height="100" transform="scale(0.01 0.01)" clip-path="url(#clip0)"/>
+</clipPath>
+
+<clipPath id="clip" clipPathUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip1)"/>
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip2)"/>
+</clipPath>
+
+<mask id="mask1" x="0" y="0" width="1" height="1" maskContentUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" fill="white"/>
+	<rect x="0" y="0" width="1" height="1" fill="black" clip-path="url(#clip)" />
+</mask>
+
+<rect width="200" height="200" fill="green" mask="url(#mask1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-002.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-002.svg
new file mode 100644
index 0000000..2e611feb
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-002.svg
@@ -0,0 +1,43 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath and mask nesting 2</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-masks"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#MaskElement"/>
+	<html:link rel="match" href="reference/mask-nested-clip-path-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Multiple references between clipPath and
+	mask elements. You should see a green square with 4 equally
+	sized and positioned white squares in it.</desc>
+</g>
+<clipPath id="clip1" clipPathUnits="objectBoundingBox">
+	<rect x="0.1" y="0.1" width="0.3" height="0.3"/>
+	<rect x="0.1" y="0.6" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip2" clipPathUnits="objectBoundingBox">
+	<rect x="0.6" y="0.1" width="0.3" height="0.3"/>
+	<rect x="0.6" y="0.6" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip" clipPathUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip1)"/>
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip2)"/>
+</clipPath>
+
+<mask id="mask1a" x="0" y="0" width="1" height="1" maskContentUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" fill="white"/>
+	<rect x="0" y="0" width="1" height="1" fill="black" clip-path="url(#clip1)" />
+</mask>
+
+<mask id="mask1b" x="0" y="0" width="1" height="1" maskContentUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" fill="white"/>
+	<rect x="0" y="0" width="1" height="1" fill="black" clip-path="url(#clip2)" />
+</mask>
+
+<g mask="url(#mask1a)">
+	<rect width="200" height="200" fill="green" mask="url(#mask1b)"/>
+</g>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-003.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-003.svg
new file mode 100644
index 0000000..6221253
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-003.svg
@@ -0,0 +1,51 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath and mask nesting 3</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-masks"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#MaskElement"/>
+	<html:link rel="match" href="reference/mask-nested-clip-path-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Multiple references between clipPath and
+	mask elements. You should see a green square with 4 equally
+	sized and positioned white squares in it.</desc>
+</g>
+<clipPath id="clip1" clipPathUnits="objectBoundingBox">
+	<rect x="0.1" y="0.1" width="0.3" height="0.3"/>
+	<rect x="0.1" y="0.6" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip2" clipPathUnits="objectBoundingBox">
+	<rect x="0.6" y="0.1" width="0.3" height="0.3"/>
+	<rect x="0.6" y="0.6" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip" clipPathUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip1)"/>
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip2)"/>
+</clipPath>
+
+<mask id="mask1a" x="0" y="0" width="1" height="1" maskContentUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" fill="white"/>
+	<rect x="0" y="0" width="1" height="1" fill="black" clip-path="url(#clip1)" />
+</mask>
+
+<mask id="mask1b" x="0" y="0" width="1" height="1" maskContentUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" fill="white"/>
+	<rect x="0" y="0" width="1" height="1" fill="black" clip-path="url(#clip2)" />
+</mask>
+
+<mask id="mask2" x="0" y="0" width="1" height="1" maskContentUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" fill="white" mask="url(#mask1a)"/>
+</mask>
+
+<mask id="mask3" x="0" y="0" width="1" height="1" maskContentUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" fill="white" mask="url(#mask1b)"/>
+</mask>
+
+<g mask="url(#mask3)">
+	<rect width="200" height="200" fill="green" mask="url(#mask2)"/>
+</g>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-004.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-004.svg
new file mode 100644
index 0000000..66bb3b7
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-004.svg
@@ -0,0 +1,36 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath and mask nesting 4</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-masks"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#MaskElement"/>
+	<html:link rel="match" href="reference/mask-nested-clip-path-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Multiple references between clipPath and
+	mask elements. You should see a green square with 4 equally
+	sized and positioned white squares in it.</desc>
+</g>
+<clipPath id="clip1" clipPathUnits="objectBoundingBox">
+	<rect x="0.1" y="0.1" width="0.3" height="0.3"/>
+	<rect x="0.1" y="0.6" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip2" clipPathUnits="objectBoundingBox">
+	<rect x="0.6" y="0.1" width="0.3" height="0.3"/>
+	<rect x="0.6" y="0.6" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip" clipPathUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip1)"/>
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip2)"/>
+</clipPath>
+
+<mask id="mask1" x="0" y="0" width="1" height="1" maskContentUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" fill="white"/>
+	<rect x="0" y="0" width="1" height="1" fill="black" clip-path="url(#clip)" />
+</mask>
+
+<rect width="200" height="200" fill="green" mask="url(#mask1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-005.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-005.svg
new file mode 100644
index 0000000..9317ce4e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-005.svg
@@ -0,0 +1,35 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath and mask nesting 5</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-masks"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#MaskElement"/>
+	<html:link rel="match" href="reference/mask-nested-clip-path-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Multiple references between clipPath and
+	mask elements. You should see 4 equally sized and positioned green
+	squares.</desc>
+</g>
+<clipPath id="clip1" clipPathUnits="objectBoundingBox">
+	<rect x="0.1" y="0.1" width="0.3" height="0.3"/>
+	<rect x="0.1" y="0.6" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip2" clipPathUnits="objectBoundingBox">
+	<rect x="0.6" y="0.1" width="0.3" height="0.3"/>
+	<rect x="0.6" y="0.6" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip" clipPathUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip1)"/>
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip2)"/>
+</clipPath>
+
+<mask id="mask2" x="0" y="0" width="1" height="1" maskContentUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" fill="white" clip-path="url(#clip)" />
+</mask>
+
+<rect width="200" height="200" fill="green" mask="url(#mask2)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-006.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-006.svg
new file mode 100644
index 0000000..6ab2a20f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-006.svg
@@ -0,0 +1,35 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath and mask nesting 6</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-masks"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#MaskElement"/>
+	<html:link rel="match" href="reference/mask-nested-clip-path-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Multiple references between clipPath and
+	mask elements. You should see 4 equally sized and positioned green
+	squares.</desc>
+</g>
+<clipPath id="clip1" clipPathUnits="objectBoundingBox">
+	<rect x="0.1" y="0.1" width="0.3" height="0.3"/>
+	<rect x="0.1" y="0.6" width="0.3" height="0.3"/>
+	<rect x="0.6" y="0.1" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip2" clipPathUnits="objectBoundingBox">
+	<rect x="0.6" y="0.6" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip" clipPathUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip1)"/>
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip2)"/>
+</clipPath>
+
+<mask id="mask2" x="0" y="0" width="1" height="1" maskContentUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" fill="white" clip-path="url(#clip)" />
+</mask>
+
+<rect width="200" height="200" fill="green" mask="url(#mask2)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-007.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-007.svg
new file mode 100644
index 0000000..27f3724
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-007.svg
@@ -0,0 +1,36 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath and mask nesting 7</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-masks"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#MaskElement"/>
+	<html:link rel="match" href="reference/mask-nested-clip-path-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Multiple references between clipPath and
+	mask elements. You should see a green square with 4 equally
+	sized and positioned white squares in it.</desc>
+</g>
+<clipPath id="clip1" clipPathUnits="objectBoundingBox">
+	<rect x="0.1" y="0.1" width="0.3" height="0.3"/>
+	<rect x="0.1" y="0.6" width="0.3" height="0.3"/>
+	<rect x="0.6" y="0.1" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip2" clipPathUnits="objectBoundingBox">
+	<rect x="0.6" y="0.6" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip" clipPathUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip1)"/>
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip2)"/>
+</clipPath>
+
+<mask id="mask1" x="0" y="0" width="1" height="1" maskContentUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" fill="white"/>
+	<rect x="0" y="0" width="1" height="1" fill="black" clip-path="url(#clip)" />
+</mask>
+
+<rect width="200" height="200" fill="green" mask="url(#mask1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-008.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-008.svg
new file mode 100644
index 0000000..b3f33e19
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-008.svg
@@ -0,0 +1,44 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath and mask nesting 8</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-masks"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#MaskElement"/>
+	<html:link rel="match" href="reference/mask-nested-clip-path-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Multiple references between clipPath and
+	mask elements. You should see a green square with 4 equally
+	sized and positioned white squares in it.</desc>
+</g>
+<clipPath id="clip1" clipPathUnits="objectBoundingBox">
+	<rect x="0.1" y="0.1" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip2" clipPathUnits="objectBoundingBox">
+	<rect x="0.1" y="0.6" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip3" clipPathUnits="objectBoundingBox">
+	<rect x="0.6" y="0.1" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip4" clipPathUnits="objectBoundingBox">
+	<rect x="0.6" y="0.6" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip" clipPathUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip1)"/>
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip2)"/>
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip3)"/>
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip4)"/>
+</clipPath>
+
+<mask id="mask1" x="0" y="0" width="1" height="1" maskContentUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" fill="white"/>
+	<rect x="0" y="0" width="1" height="1" fill="black" clip-path="url(#clip)" />
+</mask>
+
+<rect width="200" height="200" fill="green" mask="url(#mask1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-009.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-009.svg
new file mode 100644
index 0000000..955edd33
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-009.svg
@@ -0,0 +1,43 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: clipPath and mask nesting 9</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-masks"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#MaskElement"/>
+	<html:link rel="match" href="reference/mask-nested-clip-path-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Multiple references between clipPath and
+	mask elements. You should see 4 equally sized and positioned green
+	squares.</desc>
+</g>
+<clipPath id="clip1" clipPathUnits="objectBoundingBox">
+	<rect x="0.1" y="0.1" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip2" clipPathUnits="objectBoundingBox">
+	<rect x="0.1" y="0.6" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip3" clipPathUnits="objectBoundingBox">
+	<rect x="0.6" y="0.1" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip4" clipPathUnits="objectBoundingBox">
+	<rect x="0.6" y="0.6" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip" clipPathUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip1)"/>
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip2)"/>
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip3)"/>
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip4)"/>
+</clipPath>
+
+<mask id="mask2" x="0" y="0" width="1" height="1" maskContentUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" fill="white" clip-path="url(#clip)" />
+</mask>
+
+<rect width="200" height="200" fill="green" mask="url(#mask2)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-010.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-010.svg
new file mode 100644
index 0000000..dc1040e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-010.svg
@@ -0,0 +1,45 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml" width="10000" height="400">
+<g id="testmeta">
+	<title>CSS Masking: clipPath and mask nesting 10</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-masks"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#MaskElement"/>
+	<html:link rel="match" href="reference/mask-nested-clip-path-003-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Multiple references between clipPath and
+	mask elements. You should see 2 equally sized and positioned green
+	squares next to each other.</desc>
+</g>
+<defs>
+<mask id="mask">
+	<!-- forcing a repaintRect offset -->
+	<rect x="100" width="1" height="1" fill="black"/>
+	<rect x="200" width="8092" height="100" fill="white"/>
+</mask>
+
+<clipPath id="clip1">
+	<!-- forcing clipping via masking -->
+	<path d="M 0 0 V 100 H 10000 V 0 Z"/>
+</clipPath>
+
+<clipPath id="clip2" clip-path="url(#clip1)">
+	<path d="M 100 0 H 200 V 200 H 8292 V 0 Z"/>
+</clipPath>
+
+<mask id="crop">
+	<rect width="300" height="100" fill="white"/>
+</mask>
+</defs>
+
+<g mask="url(#crop)" transform="translate(-100, 0)">
+	<rect width="10000" height="400" fill="red" mask="url(#mask)"/>
+	<rect x="199" width="101" height="100" fill="green"/>
+</g>
+
+<g mask="url(#crop)" transform="translate(100, 0)">
+	<rect width="10000" height="400" fill="red" clip-path="url(#clip2)"/>
+	<rect x="199" width="101" height="100" fill="green"/>
+</g>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-panning-001.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-panning-001.svg
new file mode 100644
index 0000000..16890a58
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-panning-001.svg
@@ -0,0 +1,48 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Panning with clipPath and mask nesting 1</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-masks"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#MaskElement"/>
+	<html:link rel="match" href="reference/mask-nested-clip-path-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Multiple references between clipPath and
+	mask elements. The current viewport gets translated by panning. You
+	should see a green square with 4 equally sized and positioned white squares
+	in it.</desc>
+</g>
+<clipPath id="clip1" clipPathUnits="objectBoundingBox">
+	<rect x="0.1" y="0.1" width="0.3" height="0.3"/>
+	<rect x="0.1" y="0.6" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip2" clipPathUnits="objectBoundingBox">
+	<rect x="0.6" y="0.1" width="0.3" height="0.3"/>
+	<rect x="0.6" y="0.6" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip" clipPathUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip1)"/>
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip2)"/>
+</clipPath>
+
+<mask id="mask1a" x="0" y="0" width="1" height="1" maskContentUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" fill="white"/>
+	<rect x="0" y="0" width="1" height="1" fill="black" clip-path="url(#clip1)" />
+</mask>
+
+<mask id="mask1b" x="0" y="0" width="1" height="1" maskContentUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" fill="white"/>
+	<rect x="0" y="0" width="1" height="1" fill="black" clip-path="url(#clip2)" />
+</mask>
+
+<g mask="url(#mask1a)" transform="translate(75, 0)">
+	<rect width="200" height="200" fill="green" mask="url(#mask1b)"/>
+</g>
+<script>
+var translate = document.getElementsByTagName('svg')[0].currentTranslate;
+translate.x = -75;
+</script>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-panning-002.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-panning-002.svg
new file mode 100644
index 0000000..e5e42678
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-nested-clip-path-panning-002.svg
@@ -0,0 +1,56 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Panning with clipPath and mask nesting 2</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-masks"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#MaskElement"/>
+	<html:link rel="match" href="reference/mask-nested-clip-path-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">Multiple references between clipPath and
+	mask elements. The current viewport gets translated by panning. You
+	should see a green square with 4 equally sized and positioned white squares
+	in it.</desc>
+</g>
+<clipPath id="clip1" clipPathUnits="objectBoundingBox">
+	<rect x="0.1" y="0.1" width="0.3" height="0.3"/>
+	<rect x="0.1" y="0.6" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip2" clipPathUnits="objectBoundingBox">
+	<rect x="0.6" y="0.1" width="0.3" height="0.3"/>
+	<rect x="0.6" y="0.6" width="0.3" height="0.3"/>
+</clipPath>
+
+<clipPath id="clip" clipPathUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip1)"/>
+	<rect x="0" y="0" width="1" height="1" clip-path="url(#clip2)"/>
+</clipPath>
+
+<mask id="mask1a" x="0" y="0" width="1" height="1" maskContentUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" fill="white"/>
+	<rect x="0" y="0" width="1" height="1" fill="black" clip-path="url(#clip1)" />
+</mask>
+
+<mask id="mask1b" x="0" y="0" width="1" height="1" maskContentUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" fill="white"/>
+	<rect x="0" y="0" width="1" height="1" fill="black" clip-path="url(#clip2)" />
+</mask>
+
+<mask id="mask2" x="0" y="0" width="1" height="1" maskContentUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" fill="white" mask="url(#mask1a)"/>
+</mask>
+
+<mask id="mask3" x="0" y="0" width="1" height="1" maskContentUnits="objectBoundingBox">
+	<rect x="0" y="0" width="1" height="1" fill="white" mask="url(#mask1b)"/>
+</mask>
+
+<g mask="url(#mask3)" transform="translate(75, 0)">
+	<rect width="200" height="200" fill="green" mask="url(#mask2)"/>
+</g>
+<script>
+var translate = document.getElementsByTagName('svg')[0].currentTranslate;
+translate.x = -75;
+</script>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-objectboundingbox-content-clip-transform.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-objectboundingbox-content-clip-transform.svg
new file mode 100644
index 0000000..fc1f7688
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-objectboundingbox-content-clip-transform.svg
@@ -0,0 +1,23 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: mask with objectBoundingBox gets clipped 1</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-masks"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#MaskElement"/>
+	<html:link rel="match" href="reference/mask-content-clip-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">The mask element with
+	maskContentUnits="objectBoundingBox" gets clipped. The clipping path has a
+	transform that scales the content down. You should see a full green circle.
+	</desc>
+</g>
+<clipPath id="clip">
+	<circle cx="50" cy="50" r="25" transform="scale(0.01 0.01)"/>
+</clipPath>
+<mask id="mask" x="0" y="0" width="1" height="1" maskContentUnits="objectBoundingBox">
+	<rect x="0.25" y="0.25" width="0.5" height="0.5" fill="white" clip-path="url(#clip)"/>
+</mask>
+<rect x="0" y="0" height="200" width="200" fill="green" mask="url(#mask)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-objectboundingbox-content-clip.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-objectboundingbox-content-clip.svg
new file mode 100644
index 0000000..a8d8a12
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-objectboundingbox-content-clip.svg
@@ -0,0 +1,23 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: mask with objectBoundingBox gets clipped 2</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-masks"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#MaskElement"/>
+	<html:link rel="match" href="reference/mask-content-clip-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">The mask element with
+	maskContentUnits="objectBoundingBox" gets clipped. The clipping path has a
+	coordinates in the mask's user space. You should see a full green circle.
+	</desc>
+</g>
+<clipPath id="clip">
+	<circle cx="0.5" cy="0.5" r="0.25"/>
+</clipPath>
+<mask id="mask" x="0" y="0" width="1" height="1" maskContentUnits="objectBoundingBox">
+	<rect x="0.25" y="0.25" width="0.5" height="0.5" fill="white" clip-path="url(#clip)"/>
+</mask>
+<rect x="0" y="0" height="200" width="200" fill="green" mask="url(#mask)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-userspaceonuse-content-clip-transform.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-userspaceonuse-content-clip-transform.svg
new file mode 100644
index 0000000..b979ff3
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-userspaceonuse-content-clip-transform.svg
@@ -0,0 +1,23 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: mask with userSpaceOnUse gets clipped 1</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-masks"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#MaskElement"/>
+	<html:link rel="match" href="reference/mask-content-clip-002-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">The mask element with
+	maskContentUnits="userSpaceOnUse" gets clipped. The clipping path has a
+	transform that scales the content down. You should see a full green circle.
+	</desc>
+</g>
+<clipPath id="clip" clipPathUnits="userSpaceOnUse">
+	<circle cx="50" cy="50" r="25" transform="scale(0.01 0.01)"/>
+</clipPath>
+<mask id="mask" x="0" y="0" width="200" height="200" maskUnits="userSpaceOnUse" maskContentUnits="objectBoundingBox">
+	<rect x="0.25" y="0.25" width="0.5" height="0.5" fill="white" clip-path="url(#clip)"/>
+</mask>
+<rect x="0" y="0" height="200" width="200" fill="green" mask="url(#mask)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-userspaceonuse-content-clip.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-userspaceonuse-content-clip.svg
new file mode 100644
index 0000000..c9f3cf8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/mask-userspaceonuse-content-clip.svg
@@ -0,0 +1,23 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: mask with userSpaceOnUse gets clipped 2</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-clipping-paths"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#svg-masks"/>
+	<html:link rel="help" href="http://www.w3.org/TR/css-masking-1/#MaskElement"/>
+	<html:link rel="match" href="reference/mask-content-clip-001-ref.svg" />
+	<metadata class="flags">svg</metadata>
+	<desc class="assert">The mask element with
+	maskContentUnits="userSpaceOnUse" gets clipped. The clipping path has a
+	coordinates in the mask's user space. You should see a full green circle.
+	</desc>
+</g>
+<clipPath id="clip" clipPathUnits="userSpaceOnUse">
+	<circle cx="0.5" cy="0.5" r="0.25"/>
+</clipPath>
+<mask id="mask" x="0" y="0" width="200" height="200" maskUnits="userSpaceOnUse" maskContentUnits="objectBoundingBox">
+	<rect x="0.25" y="0.25" width="0.5" height="0.5" fill="white" clip-path="url(#clip)"/>
+</mask>
+<rect x="0" y="0" height="200" width="200" fill="green" mask="url(#mask)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-circle-001-ref.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-circle-001-ref.svg
new file mode 100644
index 0000000..56b7fbe
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-circle-001-ref.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Reftest reference</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+</g>
+<clipPath id="clip1" clipPathUnits="objectBoundingBox">
+	<circle cx="0.25" cy="0.25" r="0.25"/>
+</clipPath>
+<rect width="200" height="200" fill="green" clip-path="url(#clip1)"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-clip-rule-001-ref.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-clip-rule-001-ref.svg
new file mode 100644
index 0000000..eaeb6b2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-clip-rule-001-ref.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Reftest reference</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+</g>
+<polygon points="0 0, 150 0, 150 150, 0 150, 0 25, 125 25, 125 125, 25 125, 25 25, 0 25" fill-rule="evenodd" fill="green"/>
+<polygon points="50 50, 200 50, 200 200, 50 200, 50 75, 175 75, 175 175, 75 175, 75 75, 50 75" fill-rule="nonzero" fill="green"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-clip-rule-002-ref.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-clip-rule-002-ref.svg
new file mode 100644
index 0000000..d6c87cd
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-clip-rule-002-ref.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Reftest reference</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+</g>
+<polygon points="0 0, 150 0, 150 150, 0 150, 0 25, 125 25, 125 125, 25 125, 25 25, 0 25" fill-rule="nonzero" fill="green"/>
+<polygon points="50 50, 200 50, 200 200, 50 200, 50 75, 175 75, 175 175, 75 175, 75 75, 50 75" fill-rule="nonzero" fill="green"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-clip-rule-003-ref.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-clip-rule-003-ref.svg
new file mode 100644
index 0000000..3247b87
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-clip-rule-003-ref.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Reftest reference</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+</g>
+<polygon points="0 0, 150 0, 150 150, 0 150, 0 25, 125 25, 125 125, 25 125, 25 25, 0 25" fill-rule="nonzero" fill="green"/>
+<polygon points="50 50, 200 50, 200 200, 50 200, 50 75, 175 75, 175 175, 75 175, 75 75, 50 75" fill-rule="evenodd" fill="green"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-ellipse-001-ref.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-ellipse-001-ref.svg
new file mode 100644
index 0000000..2532eb3
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-ellipse-001-ref.svg
@@ -0,0 +1,12 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Reftest reference</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+</g>
+<defs>
+<clipPath id="clip">
+	<ellipse cx="100" cy="75" rx="100" ry="75"/>
+</clipPath>
+</defs>
+<rect width="200" height="150" fill="green" clip-path="url(#clip)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-invisible-ref.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-invisible-ref.svg
new file mode 100644
index 0000000..15cfd5f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-invisible-ref.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Reftest reference</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+</g>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-negative-scale-ref.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-negative-scale-ref.svg
new file mode 100644
index 0000000..be24efa
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-negative-scale-ref.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Reftest reference</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+</g>
+<rect width="90" height="90" fill="green"/>
+<rect x="40" y="40" width="50" height="50" fill="blue"/>
+<rect x="110" width="90" height="90" fill="green"/>
+<rect x="110" y="40" width="50" height="50" fill="blue"/>
+<rect y="110" width="90" height="90" fill="green"/>
+<rect x="40" y="110" width="50" height="50" fill="blue"/>
+<rect x="110" y="110" width="90" height="90" fill="green"/>
+<rect x="110" y="110" width="50" height="50" fill="blue"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-on-marker-001-ref.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-on-marker-001-ref.svg
new file mode 100644
index 0000000..693c72e4
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-on-marker-001-ref.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Reftest reference</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+</g>
+<rect x="50" y="50" width="100" height="100" fill="green"/>
+<rect x="50" y="50" width="50" height="50" fill="blue"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-on-marker-002-ref.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-on-marker-002-ref.svg
new file mode 100644
index 0000000..21cf9cd
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-on-marker-002-ref.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Reftest reference</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+</g>
+<rect x="50" y="50" width="100" height="100" fill="green"/>
+<rect width="100" height="100" fill="blue"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-precision-001-ref.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-precision-001-ref.svg
new file mode 100644
index 0000000..4c83c9d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-precision-001-ref.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml"
+	 xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="testmeta">
+	<title>CSS Masking: Reftest reference</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+</g>
+<defs>
+	<g id="paths">
+	<path d="M10 10 h20 v20 h-20 v-20 m1 1 v18 h18 v-18 h-18"/>
+	<rect x="19" y="19" width="2" height="2" />
+	<rect x="5.51" y="0.51" width="2" height="32.3" fill="white"/>
+	<rect x="35.4" y="0.51" width="2" height="32.3" fill="white"/>
+	</g>
+</defs>
+<use xlink:href="#paths" fill="green"/>
+<use xlink:href="#paths" fill="green" x="30"/>
+<use xlink:href="#paths" fill="green" x="60"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-shape-inset-001-ref.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-shape-inset-001-ref.svg
new file mode 100644
index 0000000..e458ad99
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-shape-inset-001-ref.svg
@@ -0,0 +1,12 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Reftest reference</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+</g>
+<defs>
+<clipPath id="clip">
+	<rect x="20" y="20" width="160" height="160" rx="20" ry="20"/>
+</clipPath>
+</defs>
+<rect width="200" height="200" fill="green" clip-path="url(#clip)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-square-001-ref.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-square-001-ref.svg
new file mode 100644
index 0000000..8ddfdaa7
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-square-001-ref.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Reftest reference</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+</g>
+<polygon points="25 25, 175 25, 175 175, 25 175, 25 50, 150 50, 150 150, 50 150, 50 50, 25 50" fill-rule="nonzero" fill="green"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg
new file mode 100644
index 0000000..e8e03bf
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-square-002-ref.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Reftest reference</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+</g>
+<rect x="50" y="50" width="100" height="100" fill="green"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-square-003-ref.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-square-003-ref.svg
new file mode 100644
index 0000000..e7709d4
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-square-003-ref.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Reftest reference</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+</g>
+<rect width="200" height="200" fill="green" />
+</svg>
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-square-hole-001-ref.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-square-hole-001-ref.svg
new file mode 100644
index 0000000..7e1cb1b3
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-square-hole-001-ref.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Reftest reference</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+</g>
+<polygon points="25 25, 175 25, 175 175, 25 175, 25 50, 150 50, 150 150, 50 150, 50 50, 25 50" fill-rule="evenodd" fill="green"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-text-001-ref.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-text-001-ref.svg
new file mode 100644
index 0000000..4b3f84b8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-text-001-ref.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Reftest reference</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+</g>
+<text x="20" y="150" fill="green" style="font-size:60px; font-weight:bold;">CLIP</text>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-text-002-ref.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-text-002-ref.svg
new file mode 100644
index 0000000..fccd7638
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-text-002-ref.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Reftest reference</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+</g>
+<text x="20" y="150" fill="green" style="font-size:60px; font-weight:bold;">CLIP</text>
+<polygon points="0 0, 200 0, 200 200, 0 200, 0 50, 150 50, 150 150, 50 150, 50 50, 0 50" fill-rule="evenodd" fill="green"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-text-003-ref.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-text-003-ref.svg
new file mode 100644
index 0000000..fa2cb815
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/clip-path-text-003-ref.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Reftest reference</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+</g>
+<clipPath id="clip2">
+	<rect x="50" y="50" width="100" height="100"/>
+</clipPath>
+<text x="20" y="150" style="font-size:60px; font-weight:bold;" clip-path="url(#clip2)" fill="green">CLIP</text>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/mask-content-clip-001-ref.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/mask-content-clip-001-ref.svg
new file mode 100644
index 0000000..0aa52333
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/mask-content-clip-001-ref.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Reftest reference</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+</g>
+<mask id="mask" x="0" y="0" width="1" height="1">
+	<circle cx="100" cy="100" r="50" fill="white"/>
+</mask>
+<rect x="0" y="0" height="200" width="200" fill="green" mask="url(#mask)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/mask-content-clip-002-ref.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/mask-content-clip-002-ref.svg
new file mode 100644
index 0000000..4da47823
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/mask-content-clip-002-ref.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Reftest reference</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+</g>
+<mask id="mask" x="0" y="0" width="1" height="1">
+	<circle cx="10000" cy="10000" r="5000" fill="white" transform="scale(0.01 0.01)"/>
+</mask>
+<rect x="0" y="0" height="200" width="200" fill="green" mask="url(#mask)"/>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-001-ref.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-001-ref.svg
new file mode 100644
index 0000000..e080c95
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-001-ref.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Reftest reference</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+</g>
+<rect width="200" height="200" fill="green"/>
+<rect x="20" y="20" width="60" height="60" fill="white"/>
+<rect x="120" y="20" width="60" height="60" fill="white"/>
+<rect x="20" y="120" width="60" height="60" fill="white"/>
+<rect x="120" y="120" width="60" height="60" fill="white"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-002-ref.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-002-ref.svg
new file mode 100644
index 0000000..7b54566
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-002-ref.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+<g id="testmeta">
+	<title>CSS Masking: Reftest reference</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+</g>
+<rect x="20" y="20" width="60" height="60" fill="green"/>
+<rect x="120" y="20" width="60" height="60" fill="green"/>
+<rect x="20" y="120" width="60" height="60" fill="green"/>
+<rect x="120" y="120" width="60" height="60" fill="green"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-003-ref.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-003-ref.svg
new file mode 100644
index 0000000..0133005
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path-svg-content/reference/mask-nested-clip-path-003-ref.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml" width="10000" height="400">
+<g id="testmeta">
+	<title>CSS Masking: Reftest reference</title>
+	<html:link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com"/>
+</g>
+<rect x="99" width="101" height="100" fill="green"/>
+<rect x="299" width="101" height="100" fill="green"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-001.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-001.html
new file mode 100644
index 0000000..d38b909
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-001.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip-path property and circle function with absolute values</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="author" title="Denise White" href="mailto:denisegwhite@outlook.com">
+    <link rel="author" title="Laury Kenton" href="mailto:webshiva@mac.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-path">
+    <link rel="match" href="reference/clip-path-circle-ref.html">
+    <meta name="assert" content="The clip-path property takes the basic shape
+    'circle' for clipping. Test absolute values for arguments. On pass you
+    should see a green circle and no red.">
+</head>
+<body>
+    <p>The test passes if there is a full green circle.</p>
+    <div style="width: 200px; height: 200px; background-color: green; clip-path: circle();"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-002.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-002.html
new file mode 100644
index 0000000..ab672aa
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-002.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip-path property and circle function with percentage values</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-path">
+    <link rel="match" href="reference/clip-path-circle-ref.html">
+    <meta name="assert" content="The clip-path property takes the basic shape
+    'circle' for clipping. Test percentage values for arguments. If no
+    reference box was specified, percentage is relative to border-box. On pass
+    there should be a green circle.">
+</head>
+<body>
+    <p>The test passes if there is a full green circle.</p>
+    <div style="width: 200px; height: 200px; background-color: green; clip-path: circle(50% at 50% 50%);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-003.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-003.html
new file mode 100644
index 0000000..6c6eadb
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-003.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip-path property and circle function with percentage radius</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-path">
+    <link rel="match" href="reference/clip-path-circle-ref.html">
+    <meta name="assert" content="The clip-path property takes the basic shape
+    'circle' for clipping. Test percentage value as argument for radius and no
+    position arguments. The circle should be in the center of the element. On
+    pass there should be a green circle.">
+</head>
+<body>
+    <p>The test passes if there is a full green circle.</p>
+    <div style="width: 200px; height: 200px; background-color: green; clip-path: circle(50%);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-004.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-004.html
new file mode 100644
index 0000000..6244d38
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-004.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip-path property and circle function with percentage position</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-path">
+    <link rel="match" href="reference/clip-path-circle-ref.html">
+    <meta name="assert" content="The clip-path property takes the basic shape
+    'circle' for clipping. Test percentage value as argument for position and
+    no radius argument. The circle must behave like it has a radius of
+    'closest-side'. On pass there should be a green circle.">
+</head>
+<body>
+    <p>The test passes if there is a full green circle.</p>
+    <div style="width: 200px; height: 200px; background-color: green; clip-path: circle(at 50%);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-005.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-005.html
new file mode 100644
index 0000000..bcabc72d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-005.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip-path property and circle function with closest-side</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-path">
+    <link rel="match" href="reference/clip-path-circle-ref.html">
+    <meta name="assert" content="The clip-path property takes the basic shape
+    'circle' for clipping. The circle has a radius of 'closest-side'. This test
+    has a squred div-box. Therefore, 'closest-side', 50% and 'farthest-side'
+    show the same behavior. On pass there should be a green circle.">
+</head>
+<body>
+    <p>The test passes if there is a full green circle.</p>
+    <div style="width: 200px; height: 200px; background-color: green; clip-path: circle(closest-side);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-006.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-006.html
new file mode 100644
index 0000000..9de416a
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-006.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip-path property and circle function with farthest-side</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-path">
+    <link rel="match" href="reference/clip-path-circle-ref.html">
+    <meta name="assert" content="The clip-path property takes the basic shape
+    'circle' for clipping. The circle has a radius of 'farthest-side'. This
+    test has a squred div-box. Therefore, 'closest-side', 50% and
+    'farthest-side' show the same behavior. On pass there should be a green
+    circle.">
+</head>
+<body>
+    <p>The test passes if there is a full green circle.</p>
+    <div style="width: 200px; height: 200px; background-color: green; clip-path: circle(farthest-side);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-007.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-007.html
new file mode 100644
index 0000000..7ff3fdd
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-007.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip-path property and circle with closest-side on rectangular div 1</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-path">
+    <link rel="match" href="reference/clip-path-circle-2-ref.html">
+    <meta name="assert" content="The clip-path property takes the basic shape
+    'circle' for clipping. The clipped div box is twice as wide as it is
+    height. With 'closest-side', there should be a full green circle.">
+    <style type="text/css">
+    body, div {
+        padding: 0;
+        margin: 0;
+    }
+    </style>
+</head>
+<body>
+    <p>The test passes if there is a full green circle.</p>
+    <div style="width: 400px; height: 200px; background-color: green; clip-path: circle(closest-side);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-008.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-008.html
new file mode 100644
index 0000000..31ae8ee5
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-circle-008.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip-path property and circle with closest-side on rectangular div 2</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-clip-path">
+    <link rel="match" href="reference/clip-path-circle-3-ref.html">
+    <meta name="assert" content="The clip-path property takes the basic shape
+    'circle' for clipping. The clipped div box is twice as high as it is
+    wide. With 'closest-side', there should be a full green circle.">
+    <style type="text/css">
+    body, div {
+        padding: 0;
+        margin: 0;
+    }
+    </style>
+</head>
+<body>
+    <p>The test passes if there is a full green circle.</p>
+    <div style="width: 200px; height: 400px; background-color: green; clip-path: circle(closest-side);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-element-userSpaceOnUse-001.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-element-userSpaceOnUse-001.html
new file mode 100644
index 0000000..1381f53
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-element-userSpaceOnUse-001.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip-path property and external clipPath reference with userSpaceOnUse - 1</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement">
+    <link rel="match" href="reference/clip-path-rectangle-ref.html">
+    <meta name="assert" content="The clip-path property takes an external reference to a clipPath element for clipping.
+        On pass you should see a green box.">
+</head>
+<body>
+    <p>The test passes if there is a green box.</p>
+    <div style="width: 150px; height: 100px; border: solid red 50px; background-color: green; clip-path: url(#clip);"></div>
+
+    <svg>
+        <clipPath id="clip">
+            <rect x="50" y="50" width="150px" height="100" />
+        </clipPath>
+    </svg>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-element-userSpaceOnUse-002.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-element-userSpaceOnUse-002.html
new file mode 100644
index 0000000..846c5e7
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-element-userSpaceOnUse-002.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip-path property and external clipPath reference with userSpaceOnUse - 2</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement">
+    <link rel="match" href="reference/clip-path-rectangle-ref.html">
+    <meta name="assert" content="The clip-path property takes an reference to a clipPath element for clipping.
+        On pass you should see a green with a blue border.">
+</head>
+<body>
+    <p>The test passes if there is a green box with a blue border.</p>
+    <div style="width: 150px; height: 100px; border: solid blue 50px; background-color: green; clip-path: url(#clip);"></div>
+
+    <svg>
+        <clipPath id="clip">
+            <rect x="0" y="0" width="250px" height="200" />
+        </clipPath>
+    </svg>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-element-userSpaceOnUse-003.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-element-userSpaceOnUse-003.html
new file mode 100644
index 0000000..5628938
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-element-userSpaceOnUse-003.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip-path property and external clipPath reference with userSpaceOnUse with percentage - 1</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement">
+    <link rel="match" href="reference/clip-path-ref-right-green-ref.html">
+    <meta name="assert" content="The clip-path property takes a reference to a clipPath element for clipping. Percentage values
+        are relative to the viewport for userSpaceOnUse on clipPathUnits.
+        On pass the left half of the site is white and the right half of the site is blue.">
+
+    <style>
+        html, body {
+            width: 100%;
+            height: 100%;
+            margin: 0;
+        }
+    </style>
+</head>
+<body>
+    <div style="width: 100%; height: 100%; background-color: green; clip-path: url(#clip); position: absolute;"></div>
+    <p style="position: absolute;">The test passes if the left half of the site is white and the right half of the site is blue.</p>
+
+    <svg>
+        <clipPath id="clip">
+            <rect x="50%" y="0" width="50%" height="100%" />
+        </clipPath>
+    </svg>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-element-userSpaceOnUse-004.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-element-userSpaceOnUse-004.html
new file mode 100644
index 0000000..af65df6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-element-userSpaceOnUse-004.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip-path property and external clipPath reference with userSpaceOnUse with percentage - 2</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement">
+    <link rel="match" href="reference/clip-path-ref-bottom-green-ref.html">
+    <meta name="assert" content="The clip-path property takes a reference to a clipPath element for clipping. Percentage values
+        are relative to the viewport for userSpaceOnUse on clipPathUnits.
+        On pass the top half of the site is white and the bottom half of the site is green.">
+
+    <style>
+        html, body {
+            width: 100%;
+            height: 100%;
+            margin: 0;
+        }
+    </style>
+</head>
+<body>
+    <div style="width: 100%; height: 100%; background-color: green; clip-path: url(#clip); position: absolute;"></div>
+    <p style="position: absolute;">The test passes if the top half of the site is white and the bottom half of the site is green.</p>
+
+    <svg>
+        <clipPath id="clip">
+            <rect x="0" y="50%" width="100%" height="50%" />
+        </clipPath>
+    </svg>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-001.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-001.html
new file mode 100644
index 0000000..197f422c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-001.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Test clip-path property and ellipse function with absolute values</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+	<link rel="match" href="reference/clip-path-ellipse-ref.html">
+	<meta name="assert" content="The clip-path property takes the basic shape
+	'ellipse' for clipping. Test absolute values for radii and position
+	arguments. On pass you should see a green ellipse.">
+</head>
+<body>
+	<p>The test passes if there is a full green ellipse.</p>
+	<div style="width: 150px; height: 100px; border: solid red 50px; background-color: green; clip-path: ellipse(75px 50px at 125px 100px);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-002.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-002.html
new file mode 100644
index 0000000..d6310ded
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-002.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Test clip-path property and ellipse function with percentage values</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+	<link rel="match" href="reference/clip-path-ellipse-ref.html">
+	<meta name="assert" content="The clip-path property takes the basic shape
+	'ellipse' for clipping. Test percentage values for radii and position
+	arguments. Percentage values are relative to a reference box. If no
+	reference box was specified, percentage values are relative to border-box.
+	On pass you should see a green ellipse.">
+</head>
+<body>
+	<p>The test passes if there is a green ellipse.</p>
+	<div style="width: 150px; height: 100px; border: solid red 50px; background-color: green; clip-path: ellipse(30% 25% at 50% 50%);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-003.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-003.html
new file mode 100644
index 0000000..e1c32bf
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-003.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Test clip-path property and ellipse function with percentage values on square</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+	<link rel="match" href="reference/clip-path-circle-ref.html">
+	<meta name="assert" content="The clip-path property takes the basic shape
+	'ellipse' for clipping. Test percentage values for radii and position
+	arguments. Percentage values are relative to a reference box. If no
+	reference box was specified, percentage values are relative to border-box.
+	On pass there should be a full green circle.">
+</head>
+<body>
+	<p>The test passes if there is a green circle.</p>
+	<div style="width: 200px; height: 200px; background-color: green; clip-path: ellipse(50% 50% at 50% 50%);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-004.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-004.html
new file mode 100644
index 0000000..3a56b53
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-004.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Test clip-path property and ellipse function with different absolute values on square</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+	<link rel="match" href="reference/clip-path-ellipse-ref.html">
+	<meta name="assert" content="The clip-path property takes the basic shape
+	'ellipse' for clipping. Test absolute values for arguments. On pass you
+	should see a green ellipse.">
+</head>
+<body>
+	<p>The test passes if there is a green ellipse.</p>
+	<div style="width: 200px; height: 200px; background-color: green; clip-path: ellipse(75px 50px at 125px 100px);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-005.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-005.html
new file mode 100644
index 0000000..fd364cc
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-005.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Test clip-path property and ellipse function with different percentage values on square</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+	<link rel="match" href="reference/clip-path-ellipse-ref.html">
+	<meta name="assert" content="The clip-path property takes the basic shape
+	'ellipse' for clipping. Test percentage values for radii and position
+	arguments. Percentage values are relative to a reference box. If no
+	reference box was specified, percentage values are relative to border-box.
+	Different values for getting an ellipse from a square. On pass you should
+	see a green ellipse.">
+</head>
+<body>
+	<p>The test passes if there is a green ellipse.</p>
+	<div style="width: 200px; height: 200px; background-color: green; clip-path: ellipse(37.5% 25% at 62.5% 50%);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-006.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-006.html
new file mode 100644
index 0000000..e261bac
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-006.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Test clip-path property and ellipse function with absolute values</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+	<link rel="match" href="reference/clip-path-circle-ref.html">
+	<meta name="assert" content="The clip-path property takes the basic shape
+	'ellipse' for clipping. Test absolute values for radii and position
+	arguments. On pass you should see a green circle.">
+</head>
+<body>
+	<p>The test passes if there is a green circle.</p>
+	<div style="width: 150px; height: 100px; border: solid red 50px; background-color: green; clip-path: ellipse();"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-007.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-007.html
new file mode 100644
index 0000000..16cfce14
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-007.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Test clip-path property and ellipse function with no arguments</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+	<link rel="match" href="reference/clip-path-ellipse-ref.html">
+	<meta name="assert" content="The clip-path property takes the basic shape
+	'ellipse' for clipping. If no further arguments were specified, the radii
+	are 'closest-side' each. The position is initialised to the center of the
+	element. On pass there is a full green ellipse.">
+</head>
+<body>
+	<p>The test passes if there is a full green ellipse.</p>
+	<div style="width: 150px; height: 100px; border: solid red 50px; background-color: green; clip-path: ellipse(farthest-side closest-side);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-008.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-008.html
new file mode 100644
index 0000000..f56c645
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-ellipse-008.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Test clip-path property and ellipse function with 50% 50%</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+	<link rel="match" href="reference/clip-path-ellipse-ref.html">
+	<meta name="assert" content="The clip-path property takes the basic shape
+	'ellipse' for clipping. Test percentage values for radii and position
+	arguments. Percentage values are relative to a reference box. If no
+	reference box was specified, percentage values are relative to border-box.
+	Both radii are specified with percentage values. The position is
+	initialised to the center of the element. On pass there is a full green
+	ellipse.">
+</head>
+<body>
+	<p>The test passes if there is a full green ellipse.</p>
+	<div style="width: 150px; height: 100px; border: solid red 50px; background-color: green; clip-path: ellipse(50% 50%);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-001.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-001.html
new file mode 100644
index 0000000..565d747
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-001.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Test clip-path property and polygon function with absolute values</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+	<link rel="match" href="reference/clip-path-rectangle-ref.html">
+	<meta name="assert" content="The clip-path property takes the basic shape
+	'polygon' for clipping. Test absolute value arguments. On pass you should
+	see a green square and no red.">
+</head>
+<body>
+	<p>The test passes if there is a green square and no red.</p>
+	<div style="width: 150px; height: 100px; border: solid red 50px; background-color: green; clip-path: polygon(50px 50px, 200px 50px, 200px 150px, 50px 150px)"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-002.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-002.html
new file mode 100644
index 0000000..5d1004870
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-002.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Test clip-path property and polygon function with percentage values</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+	<link rel="match" href="reference/clip-path-rectangle-ref.html">
+	<meta name="assert" content="The clip-path property takes the basic shape
+	'polygon' for clipping. Test percentage values for arguments. Percentage
+	values are relative to specified reference box. If no reference box was
+	specified, percentage values are relative to border-box. A number of
+	percentage values are specified as coordinates. On pass you should see a
+	green square and no red.">
+</head>
+<body>
+	<p>The test passes if there is a green square and no red.</p>
+	<div style="width: 150px; height: 100px; border: solid red 50px; background-color: green; clip-path: polygon(20% 25%, 80% 25%, 80% 75%, 20% 75%)"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-003.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-003.html
new file mode 100644
index 0000000..b50f9c4
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-003.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Test clip-path property and polygon function with absolute and percentage values</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+	<link rel="match" href="reference/clip-path-rectangle-ref.html">
+	<meta name="assert" content="The clip-path property takes the basic shape
+	'polygon' for clipping. Test absolute and percentage value arguments. On
+	pass you should see a green square and no red.">
+</head>
+<body>
+	<p>The test passes if there is a green square and no red.</p>
+	<div style="width: 150px; height: 100px; border: solid red 50px; background-color: green; position: absolute; clip-path: polygon(20% 50px, 200px 25%, 200px 150px, 20% 75%)"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-004.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-004.html
new file mode 100644
index 0000000..e057d85
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-004.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Test clip-path property and polygon function with fill rule evenodd</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+	<link rel="match" href="reference/clip-path-rectangle-border-ref.html">
+	<meta name="assert" content="The clip-path property takes the basic shape 'polygon' for clipping.
+		The point list for the polygon creates a 'whole' with the dimension of the background. With
+		the fill rule 'evenodd', this whole is clipped out. The clipping makes the green background
+		of the parent div box visible.
+		On pass you should see a green box with a blue border.">
+</head>
+<body>
+	<p>The test passes if there is a green box with a blue border.</p>
+	<div style="background-color: green; width: 250px;">
+		<div style="width: 150px; height: 100px; border: solid blue 50px; background-color: red; clip-path: polygon(evenodd, 0 0, 250px 0, 250px 200px, 0 200px, 0 50px, 200px 50px, 200px 150px, 50px 150px, 50px 50px, 0 50px)"></div>
+	</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-005.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-005.html
new file mode 100644
index 0000000..497e7f3
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-005.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Test clip-path property and polygon function with fill rule nonzero</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+	<link rel="match" href="reference/clip-path-rectangle-border-ref.html">
+	<meta name="assert" content="The clip-path property takes the basic shape
+	'polygon' for clipping. The point list for the polygon creates a 'hole'
+	with the dimension of the background of the clipped element. With the fill
+	rule 'nonzero', this hole is clipped out. The clipping makes the green
+	background of the parent div box visible. On pass you should see a green
+	square with a blue border.">
+</head>
+<body>
+	<p>The test passes if there is a green square with a blue border.</p>
+	<div style="background-color: green; width: 250px;">
+		<div style="width: 150px; height: 100px; border: solid blue 50px; background-color: red; clip-path: polygon(nonzero, 0 0, 250px 0, 250px 200px, 0 200px, 0 50px, 50px 50px, 50px 150px, 200px 150px, 200px 50px, 0 50px)"></div>
+	</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-006.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-006.html
new file mode 100644
index 0000000..1b33eaaf
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-006.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Test clip-path and polygon with padding-box</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+	<link rel="match" href="reference/clip-path-square-001-ref.html">
+	<meta name="assert" content="The clip-path property allows specifying
+	basic shapes and reference boxes. This test checks the usage of the correct
+	reference box 'padding-box' for the polygon() function by mixing percentage
+	and absolute values as coordinates. On sucess you should see a green square
+	and no red.">
+</head>
+<style>
+div {
+	width: 50px;
+	height: 50px;
+	background-color: green;
+	padding: 25px;
+	margin: 25px;
+	border: red solid 50px;
+}
+</style>
+<body>
+	<p>The test passes if there is a green square and no red.</p>
+	<div style="clip-path: polygon(0% 0%, 100% 0%, 100px 100%, 0 100px) padding-box"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-007.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-007.html
new file mode 100644
index 0000000..9b590979
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-007.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Test clip-path and polygon with border-box</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+	<link rel="match" href="reference/clip-path-stripes-001-ref.html">
+	<meta name="assert" content="The clip-path property allows specifying
+	basic shapes and reference boxes. This test checks the usage of the correct
+	reference box 'border-box' for the polygon() function by mixing percentage
+	and absolute values as coordinates. On sucess you should see a green
+	vertical stripe next to a lime green vertical stripe. Both should be of equal
+	size.">
+<style>
+div {
+	width: 50px;
+	height: 50px;
+	background-color: green;
+	padding: 25px;
+	margin: 25px;
+	border: red solid 50px;
+	border-left: lime solid 50px;
+}
+</style>
+<body>
+	<p>The test passes if you see a green vertical stripe next to a lime green vertical stripe, both stripes should be of equal size and there should be no red.</p>
+	<div style="clip-path: polygon(0% 25%, 50% 50px, 100px 75%, 0 150px) border-box"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-008.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-008.html
new file mode 100644
index 0000000..454aba6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-008.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Test clip-path and polygon with margin-box</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+	<link rel="match" href="reference/clip-path-stripes-002-ref.html">
+	<meta name="assert" content="The clip-path property allows specifying
+	basic shapes and reference boxes. This test checks the usage of the correct
+	reference box 'margin-box' for the polygon() function by mixing percentage
+	and absolute values as coordinates. On sucess you should see a green
+	vertical stripe next to a lime green vertical stripe. Both should be of equal
+	size.">
+</head>
+<style>
+div {
+	width: 50px;
+	height: 50px;
+	background-color: green;
+	padding: 25px;
+	margin: 25px;
+	border: red solid 25px;
+	border-left: lime solid 25px;
+}
+</style>
+<body>
+	<p>The test passes if you see a green vertical stripe next to a lime green vertical stripe, both stripes should be of equal size and there should be no red.</p>
+	<div style="clip-path: polygon(12.5% 25%, 37.5% 50px, 75px 50%, 25px 100px) margin-box"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-009.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-009.html
new file mode 100644
index 0000000..7d3ab5bb
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-009.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Test clip-path and polygon with content-box</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+	<link rel="match" href="reference/clip-path-square-002-ref.html">
+	<meta name="assert" content="The clip-path property allows specifying
+	basic shapes and reference boxes. This test checks the usage of the correct
+	reference box 'content-box' for the polygon() function by mixing percentage
+	and absolute values as coordinates. On sucess you should see a green square
+	and no red.">
+</head>
+<style>
+div {
+	width: 50px;
+	height: 50px;
+	background-color: green;
+	padding: 25px;
+	margin: 25px;
+	border: red solid 25px;
+}
+</style>
+<body>
+	<p>The test passes if there is a green square and no red.</p>
+	<div style="clip-path: polygon(0% 0px, 50px 0%, 100% 50px, 0px 100%) content-box"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-010.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-010.html
new file mode 100644
index 0000000..c60aba2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-010.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Test clip-path and polygon with fill-box</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+	<link rel="match" href="reference/clip-path-stripes-001-ref.html">
+	<meta name="assert" content="The clip-path property allows specifying
+	basic shapes and reference boxes. This test checks the usage of the correct
+	reference box. 'fill-box' was specified but is not supported for HTML
+	elements. The used value should be 'border-box' for the polygon() function
+	instead. By mixing percentage and absolute values as coordinates we check
+	the correct used reference box. On sucess you should see a green
+	vertical stripe next to a lime green vertical stripe. Both should be of equal
+	size.">
+<style>
+div {
+	width: 50px;
+	height: 50px;
+	background-color: green;
+	padding: 25px;
+	margin: 25px;
+	border: red solid 50px;
+	border-left: lime solid 50px;
+}
+</style>
+<body>
+	<p>The test passes if you see a green vertical stripe next to a lime green vertical stripe, both stripes should be of equal size and there should be no red.</p>
+	<div style="clip-path: polygon(0% 25%, 50% 50px, 100px 75%, 0 150px) fill-box"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-011.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-011.html
new file mode 100644
index 0000000..7acccd1
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-011.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Test clip-path and polygon with stroke-box</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+	<link rel="match" href="reference/clip-path-stripes-001-ref.html">
+	<meta name="assert" content="The clip-path property allows specifying
+	basic shapes and reference boxes. This test checks the usage of the correct
+	reference box. 'stroke-box' was specified but is not supported for HTML
+	elements. The used value should be 'border-box' for the polygon() function
+	instead. By mixing percentage and absolute values as coordinates we check
+	the correct used reference box. On sucess you should see a green
+	vertical stripe next to a lime green vertical stripe. Both should be of equal
+	size.">
+<style>
+div {
+	width: 50px;
+	height: 50px;
+	background-color: green;
+	padding: 25px;
+	margin: 25px;
+	border: red solid 50px;
+	border-left: lime solid 50px;
+}
+</style>
+<body>
+	<p>The test passes if you see a green vertical stripe next to a lime green vertical stripe, both stripes should be of equal size and there should be no red.</p>
+	<div style="clip-path: polygon(0% 25%, 50% 50px, 100px 75%, 0 150px) stroke-box"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-012.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-012.html
new file mode 100644
index 0000000..9a24abf
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-012.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Test clip-path and polygon with view-box</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+	<link rel="match" href="reference/clip-path-stripes-001-ref.html">
+	<meta name="assert" content="The clip-path property allows specifying
+	basic shapes and reference boxes. This test checks the usage of the correct
+	reference box. 'view-box' was specified but is not supported for HTML
+	elements. The used value should be 'border-box' for the polygon() function
+	instead. By mixing percentage and absolute values as coordinates we check
+	the correct used reference box. On sucess you should see a green
+	vertical stripe next to a lime green vertical stripe. Both should be of equal
+	size.">
+<style>
+div {
+	width: 50px;
+	height: 50px;
+	background-color: green;
+	padding: 25px;
+	margin: 25px;
+	border: red solid 50px;
+	border-left: lime solid 50px;
+}
+</style>
+<body>
+	<p>The test passes if you see a green vertical stripe next to a lime green vertical stripe, both stripes should be of equal size and there should be no red.</p>
+	<div style="clip-path: polygon(0% 25%, 50% 50px, 100px 75%, 0 150px) view-box"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-013.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-013.html
new file mode 100644
index 0000000..8d2c049
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/clip-path-polygon-013.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Test clip-path and polygon different units</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+	<link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+	<link rel="match" href="reference/clip-path-stripes-003-ref.html">
+	<meta name="assert" content="Test the support of different units for
+	polygon coordinates. The test passes if you see a multiple green and blue
+	stripe pairs. For each pair, the blue and green stripe must be of same
+	length.">
+<style>
+div {
+	width: 100%;
+	height: 20px;
+	background-color: green;
+	padding: 0;
+	margin: 0;
+}
+div:nth-child(odd) {
+	margin-bottom: 5px;
+	background-color: blue;
+}
+</style>
+<body>
+	<p>The test passes if you see a multiple green and blue stripe pairs. For each pair, the blue and green stripe must be of same length.</p>
+	<div style="clip-path: polygon(0 0, 50% 0, 50% 20px, 0 20px)"></div>
+	<div style="width: 50%"></div>
+
+	<div style="clip-path: polygon(0 0, 20em 0, 20em 20px, 0 20px)"></div>
+	<div style="width: 20em"></div>
+
+	<div style="clip-path: polygon(0 0, 50vw 0, 50vw 20px, 0 20px)"></div>
+	<div style="width: 50vw"></div>
+
+	<div style="clip-path: polygon(0 0, 40vh 0, 40vh 20px, 0 20px)"></div>
+	<div style="width: 40vh"></div>
+
+	<div style="clip-path: polygon(0 0, calc(30% + 15px) 0, calc(30% + 15px) 20px, 0 20px)"></div>
+	<div style="width: calc(30% + 15px)"></div>
+
+	<div style="clip-path: polygon(0 0, 30ex 0, 30ex 20px, 0 20px)"></div>
+	<div style="width: 30ex"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-circle-2-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-circle-2-ref.html
new file mode 100644
index 0000000..7794d32
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-circle-2-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Reftest Reference</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <style type="text/css">
+    body, div {
+        padding: 0;
+        margin: 0;
+    }
+    </style>
+</head>
+<body>
+    <p>The test passes if there is a full green circle.</p>
+    <div style="width: 200px; height: 200px; border-radius: 100px; background-color: green; position: absolute; left: 100px;"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-circle-3-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-circle-3-ref.html
new file mode 100644
index 0000000..4cffe883
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-circle-3-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Reftest Reference</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <style type="text/css">
+    body, div {
+        padding: 0;
+        margin: 0;
+    }
+    </style>
+</head>
+<body>
+    <p>The test passes if there is a full green circle.</p>
+    <div style="width: 200px; height: 200px; border-radius: 100px; background-color: green; position: relative; top: 100px;"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-circle-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-circle-ref.html
new file mode 100644
index 0000000..c427e69
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-circle-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Reftest Reference</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+</head>
+<body>
+    <p>The test passes if there is a full green circle.</p>
+    <div style="width: 200px; height: 200px; border-radius: 100px; background-color: green;"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-ellipse-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-ellipse-ref.html
new file mode 100644
index 0000000..5adc91a5
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-ellipse-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Reftest Reference</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+</head>
+<body>
+    <p>The test passes if there is a full green ellipse.</p>
+    <div style="margin-top: 50px; margin-left: 50px; position: absolute; width: 150px; height: 100px; border-top-right-radius: 75px 50px; border-bottom-right-radius: 75px 50px; border-top-left-radius: 75px 50px; border-bottom-left-radius: 75px 50px; background-color: green;"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-rectangle-border-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-rectangle-border-ref.html
new file mode 100644
index 0000000..9a61c76
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-rectangle-border-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Reftest Reference</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+</head>
+<body>
+    <p>The test passes if there is a green box with a blue border.</p>
+    <div style="width: 150px; height: 100px; border: solid blue 50px; background-color: green;"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-rectangle-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-rectangle-ref.html
new file mode 100644
index 0000000..1d3810a7
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-rectangle-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Reftest Reference</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+</head>
+<body>
+    <p>The test passes if there is a green box.</p>
+    <div style="margin-top: 50px; margin-left: 50px; position: absolute; width: 150px; height: 100px; background-color: green;"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-ref-bottom-green-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-ref-bottom-green-ref.html
new file mode 100644
index 0000000..ad6abc4f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-ref-bottom-green-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<head>
+    <title>CSS Reftest Reference</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <style>
+        html, body {
+            width: 100%;
+            height: 100%;
+            margin: 0;
+        }
+    </style>
+</head>
+<body>
+    <div style="width: 100%; height: 50%; margin-top: 50%; background-color: green; position: absolute;"></div>
+    <p style="position: absolute;">The test passes if the top half of the site is white and the bottom half of the site is green.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-ref-right-green-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-ref-right-green-ref.html
new file mode 100644
index 0000000..a19be598
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-ref-right-green-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<head>
+    <title>CSS Reftest Reference</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <style>
+        html, body {
+            width: 100%;
+            height: 100%;
+            margin: 0;
+        }
+    </style>
+</head>
+<body>
+    <div style="width: 50%; height: 100%; margin-left: 50%; background-color: green; position: absolute;"></div>
+    <p style="position: absolute;">The test passes if the left half of the site is white and the right half of the site is green.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-square-001-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-square-001-ref.html
new file mode 100644
index 0000000..6bc5a167
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-square-001-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Reftest reference</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+</head>
+<body>
+	<p>The test passes if there is a green square and no red.</p>
+	<div style="width: 100px; height: 100px; background-color: green; margin: 75px;"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-square-002-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-square-002-ref.html
new file mode 100644
index 0000000..5230b0f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-square-002-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Reftest reference</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+</head>
+<body>
+	<p>The test passes if there is a green square and no red.</p>
+	<div style="width: 50px; height: 50px; background-color: green; margin: 75px;"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-stripes-001-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-stripes-001-ref.html
new file mode 100644
index 0000000..75d08dc
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-stripes-001-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Reftest reference</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+</head>
+<body>
+	<p>The test passes if you see a green vertical stripe next to a lime green vertical stripe, both stripes should be of equal size and there should be no red.</p>
+	<div style="width: 100px; height: 100px; margin: 75px 0 0 25px; background-color: green;">
+		<div style="width: 50px; height: 100px; background-color: lime;"></div>
+	</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-stripes-002-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-stripes-002-ref.html
new file mode 100644
index 0000000..8dd1c926
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-stripes-002-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Reftest reference</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+</head>
+<body>
+	<p>The test passes if you see a green vertical stripe next to a lime green vertical stripe, both stripes should be of equal size and there should be no red.</p>
+	<div style="width: 50px; height: 50px; margin: 50px 0 0 25px; background-color: green;">
+		<div style="width: 25px; height: 50px; background-color: lime;"></div>
+	</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-stripes-003-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-stripes-003-ref.html
new file mode 100644
index 0000000..e31282c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/reference/clip-path-stripes-003-ref.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<title>CSS Masking: Reftest reference</title>
+	<link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+<style>
+div {
+	height: 20px;
+	background-color: green;
+	padding: 0;
+	margin: 0;
+}
+div:nth-child(odd) {
+	margin-bottom: 5px;
+	background-color: blue;
+}
+</style>
+<body>
+	<p>The test passes if you see a multiple green and blue stripe pairs. For each pair, the blue and green stripe must be of same length.</p>
+	<div style="width: 50%"></div>
+	<div style="width: 50%"></div>
+
+	<div style="width: 20em"></div>
+	<div style="width: 20em"></div>
+
+	<div style="width: 50vw"></div>
+	<div style="width: 50vw"></div>
+
+	<div style="width: 40vh"></div>
+	<div style="width: 40vh"></div>
+
+	<div style="width: calc(30% + 15px)"></div>
+	<div style="width: calc(30% + 15px)"></div>
+
+	<div style="width: 30ex"></div>
+	<div style="width: 30ex"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/svg-clipPath.svg b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/svg-clipPath.svg
new file mode 100644
index 0000000..d31a1df
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-path/svg-clipPath.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<defs>
+	<clipPath id="clip-path-ref-userSpaceOnUse-001">
+		<rect x="50" y="50" width="150px" height="100" />
+	</clipPath>
+</defs>
+</svg>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-rule/clip-rule-001.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-rule/clip-rule-001.html
new file mode 100644
index 0000000..a5a0961
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-rule/clip-rule-001.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip-rule property on polygon clip rule evenodd</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement">
+    <link rel="match" href="reference/clip-rule-rectangle-border-ref.html">
+    <meta name="assert" content="The clipPath element takes the basic shape 'polygon' for clipping.
+        The point list for the polygon creates a 'whole' with the dimension of the background. With
+        the clip rule 'evenodd', this whole is clipped out. The clipping makes the green background
+        of the parent div box visible.
+        On pass you should see a green box with a blue border.">
+</head>
+<body>
+    <p>The test passes if there is a green box with a blue border.</p>
+    <div style="background-color: green; width: 250px;">
+       <div style="width: 150px; height: 100px; border: solid blue 50px; background-color: red; clip-path: url(#clip);"></div>
+    </div>
+
+    <svg>
+        <clipPath id="clip">
+            <polygon points="0 0, 250 0, 250 200, 0 200, 0 50, 200 50, 200 150, 50 150, 50 50, 0 50" clip-rule="evenodd" />
+        </clipPath>
+    </svg>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-rule/clip-rule-002.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-rule/clip-rule-002.html
new file mode 100644
index 0000000..5fee625
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-rule/clip-rule-002.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip-rule property on polygon clip rule nonzero</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#propdef-clip-path">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#ClipPathElement">
+    <link rel="match" href="reference/clip-rule-rectangle-border-ref.html">
+    <meta name="assert" content="The clipPath element takes the basic shape 'polygon' for clipping.
+        The point list for the polygon creates a 'whole' with the dimension of the background. With
+        the clip rule 'nonzero', this whole is clipped out. The clipping makes the green background
+        of the parent div box visible.
+        On pass you should see a green box with a blue border.">
+</head>
+<body>
+    <p>The test passes if there is a green box with a blue border.</p>
+    <div style="background-color: green; width: 250px;">
+       <div style="width: 150px; height: 100px; border: solid blue 50px; background-color: red; clip-path: url(#clip);"></div>
+    </div>
+
+    <svg>
+        <clipPath id="clip">
+            <polygon points="0 0, 250 0, 250 200, 0 200, 0 50, 50 50, 50 150, 200 150, 200 50, 0 50" clip-rule="nonzero" />
+        </clipPath>
+    </svg>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-rule/reference/clip-rule-rectangle-border-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-rule/reference/clip-rule-rectangle-border-ref.html
new file mode 100644
index 0000000..9a61c76
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip-rule/reference/clip-rule-rectangle-border-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Reftest Reference</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+</head>
+<body>
+    <p>The test passes if there is a green box with a blue border.</p>
+    <div style="width: 150px; height: 100px; border: solid blue 50px; background-color: green;"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-absolute-positioned-001.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-absolute-positioned-001.html
new file mode 100644
index 0000000..c5b0d900
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-absolute-positioned-001.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip property and rect function on div with position: absolute</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clip-property">
+    <link rel="match" href="reference/clip-absolute-positioned-ref.html">
+    <meta name="assert" content="The clip property should clip elements whose
+    layout are governed by the CSS box model and that are abolutely positioned
+    with 'position: absolute;'. On pass you should see a green square and no
+    red.">
+</head>
+<body>
+    <p>The test passes if there is a green square and no red.</p>
+    <div style="width: 100px; height: 100px; border: solid red 50px; position: absolute; background-color: green; clip: rect(50px, 150px, 150px, 50px);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-absolute-positioned-002.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-absolute-positioned-002.html
new file mode 100644
index 0000000..53b2517a
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-absolute-positioned-002.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip property and rect function on div with position: fixed</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clip-property">
+    <link rel="match" href="reference/clip-absolute-positioned-ref.html">
+    <meta name="assert" content="The clip property should clip elements whose
+    layout are governed by the CSS box model and that are abolutely positioned
+    with 'position: fixed;'. On pass you should see a green square and no
+    red.">
+</head>
+<body>
+    <p>The test passes if there is a green square and no red.</p>
+    <div style="width: 100px; height: 100px; border: solid red 50px; position: fixed; background-color: green; clip: rect(50px, 150px, 150px, 50px);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-negative-values-001.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-negative-values-001.html
new file mode 100644
index 0000000..b295c89b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-negative-values-001.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip property does not clip on with negative values - 1</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clip-property">
+    <link rel="match" href="reference/clip-full-ref.html">
+    <meta name="assert" content="Negative values are permitted on rect function
+    for clip. Test that whole element is clipped if bottom edge is before top
+    edge. On pass you should see a green square and no red.">
+</head>
+<body>
+    <p>The test passes if there is a green square and no red.</p>
+    <div style="background-color: green; width: 200px; height: 200px;">
+        <div style="width: 200px; height: 200px; background-color: red; position: absolute; clip: rect(0, -50px, 200px, 50px);"></div>
+    </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-negative-values-002.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-negative-values-002.html
new file mode 100644
index 0000000..dd92e5b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-negative-values-002.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip property does not clip on with negative values - 2</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clip-property">
+    <link rel="match" href="reference/clip-full-ref.html">
+    <meta name="assert" content="Negative values are permitted on rect function
+    for clip. Test that whole element is clipped if right edge is before left
+    edge. On pass you should see a green square and no red.">
+</head>
+<body>
+    <p>The test passes if there is a green square and no red.</p>
+    <div style="background-color: green; width: 200px; height: 200px;">
+        <div style="width: 200px; height: 200px; background-color: red; position: absolute; clip: rect(50px, 200px, -50px, 0);"></div>
+    </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-negative-values-003.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-negative-values-003.html
new file mode 100644
index 0000000..51181353
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-negative-values-003.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip property does not clip on with negative values - 3</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clip-property">
+    <link rel="match" href="reference/clip-vertical-stripe-ref.html">
+    <meta name="assert" content="Negative values are permited on rect function
+    for clip. Test that left edge can be negative. On pass you should see a
+    vertical blue stripe.">
+</head>
+<body>
+    <p>The test passes if there is only a vertical blue stripe.</p>
+    <div style="width: 100px; height: 100px; border: solid blue 50px; background-color: green; position: absolute; clip: rect(0, 50px, 200px, -50px);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-negative-values-004.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-negative-values-004.html
new file mode 100644
index 0000000..1fb5add
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-negative-values-004.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip property does not clip on with negative values - 4</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clip-property">
+    <link rel="match" href="reference/clip-horizontal-stripe-ref.html">
+    <meta name="assert" content="Negative values are permited on rect function
+    for clip. Test that top edge can be negative. On pass you should see a
+    horizontal blue stripe.">
+</head>
+<body>
+    <p>The test passes if there is only a horizontal blue stripe.</p>
+    <div style="width: 100px; height: 100px; border: solid blue 50px; background-color: green; position: absolute; clip: rect(-50px, 200px, 50px, 0);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-no-clipping-001.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-no-clipping-001.html
new file mode 100644
index 0000000..3a63c2ca
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-no-clipping-001.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip property does not clip on 'auto'</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clip-property">
+    <link rel="match" href="reference/clip-no-clipping-ref.html">
+    <meta name="assert" content="The clip property should on 'auto'. On pass
+    you should see a green box with a blue border.">
+</head>
+<body>
+    <p>The test passes if there is a green square with a blue border.</p>
+    <div style="width: 100px; height: 100px; border: solid blue 50px; background-color: green; position: absolute; clip: auto"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-no-clipping-002.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-no-clipping-002.html
new file mode 100644
index 0000000..a09e6a0
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-no-clipping-002.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip property does not clip overflowing content on 'auto'.</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clip-property">
+    <link rel="match" href="reference/clip-no-clipping-ref.html">
+    <meta name="assert" content="The clip property should not clip overflowing
+    content of elements whose layout are governed by the CSS box model, the
+    position is absolute and the clip value is 'auto'. On pass you should see a
+    a green square with a blue border.">
+</head>
+<body>
+    <p>The test passes if there is a green square with a blue border.</p>
+    <div style="width: 100px; height: 100px; position: absolute; clip: auto;">
+        <div style="width: 100px; height: 100px; border: solid blue 50px; background-color: green;"></div>
+    </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-not-absolute-positioned-001.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-not-absolute-positioned-001.html
new file mode 100644
index 0000000..613f6af
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-not-absolute-positioned-001.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip property and rect function on not absolutely positioned div - 1</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clip-property">
+    <link rel="match" href="reference/clip-no-clipping-ref.html">
+    <meta name="assert" content="The clip property should be ignored on
+    elements whose layout are governed by the CSS box model and are not
+    abolutely positioned. On pass you should see a green square with a blue
+    border.">
+</head>
+<body>
+    <p>The test passes if there is a green square with a blue border.</p>
+    <div style="width: 100px; height: 100px; border: solid blue 50px; background-color: green; clip: rect(50px, 150px, 150px, 50px);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-not-absolute-positioned-002.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-not-absolute-positioned-002.html
new file mode 100644
index 0000000..4d07132
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-not-absolute-positioned-002.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip property and rect function on not absolutely positioned div - 2</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clip-property">
+    <link rel="match" href="reference/clip-no-clipping-ref.html">
+    <meta name="assert" content="The clip property should be ignored on
+    elements whose layout are governed by the CSS box model and are not
+    abolutely positioned. Creating a stacking context with z-index does not
+    influence clipping behavior. On pass you should see a green box square with
+    a blue border.">
+</head>
+<body>
+    <p>The test passes if there is a green square with a blue border.</p>
+    <div style="width: 100px; height: 100px; border: solid blue 50px; background-color: green; z-index: 10; clip: rect(50px, 150px, 150px, 50px);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-not-absolute-positioned-003.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-not-absolute-positioned-003.html
new file mode 100644
index 0000000..ee3765db
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-not-absolute-positioned-003.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip property and rect function on not absolutely positioned div - 3</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clip-property">
+    <link rel="match" href="reference/clip-no-clipping-ref.html">
+    <meta name="assert" content="The clip property should be ignored on
+    elements whose layout are governed by the CSS box model and are not
+    abolutely positioned. Creating a 3d rednering context does not influence
+    clipping behavior. On pass you should see a green square with a blue
+    border.">
+</head>
+<body style="perspective: 400px;">
+    <p>The test passes if there is a green square with a blue border.</p>
+    <div style="width: 100px; height: 100px; border: solid blue 50px; background-color: green; transform-style: preserve-3d; transform: translate3d(0, 0, 0); clip: rect(50px, 150px, 150px, 50px);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-not-absolute-positioned-004.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-not-absolute-positioned-004.html
new file mode 100644
index 0000000..56825d1
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-not-absolute-positioned-004.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip property and rect function on not absolutely positioned div - 4</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clip-property">
+    <link rel="match" href="reference/clip-no-clipping-ref.html">
+    <meta name="assert" content="The clip property should be ignored on
+    elements whose layout are governed by the CSS box model and are not
+    abolutely positioned. position: relative does not influence clipping
+    behavior. On pass you should see a green square with a blue border.">
+</head>
+<body>
+    <p>The test passes if there is a green square with a blue border.</p>
+    <div style="width: 100px; height: 100px; border: solid blue 50px; background-color: green; position: relative; clip: rect(50px, 150px, 150px, 50px);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-auto-001.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-auto-001.html
new file mode 100644
index 0000000..6a1a0640
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-auto-001.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip property with rect function and auto values clip to border box - 1</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clip-property">
+    <link rel="match" href="reference/clip-overflow-hidden-ref.html">
+    <meta name="assert" content="A value of 'auto' in the rect function is
+    equal to the certain edge of the border box. The content should be clipped
+    to the border box. On pass you see a blue square and a smaller green square
+    in the bottom right corner of the blue square.">
+</head>
+<body>
+    <p>The test passes if there is a blue square and a smaller green square in the bottom right corner of the blue square.</p>
+    <div style="position: absolute; clip: rect(auto, auto, auto, auto); width: 100px; height: 100px;">
+        <div style="width: 100px; height: 100px; border: solid blue 50px; background-color: green;"></div>
+    </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-auto-002.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-auto-002.html
new file mode 100644
index 0000000..0ee4518
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-auto-002.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip property with rect function and auto values clip to border box - 2</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clip-property">
+    <link rel="match" href="reference/clip-no-clipping-ref.html">
+    <meta name="assert" content="A value of 'auto' in the rect function is
+    equal to the certain edge of the border box. The box shadow should be
+    clipped, since it is painted outside the border box. On pass you should see
+    a green square with a blue border.">
+</head>
+<body>
+    <p>The test passes if there is a green square with a blue border.</p>
+    <div style="width: 100px; height: 100px; border: solid blue 50px; background-color: green; position: absolute; clip: rect(auto, auto, auto, auto); box-shadow: 0 0 10px red;"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-auto-003.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-auto-003.html
new file mode 100644
index 0000000..5f12ae4
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-auto-003.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip property with rect function and auto value for top value</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clip-property">
+    <link rel="match" href="reference/clip-rect-top-ref.html">
+    <meta name="assert" content="A value of 'auto' for 'top' in the rect
+    function is equal to the top edge of the border box. The box shadow should
+    be clipped, since it is painted outside the border box. On pass you should see a horizontal green stripe under a horizontal blue stripe.">
+</head>
+<body>
+    <p>The test passes if there is a horizontal green stripe under a horizontal blue stripe.</p>
+    <div style="width: 100px; height: 100px; border: solid blue 50px; background-color: green; position: absolute; clip: rect(auto, 150px, 100px, 50px); box-shadow: 0 0 10px red;"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-auto-004.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-auto-004.html
new file mode 100644
index 0000000..d5d5ce9
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-auto-004.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip property with rect function and auto value for right value</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clip-property">
+    <link rel="match" href="reference/clip-rect-right-ref.html">
+    <meta name="assert" content="A value of 'auto' for 'right' in the rect
+    function is equal to the top edge of the border box. The box shadow should
+    be clipped, since it is painted outside the border box. On pass you should
+    see a vertical blue stripe on the right side of a vertical green stripe.">
+</head>
+<body>
+    <p>The test passes if there is a vertical blue stripe on the right side of a vertical green stripe.</p>
+    <div style="width: 100px; height: 100px; border: solid blue 50px; background-color: green; position: absolute; clip: rect(50px, auto, 150px, 100px); box-shadow: 0 0 10px red;"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-auto-005.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-auto-005.html
new file mode 100644
index 0000000..b2b3b13
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-auto-005.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip property with rect function and auto value for bottom value</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clip-property">
+    <link rel="match" href="reference/clip-rect-bottom-ref.html">
+    <meta name="assert" content="A value of 'auto' for 'bottom' in the rect
+    function is equal to the top edge of the border box. The box shadow should
+    be clipped, since it is painted outside the border box. On pass you should
+    see a horizontal blue stripe under a horizontal green stripe.">
+</head>
+<body>
+    <p>The test passes if there is a horizontal blue stripe under a horizontal green stripe.</p>
+    <div style="width: 100px; height: 100px; border: solid blue 50px; background-color: green; position: absolute; clip: rect(100px, 150px, auto, 50px); box-shadow: 0 0 10px red;"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-auto-006.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-auto-006.html
new file mode 100644
index 0000000..410b93d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-auto-006.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test clip property with rect function and auto value for left value</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clipping-paths">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clip-property">
+    <link rel="match" href="reference/clip-rect-left-ref.html">
+    <meta name="assert" content="A value of 'auto' for 'left' in the rect
+    function is equal to the top edge of the border box. The box shadow should
+    be clipped, since it is painted outside the border box. On pass you should
+    see a vertical green stripe on the right side of a vertical blue stripe.">
+</head>
+<body>
+    <p>The test passes if there is a vertical green stripe on the right side of a vertical blue stripe.</p>
+    <div style="width: 100px; height: 100px; border: solid blue 50px; background-color: green; position: absolute; clip: rect(50px, 100px, 150px, auto); box-shadow: 0 0 10px red;"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-comma-001.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-comma-001.html
new file mode 100644
index 0000000..d15b324
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-comma-001.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test comma separation of rect function on clip - no commas</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clip-property">
+    <link rel="match" href="reference/clip-absolute-positioned-ref.html">
+    <meta name="assert" content="Values for rect function on clip can be white
+    space or comma separated, but not both. Otherwise the property setting gets
+    ignored. Testing rect function with white space separation. On pass you
+    should see a green square and no red.">
+</head>
+<body>
+    <p>The test passes if there is a green square and no red.</p>
+    <div style="width: 100px; height: 100px; border: solid red 50px; background-color: green; position: absolute; clip: rect(50px 150px 150px 50px);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-comma-002.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-comma-002.html
new file mode 100644
index 0000000..aad2aeb
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-comma-002.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test comma separation of rect function on clip - no comma between 1st and 2nd value</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clip-property">
+    <link rel="match" href="reference/clip-no-clipping-ref.html">
+    <meta name="assert" content="Values for rect function on clip can be white
+    space or comma separated, but not both. Otherwise the property setting gets
+    ignored. Testing rect function without comma separation between 1st and 2nd
+    value. On pass you should see a green square with a blue border.">
+</head>
+<body>
+    <p>The test passes if there is a green square with a blue border.</p>
+    <div style="width: 100px; height: 100px; border: solid blue 50px; background-color: green; position: absolute; clip: rect(50px 150px, 150px, 50px);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-comma-003.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-comma-003.html
new file mode 100644
index 0000000..875e971
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-comma-003.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test comma separation of rect function on clip - no comma between 2nd and 3rd value</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clip-property">
+    <link rel="match" href="reference/clip-no-clipping-ref.html">
+    <meta name="assert" content="Values for rect function on clip can be white
+    space or comma separated, but not both. Otherwise the property setting gets
+    ignored. Testing rect function without comma separation between 2nd and 3rd
+    value. On pass you should see a green square with a blue border.">
+</head>
+<body>
+    <p>The test passes if there is a green square with a blue border.</p>
+    <div style="width: 100px; height: 100px; border: solid blue 50px; background-color: green; position: absolute; clip: rect(50px, 150px 150px, 50px);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-comma-004.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-comma-004.html
new file mode 100644
index 0000000..2a8b930
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/clip-rect-comma-004.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Masking: Test comma separation of rect function on clip - no comma between 3rd and 4th value</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+    <link rel="help" href="http://www.w3.org/TR/css-masking-1/#clip-property">
+    <link rel="match" href="reference/clip-no-clipping-ref.html">
+    <meta name="assert" content="Values for rect function on clip can be white
+    space or comma separated, but not both. Otherwise the property setting gets
+    ignored. Testing rect function without comma separation between 3rd and 4th
+    value. On pass you should see a green square with a blue border.">
+</head>
+<body>
+    <p>The test passes if there is a green square with a blue border.</p>
+    <div style="width: 100px; height: 100px; border: solid blue 50px; background-color: green; position: absolute; clip: rect(50px, 150px, 150px 50px);"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-absolute-positioned-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-absolute-positioned-ref.html
new file mode 100644
index 0000000..2a9a49fd
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-absolute-positioned-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Reftest Reference</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+</head>
+<body>
+    <p>The test passes if there is a green square and no red.</p>
+    <div style="width: 100px; height: 100px; border: 50px solid white; background-color: green;"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-full-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-full-ref.html
new file mode 100644
index 0000000..f556d5f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-full-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Reftest Reference</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+</head>
+<body>
+    <p>The test passes if there is a green square and no red.</p>
+    <div style="width: 200px; height: 200px; background-color: green;"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-horizontal-stripe-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-horizontal-stripe-ref.html
new file mode 100644
index 0000000..fccb183
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-horizontal-stripe-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Reftest Reference</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+</head>
+<body>
+    <p>The test passes if there is only a horizontal blue stripe.</p>
+    <div style="width: 200px; height: 50px; background-color: blue;"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-no-clipping-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-no-clipping-ref.html
new file mode 100644
index 0000000..724f8c5
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-no-clipping-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Reftest Reference</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+</head>
+<body>
+    <p>The test passes if there is a green square with a blue border.</p>
+    <div style="width: 100px; height: 100px; border: solid blue 50px; background-color: green;"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-overflow-hidden-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-overflow-hidden-ref.html
new file mode 100644
index 0000000..e6a6e12
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-overflow-hidden-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Reftest Reference</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+</head>
+<body>
+    <p>The test passes if there is a blue square and a smaller green square in the bottom right corner of the blue square.</p>
+    <div style="overflow: hidden; width: 100px; height: 100px;">
+    	<div style="width: 100px; height: 100px; border: solid blue 50px; background-color: green;"></div>
+    </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-rect-bottom-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-rect-bottom-ref.html
new file mode 100644
index 0000000..9642435bf
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-rect-bottom-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Reftest Reference</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+</head>
+<body>
+    <p>The test passes if there is a horizontal blue stripe under a horizontal green stripe.</p>
+    <div style="background-color: blue; width: 100px; height: 100px; border-left: 50px solid white; border-top: 100px solid white">
+    	<div style="width: 100px; height: 50px; background-color: green;"></div>
+    </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-rect-left-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-rect-left-ref.html
new file mode 100644
index 0000000..b6dd6e0
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-rect-left-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Reftest Reference</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+</head>
+<body>
+    <p>The test passes if there is a vertical green stripe on the right side of a vertical blue stripe.</p>
+    <div style="background-color: green; width: 100px; height: 100px; border-top: 50px solid white;">
+    	<div style="width: 50px; height: 100px; background-color: blue;"></div>
+    </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-rect-right-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-rect-right-ref.html
new file mode 100644
index 0000000..09b90e7
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-rect-right-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Reftest Reference</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+</head>
+<body>
+    <p>The test passes if there is a vertical blue stripe on the right side of a vertical green stripe.</p>
+    <div style="background-color: blue; width: 100px; height: 100px; border-left: 100px solid white; border-top: 50px solid white;">
+    	<div style="width: 50px; height: 100px; background-color: green;"></div>
+    </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-rect-top-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-rect-top-ref.html
new file mode 100644
index 0000000..5ec30181
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-rect-top-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Reftest Reference</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+</head>
+<body>
+    <p>The test passes if there is a horizontal green stripe under a horizontal blue stripe.</p>
+    <div style="background-color: green; width: 100px; height: 100px; margin-left: 50px;">
+    	<div style="width: 100px; height: 50px; background-color: blue;"></div>
+    </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-vertical-stripe-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-vertical-stripe-ref.html
new file mode 100644
index 0000000..96ccc2c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/clip/reference/clip-vertical-stripe-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>CSS Reftest Reference</title>
+    <link rel="author" title="Dirk Schulze" href="mailto:dschulze@adobe.com">
+</head>
+<body>
+    <p>The test passes if there is only a vertical blue stripe.</p>
+    <div style="width: 50px; height: 200px; background-color: blue;"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-invalid.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-invalid.html
new file mode 100644
index 0000000..1ac795b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-invalid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Masking Module Level 1: parsing clip with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/css-masking-1/#clip-property">
+<meta name="assert" content="clip supports only the grammar 'rect() | auto'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("clip", "none");
+test_invalid_value("clip", "rect(10px, 20px, 30px)");
+</script>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-path-invalid-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-path-invalid-expected.txt
new file mode 100644
index 0000000..bafc8294
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-path-invalid-expected.txt
@@ -0,0 +1,28 @@
+This is a testharness.js-based test.
+PASS e.style['clip-path'] = "auto" should not set the property value
+PASS e.style['clip-path'] = "ray(0deg)" should not set the property value
+PASS e.style['clip-path'] = "inset()" should not set the property value
+PASS e.style['clip-path'] = "inset(123)" should not set the property value
+PASS e.style['clip-path'] = "inset(1% 2% 3% 4% 5%)" should not set the property value
+PASS e.style['clip-path'] = "inset(round 0)" should not set the property value
+PASS e.style['clip-path'] = "inset(0px round)" should not set the property value
+PASS e.style['clip-path'] = "inset(0px round 123)" should not set the property value
+PASS e.style['clip-path'] = "inset(0px round 1% 2% 3% 4% 5%)" should not set the property value
+PASS e.style['clip-path'] = "inset(0px round / 1px)" should not set the property value
+PASS e.style['clip-path'] = "inset(10px round -20px)" should not set the property value
+PASS e.style['clip-path'] = "inset(30% round -40%)" should not set the property value
+PASS e.style['clip-path'] = "circle(123)" should not set the property value
+PASS e.style['clip-path'] = "circle(at)" should not set the property value
+PASS e.style['clip-path'] = "circle(10% 20%)" should not set the property value
+PASS e.style['clip-path'] = "circle(-10px at 20px 30px)" should not set the property value
+PASS e.style['clip-path'] = "circle(-10% at 20% 30%)" should not set the property value
+PASS e.style['clip-path'] = "circle(1% 2% at 0% 100%)" should not set the property value
+PASS e.style['clip-path'] = "ellipse(farthest-side at)" should not set the property value
+PASS e.style['clip-path'] = "ellipse(1% 2% top right)" should not set the property value
+FAIL e.style['clip-path'] = "ellipse(3% at 100% 0%)" should not set the property value assert_equals: expected "" but got "ellipse(3% at 100% 0%)"
+PASS e.style['clip-path'] = "ellipse(10% -20% at 30% 40%)" should not set the property value
+PASS e.style['clip-path'] = "ellipse(-50px 60px at 70% 80%)" should not set the property value
+PASS e.style['clip-path'] = "polygon(1%)" should not set the property value
+PASS e.style['clip-path'] = "unknown-box" should not set the property value
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-path-invalid.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-path-invalid.html
new file mode 100644
index 0000000..3f5940a5f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-path-invalid.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Masking Module Level 1: parsing clip-path with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/css-masking-1/#the-clip-path">
+<meta name="assert" content="clip-path supports only the grammar '<clip-source> | [ <basic-shape> || <geometry-box> ] | none'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("clip-path", "auto");
+test_invalid_value("clip-path", "ray(0deg)");
+
+test_invalid_value("clip-path", "inset()");
+test_invalid_value("clip-path", "inset(123)");
+test_invalid_value("clip-path", "inset(1% 2% 3% 4% 5%)");
+test_invalid_value("clip-path", "inset(round 0)");
+test_invalid_value("clip-path", "inset(0px round)");
+test_invalid_value("clip-path", "inset(0px round 123)");
+test_invalid_value("clip-path", "inset(0px round 1% 2% 3% 4% 5%)");
+test_invalid_value("clip-path", "inset(0px round / 1px)");
+test_invalid_value("clip-path", "inset(10px round -20px)");
+test_invalid_value("clip-path", "inset(30% round -40%)");
+
+test_invalid_value("clip-path", "circle(123)");
+test_invalid_value("clip-path", "circle(at)");
+test_invalid_value("clip-path", "circle(10% 20%)");
+test_invalid_value("clip-path", "circle(-10px at 20px 30px)");
+test_invalid_value("clip-path", "circle(-10% at 20% 30%)");
+test_invalid_value("clip-path", "circle(1% 2% at 0% 100%)");
+
+test_invalid_value("clip-path", "ellipse(farthest-side at)");
+test_invalid_value("clip-path", "ellipse(1% 2% top right)");
+test_invalid_value("clip-path", "ellipse(3% at 100% 0%)");
+test_invalid_value("clip-path", "ellipse(10% -20% at 30% 40%)");
+test_invalid_value("clip-path", "ellipse(-50px 60px at 70% 80%)");
+
+test_invalid_value("clip-path", "polygon(1%)");
+
+test_invalid_value("clip-path", "unknown-box");
+</script>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-path-valid-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-path-valid-expected.txt
new file mode 100644
index 0000000..3b1e58479
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-path-valid-expected.txt
@@ -0,0 +1,37 @@
+This is a testharness.js-based test.
+PASS e.style['clip-path'] = "none" should set the property value
+PASS e.style['clip-path'] = "inset(100%)" should set the property value
+PASS e.style['clip-path'] = "inset(0 1px)" should set the property value
+PASS e.style['clip-path'] = "inset(0px 1px 2%)" should set the property value
+PASS e.style['clip-path'] = "inset(0px 1px 2% 3em)" should set the property value
+PASS e.style['clip-path'] = "inset(0px round 100%)" should set the property value
+PASS e.style['clip-path'] = "inset(0px round 0 1px)" should set the property value
+PASS e.style['clip-path'] = "inset(0px round 0px 1px 2%)" should set the property value
+PASS e.style['clip-path'] = "inset(0px round 0px 1px 2% 3em)" should set the property value
+PASS e.style['clip-path'] = "inset(10px round 20% / 0px 1px 2% 3em)" should set the property value
+PASS e.style['clip-path'] = "circle()" should set the property value
+PASS e.style['clip-path'] = "circle(1px)" should set the property value
+PASS e.style['clip-path'] = "circle(closest-side)" should set the property value
+PASS e.style['clip-path'] = "circle(at 10% 20%)" should set the property value
+PASS e.style['clip-path'] = "circle(farthest-side at center top)" should set the property value
+PASS e.style['clip-path'] = "circle(4% at top right)" should set the property value
+PASS e.style['clip-path'] = "ellipse()" should set the property value
+PASS e.style['clip-path'] = "ellipse(1px closest-side)" should set the property value
+PASS e.style['clip-path'] = "ellipse(at 10% 20%)" should set the property value
+PASS e.style['clip-path'] = "ellipse(farthest-side 4% at bottom left)" should set the property value
+PASS e.style['clip-path'] = "polygon(1% 2%)" should set the property value
+PASS e.style['clip-path'] = "polygon(nonzero, 1px 2px, 3em 4em)" should set the property value
+PASS e.style['clip-path'] = "polygon(evenodd, 1px 2px, 3em 4em, 5pt 6%)" should set the property value
+FAIL e.style['clip-path'] = "border-box" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['clip-path'] = "padding-box" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['clip-path'] = "content-box" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['clip-path'] = "margin-box" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['clip-path'] = "fill-box" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['clip-path'] = "stroke-box" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['clip-path'] = "view-box" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['clip-path'] = "circle(7% at 8% 9%) border-box" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['clip-path'] = "border-box circle(7% at 8% 9%)" should set the property value assert_not_equals: property should be set got disallowed value ""
+PASS e.style['clip-path'] = "url(https://example.com/)" should set the property value
+PASS e.style['clip-path'] = "url(\"https://example.com/\")" should set the property value
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-path-valid.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-path-valid.html
new file mode 100644
index 0000000..d7b27859
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-path-valid.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Masking Module Level 1: parsing clip-path with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/css-masking-1/#the-clip-path">
+<meta name="assert" content="clip-path supports the full grammar '<clip-source> | [ <basic-shape> || <geometry-box> ] | none'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("clip-path", "none");
+
+// <basic-shape>
+test_valid_value("clip-path", "inset(100%)");
+test_valid_value("clip-path", "inset(0 1px)", "inset(0px 1px)");
+test_valid_value("clip-path", "inset(0px 1px 2%)");
+test_valid_value("clip-path", "inset(0px 1px 2% 3em)");
+test_valid_value("clip-path", "inset(0px round 100%)");
+test_valid_value("clip-path", "inset(0px round 0 1px)", "inset(0px round 0px 1px)");
+test_valid_value("clip-path", "inset(0px round 0px 1px 2%)");
+test_valid_value("clip-path", "inset(0px round 0px 1px 2% 3em)");
+test_valid_value("clip-path", "inset(10px round 20% / 0px 1px 2% 3em)");
+
+test_valid_value("clip-path", "circle()", "circle(at 50% 50%)");
+test_valid_value("clip-path", "circle(1px)", "circle(1px at 50% 50%)");
+test_valid_value("clip-path", "circle(closest-side)", "circle(at 50% 50%)");
+test_valid_value("clip-path", "circle(at 10% 20%)");
+test_valid_value("clip-path", "circle(farthest-side at center top)", "circle(farthest-side at 50% 0%)");
+test_valid_value("clip-path", "circle(4% at top right)", "circle(4% at 100% 0%)");
+
+test_valid_value("clip-path", "ellipse()", "ellipse(at 50% 50%)");
+test_valid_value("clip-path", "ellipse(1px closest-side)", "ellipse(1px at 50% 50%)");
+test_valid_value("clip-path", "ellipse(at 10% 20%)");
+test_valid_value("clip-path", "ellipse(farthest-side 4% at bottom left)", "ellipse(farthest-side 4% at 0% 100%)");
+
+test_valid_value("clip-path", "polygon(1% 2%)");
+test_valid_value("clip-path", "polygon(nonzero, 1px 2px, 3em 4em)", "polygon(1px 2px, 3em 4em)");
+test_valid_value("clip-path", "polygon(evenodd, 1px 2px, 3em 4em, 5pt 6%)");
+
+// <geometry-box>
+test_valid_value("clip-path", "border-box");
+test_valid_value("clip-path", "padding-box");
+test_valid_value("clip-path", "content-box");
+test_valid_value("clip-path", "margin-box");
+test_valid_value("clip-path", "fill-box");
+test_valid_value("clip-path", "stroke-box");
+test_valid_value("clip-path", "view-box");
+
+// basic-shape> <geometry-box>
+test_valid_value("clip-path", "circle(7% at 8% 9%) border-box");
+
+// <geometry-box> basic-shape>
+test_valid_value("clip-path", "border-box circle(7% at 8% 9%)");
+
+//  <clip-source>
+test_valid_value("clip-path", "url(https://example.com/)", ["url(https://example.com/)", "url(\"https://example.com/\")"]);
+test_valid_value("clip-path", "url(\"https://example.com/\")", ["url(https://example.com/)", "url(\"https://example.com/\")"]);
+</script>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-rule-invalid.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-rule-invalid.html
new file mode 100644
index 0000000..10f6aee
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-rule-invalid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Masking Module Level 1: parsing clip-rule with invalid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/css-masking-1/#the-clip-rule">
+<meta name="assert" content="clip-rule supports only the grammar 'nonzero | evenodd'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("clip-rule", "auto");
+test_invalid_value("clip-rule", "1");
+</script>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-rule-valid.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-rule-valid.html
new file mode 100644
index 0000000..db22680
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-rule-valid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Masking Module Level 1: parsing clip-rule with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/css-masking-1/#the-clip-rule">
+<meta name="assert" content="clip-rule supports the full grammar 'nonzero | evenodd'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("clip-rule", "nonzero");
+test_valid_value("clip-rule", "evenodd");
+</script>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-valid-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-valid-expected.txt
new file mode 100644
index 0000000..fc6bf51
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-valid-expected.txt
@@ -0,0 +1,6 @@
+This is a testharness.js-based test.
+PASS e.style['clip'] = "auto" should set the property value
+PASS e.style['clip'] = "rect(10px, 20px, -30px, 40px)" should set the property value
+FAIL e.style['clip'] = "rect(10%, -20%, auto, auto)" should set the property value assert_not_equals: property should be set got disallowed value ""
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-valid.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-valid.html
new file mode 100644
index 0000000..24bb782d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/clip-valid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Masking Module Level 1: parsing clip with valid values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/css-masking-1/#clip-property">
+<meta name="assert" content="clip supports the full grammar 'rect() | auto'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("clip", "auto");
+test_valid_value("clip", "rect(10px, 20px, -30px, 40px)", ["rect(10px, 20px, -30px, 40px)", "rect(10px 20px -30px 40px)"]);
+test_valid_value("clip", "rect(10%, -20%, auto, auto)");
+</script>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/resources/parsing-testcommon.js b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/resources/parsing-testcommon.js
new file mode 100644
index 0000000..b075882
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/parsing/resources/parsing-testcommon.js
@@ -0,0 +1,39 @@
+'use strict';
+
+// serializedValue can be the expected serialization of value,
+// or an array of permitted serializations,
+// or omitted if value should serialize as value.
+function test_valid_value(property, value, serializedValue) {
+    if (arguments.length < 3)
+        serializedValue = value;
+
+    var stringifiedValue = JSON.stringify(value);
+
+    test(function(){
+        var div = document.createElement('div');
+        div.style[property] = value;
+        assert_not_equals(div.style.getPropertyValue(property), "", "property should be set");
+
+        var div = document.createElement('div');
+        div.style[property] = value;
+        var readValue = div.style.getPropertyValue(property);
+        if (serializedValue instanceof Array)
+            assert_in_array(readValue, serializedValue, "serialization should be sound");
+        else
+            assert_equals(readValue, serializedValue, "serialization should be canonical");
+
+        div.style[property] = readValue;
+        assert_equals(div.style.getPropertyValue(property), readValue, "serialization should round-trip");
+
+    }, "e.style['" + property + "'] = " + stringifiedValue + " should set the property value");
+}
+
+function test_invalid_value(property, value) {
+    var stringifiedValue = JSON.stringify(value);
+
+    test(function(){
+        var div = document.createElement('div');
+        div.style[property] = value;
+        assert_equals(div.style.getPropertyValue(property), "");
+    }, "e.style['" + property + "'] = " + stringifiedValue + " should not set the property value");
+}
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/test-mask-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/test-mask-ref.html
new file mode 100644
index 0000000..938235ac
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/test-mask-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>CSS Masking: mask-repeat:round repeat;</title>
+<link rel="author" title="Li Jun" href="mailto:64835173@qq.com">
+<link rel="reviewer" title="Dirk Schulze" href="mailto:dschulze@adobe.com"><!-- 11-09-2013 @TestTWF Shenzhen -->
+
+<body>
+    <p>Test passes if there is a blue square and no red.</p>
+    <div style="width: 200px; height:200px; position: fixed; background-color:blue;"></div>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/test-mask.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/test-mask.html
new file mode 100644
index 0000000..b1e2156
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-masking/test-mask.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>CSS Masking: mask-repeat:round repeat;</title>
+<link rel="author" title="Li Jun" href="mailto:64835173@qq.com">
+<link rel="reviewer" title="Dirk Schulze" href="mailto:dschulze@adobe.com"><!-- 11-09-2013 @TestTWF Shenzhen -->
+<link rel="help" href="http://www.w3.org/TR/css-masking-1/#the-mask-repeat">
+<link rel="match" href="test-mask-ref.html">
+<meta name="assert" content="Test checks that the mask-repeart value "round" is working properly: it should scale to fit the whole area.">
+<style>
+.test {
+	width:200px;
+	height:200px;
+	white-space:normal;
+	background:blue;
+ 	mask-image:radial-gradient(black, black);
+ 	mask-repeat:round;
+ 	mask-size:100px 100px;
+ 	mask-box-image:none;
+ 	mask-origin:border;
+};
+</style>
+<body>
+    <p>Test passes if there is a blue square and no red.</p>
+    <div style="width: 200px; height:200px; position: fixed; background-color:red;"></div>
+	<div class="test"></div>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-html-correctly-labeled.sub-expected.html b/third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-html-correctly-labeled.sub-ref.html
similarity index 100%
rename from third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-html-correctly-labeled.sub-expected.html
rename to third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-html-correctly-labeled.sub-ref.html
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-html-correctly-labeled.sub.html b/third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-html-correctly-labeled.sub.html
index b2612b4..78fc09f9 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-html-correctly-labeled.sub.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-html-correctly-labeled.sub.html
@@ -6,6 +6,6 @@
 -->
 <meta charset="utf-8">
 <!-- Reference page uses same-origin resources, which are not CORB-eligible. -->
-<link rel="match" href="img-png-mislabeled-as-html.sub-expected.html">
+<link rel="match" href="img-png-mislabeled-as-html.sub-ref.html">
 <!-- www1 is cross-origin, so the HTTP response is CORB-eligible -->
 <img src="http://{{domains[www1]}}:{{ports[http][0]}}/fetch/corb/resources/html-correctly-labeled.html">
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub-expected.html b/third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub-ref.html
similarity index 100%
rename from third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub-expected.html
rename to third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub-ref.html
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub.html b/third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub.html
index 19d29b20..a4e519d 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-png-mislabeled-as-html-nosniff.tentative.sub.html
@@ -6,6 +6,6 @@
 -->
 <meta charset="utf-8">
 <!-- Reference page uses same-origin resources, which are not CORB-eligible. -->
-<link rel="match" href="img-png-mislabeled-as-html.sub-expected.html">
+<link rel="match" href="img-png-mislabeled-as-html.sub-ref.html">
 <!-- www1 is cross-origin, so the HTTP response is CORB-eligible -->
 <img src="http://{{domains[www1]}}:{{ports[http][0]}}/fetch/corb/resources/png-mislabeled-as-html-nosniff.png">
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-png-mislabeled-as-html.sub-expected.html b/third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-png-mislabeled-as-html.sub-ref.html
similarity index 100%
rename from third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-png-mislabeled-as-html.sub-expected.html
rename to third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-png-mislabeled-as-html.sub-ref.html
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-png-mislabeled-as-html.sub.html b/third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-png-mislabeled-as-html.sub.html
index 85d444d..1ae4cfc 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-png-mislabeled-as-html.sub.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/fetch/corb/img-png-mislabeled-as-html.sub.html
@@ -5,6 +5,6 @@
 -->
 <meta charset="utf-8">
 <!-- Reference page uses same-origin resources, which are not CORB-eligible. -->
-<link rel="match" href="img-png-mislabeled-as-html.sub-expected.html">
+<link rel="match" href="img-png-mislabeled-as-html.sub-ref.html">
 <!-- www1 is cross-origin, so the HTTP response is CORB-eligible -->
 <img src="http://{{domains[www1]}}:{{ports[http][0]}}/fetch/corb/resources/png-mislabeled-as-html.png">
diff --git a/third_party/WebKit/LayoutTests/external/wpt/interfaces/cookie-store.idl b/third_party/WebKit/LayoutTests/external/wpt/interfaces/cookie-store.idl
index 6d4f359..b38f61e7 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/interfaces/cookie-store.idl
+++ b/third_party/WebKit/LayoutTests/external/wpt/interfaces/cookie-store.idl
@@ -13,13 +13,26 @@
 };
 
 [
-  Exposed=(ServiceWorker,Window),
+  Exposed=Window,
   Constructor(DOMString type, optional CookieChangeEventInit eventInitDict)
 ] interface CookieChangeEvent : Event {
   readonly attribute CookieList changed;
   readonly attribute CookieList deleted;
 };
 
+dictionary ExtendableCookieChangeEventInit : ExtendableEventInit {
+  CookieList changed;
+  CookieList deleted;
+};
+
+[
+  Exposed=ServiceWorker,
+  Constructor(DOMString type, optional ExtendableCookieChangeEventInit eventInitDict)
+] interface ExtendableCookieChangeEvent : ExtendableEvent {
+  readonly attribute CookieList changed;
+  readonly attribute CookieList deleted;
+};
+
 enum CookieMatchType {
   "equals",
   "startsWith"
@@ -59,7 +72,11 @@
   Promise<void> delete(USVString name, optional CookieStoreSetOptions options);
   Promise<void> delete(CookieStoreSetOptions options);
 
-  attribute EventHandler onchange;
+  [Exposed=ServiceWorker] Promise<void> subscribeToChanges(sequence<CookieStoreGetOptions> subscriptions);
+
+  [Exposed=ServiceWorker] Promise<sequence<CookieStoreGetOptions>> getChangeSubscriptions();
+
+  [Exposed=Window] attribute EventHandler onchange;
 };
 
 partial interface Window {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/svg/extensibility/foreignObject/stacking-context-expected.html b/third_party/WebKit/LayoutTests/external/wpt/svg/extensibility/foreignObject/stacking-context-ref.html
similarity index 100%
rename from third_party/WebKit/LayoutTests/external/wpt/svg/extensibility/foreignObject/stacking-context-expected.html
rename to third_party/WebKit/LayoutTests/external/wpt/svg/extensibility/foreignObject/stacking-context-ref.html
diff --git a/third_party/WebKit/LayoutTests/external/wpt/svg/extensibility/foreignObject/stacking-context.html b/third_party/WebKit/LayoutTests/external/wpt/svg/extensibility/foreignObject/stacking-context.html
index c60a111..dfad5a1 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/svg/extensibility/foreignObject/stacking-context.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/svg/extensibility/foreignObject/stacking-context.html
@@ -1,6 +1,6 @@
 <!doctype HTML>
 <title>Test that the foreignObject element is a stacking context</title>
-<link rel="match" href="stacking-context-expected.html">
+<link rel="match" href="stacking-context-ref.html">
 <link rel="help" href="https://svgwg.org/svg2-draft/single-page.html#embedded-ForeignObjectElement"/>
 <style>
   * {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-setDescription-transceiver.html b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-setDescription-transceiver.html
index a44a165..d19b474 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-setDescription-transceiver.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-setDescription-transceiver.html
@@ -109,7 +109,7 @@
     .then(offer => {
       return Promise.all([
         pc1.setLocalDescription(offer),
-        pc2.setRemoteDescrption(offer)
+        pc2.setRemoteDescription(offer)
       ])
       .then(() => {
         const transceivers = pc2.getTransceivers();
diff --git a/third_party/WebKit/LayoutTests/fast/forms/date/date-chooseronly-defaultValue-expected.html b/third_party/WebKit/LayoutTests/fast/forms/date/date-chooseronly-defaultValue-expected.html
new file mode 100644
index 0000000..0daef0e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/forms/date/date-chooseronly-defaultValue-expected.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<body>
+<input type="date" value="2015-07-25">
+</body>
diff --git a/third_party/WebKit/LayoutTests/fast/forms/date/date-chooseronly-defaultValue.html b/third_party/WebKit/LayoutTests/fast/forms/date/date-chooseronly-defaultValue.html
new file mode 100644
index 0000000..6144d70
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/forms/date/date-chooseronly-defaultValue.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<!-- A test for crbug.com/838898. defaultValue setter should invalidate view. -->
+<body>
+<input type="date">
+<script>
+document.querySelector('input').defaultValue = '2015-07-25';
+</script>
+</body>
diff --git a/third_party/WebKit/LayoutTests/fast/mediastream/MediaStream-MediaElement-networkState.html b/third_party/WebKit/LayoutTests/fast/mediastream/MediaStream-MediaElement-networkState.html
new file mode 100644
index 0000000..b2d4a26
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/mediastream/MediaStream-MediaElement-networkState.html
@@ -0,0 +1,73 @@
+<!doctype html>
+<html>
+<head>
+<title>The networkState of a media element that has assigned a MediaStream
+  should be NETWORK_LOADING if the stream is active and NETWORK_IDLE if the
+  stream is inactive.
+</title>
+</head>
+<body>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script>
+var t = async_test("Tests that the networkState of a video element playing a " +
+                   "MediaStream is updated correctly.");
+t.step(function() {
+  navigator.mediaDevices.getUserMedia({audio:true, video: true})
+    .then(t.step_func(stream => {
+      var video = document.createElement('video');
+      assert_equals(video.networkState, HTMLMediaElement.NETWORK_EMPTY);
+      video.srcObject = stream;
+      setTimeout(()=>{
+        assert_equals(video.networkState, HTMLMediaElement.NETWORK_LOADING);
+        stream.getTracks().forEach(track => track.stop());
+        assert_equals(video.networkState, HTMLMediaElement.NETWORK_IDLE);
+
+        // Removing and re-adding the stopped audio track does not change the
+        // network state.
+        var track = stream.getAudioTracks()[0];
+        stream.removeTrack(track);
+        assert_equals(video.networkState, HTMLMediaElement.NETWORK_IDLE);
+        stream.addTrack(track);
+        assert_equals(video.networkState, HTMLMediaElement.NETWORK_IDLE);
+
+        // Removing and re-adding the stopped tracks does not change the
+        // network state.
+        var track = stream.getVideoTracks()[0];
+        stream.removeTrack(track);
+        assert_equals(video.networkState, HTMLMediaElement.NETWORK_IDLE);
+        stream.addTrack(track);
+        assert_equals(video.networkState, HTMLMediaElement.NETWORK_IDLE);
+
+        var track = stream.getAudioTracks()[0];
+        stream.removeTrack(track);
+        assert_equals(video.networkState, HTMLMediaElement.NETWORK_IDLE);
+        stream.addTrack(track);
+        assert_equals(video.networkState, HTMLMediaElement.NETWORK_IDLE);
+
+        // Replacing the existing tracks with new active tracks does change the
+        // network state.
+        navigator.mediaDevices.getUserMedia({audio:true, video:true}).then(
+          t.step_func(stream2 => {
+            stream.removeTrack(stream.getAudioTracks()[0]);
+            stream.addTrack(stream2.getAudioTracks()[0]);
+            assert_equals(video.networkState, HTMLMediaElement.NETWORK_LOADING);
+
+            stream.removeTrack(stream.getAudioTracks()[0]);
+            assert_equals(video.networkState, HTMLMediaElement.NETWORK_IDLE);
+
+            stream.removeTrack(stream.getVideoTracks()[0]);
+            stream.addTrack(stream2.getVideoTracks()[0]);
+            assert_equals(video.networkState, HTMLMediaElement.NETWORK_LOADING);
+
+            stream.removeTrack(stream.getVideoTracks()[0]);
+            assert_equals(video.networkState, HTMLMediaElement.NETWORK_IDLE);
+
+            t.done();
+          }), t.unreached_func);
+      });
+    }), t.unreached_func);
+});
+</script>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/WebKit/LayoutTests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index 18a8093..830b00b 100644
--- a/third_party/WebKit/LayoutTests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -157,21 +157,16 @@
     getter reason
     getter wasClean
     method constructor
-interface CookieChangeEvent : Event
-    attribute @@toStringTag
-    getter changed
-    getter deleted
-    method constructor
 interface CookieStore : EventTarget
     attribute @@toStringTag
-    getter onchange
     method constructor
     method delete
     method get
     method getAll
+    method getChangeSubscriptions
     method has
     method set
-    setter onchange
+    method subscribeToChanges
 interface CountQueuingStrategy
     method constructor
     method size
@@ -439,6 +434,11 @@
     method constructor
     method dispatchEvent
     method removeEventListener
+interface ExtendableCookieChangeEvent : ExtendableEvent
+    attribute @@toStringTag
+    getter changed
+    getter deleted
+    method constructor
 interface ExtendableEvent : Event
     attribute @@toStringTag
     method constructor
diff --git a/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/external/wpt/css/css-scoping/README.txt b/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/external/wpt/css/css-scoping/README.txt
deleted file mode 100644
index 8ba8ab5b..0000000
--- a/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/external/wpt/css/css-scoping/README.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-# This suite runs the tests in external/wpt/css/css-scoping with
-# --enable-blink-features=IncrementalShadowDOM.
-# See crbug.com/776656 for details.
diff --git a/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/external/wpt/shadow-dom/README.txt b/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/external/wpt/shadow-dom/README.txt
deleted file mode 100644
index d0d8b92..0000000
--- a/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/external/wpt/shadow-dom/README.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-# This suite runs the tests in external/wpt/shadow-dom with
-# --enable-blink-features=IncrementalShadowDOM.
-# See crbug.com/776656 for details.
diff --git a/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/fast/dom/shadow/README.txt b/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/fast/dom/shadow/README.txt
deleted file mode 100644
index 3045fb5..0000000
--- a/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/fast/dom/shadow/README.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-# This suite runs the tests in fast/dom/shadow with
-# --enable-blink-features=IncrementalShadowDOM.
-# See crbug.com/776656 for details.
diff --git a/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/html/details_summary/README.txt b/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/html/details_summary/README.txt
deleted file mode 100644
index 17b274a..0000000
--- a/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/html/details_summary/README.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-# This suite runs the tests in html/details_summary with
-# --enable-blink-features=IncrementalShadowDOM.
-# See crbug.com/776656 for details.
diff --git a/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/http/tests/devtools/elements/shadow/README.txt b/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/http/tests/devtools/elements/shadow/README.txt
deleted file mode 100644
index 204f08b..0000000
--- a/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/http/tests/devtools/elements/shadow/README.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-# This suite runs the tests in http/tests/devtools/elements/shadow with
-# --enable-blink-features=IncrementalShadowDOM.
-# See crbug.com/776656 for details.
-# See crbug.com/840238 for http/tests/devtools/elements/shadow-distribution.js
diff --git a/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/http/tests/devtools/elements/shadow/shadow-distribution-expected.txt b/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/http/tests/devtools/elements/shadow/shadow-distribution-expected.txt
deleted file mode 100644
index 0e10558..0000000
--- a/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/http/tests/devtools/elements/shadow/shadow-distribution-expected.txt
+++ /dev/null
@@ -1,257 +0,0 @@
-Tests that elements panel updates dom tree structure upon distribution in shadow dom.
-
-
-Running: createHost1
-- <div id="host1">
-    - #shadow-root (open)
-        - <slot id="slot1" name="slot1">
-          </slot>
-        - <slot id="slot2" name="slot2">
-          </slot>
-        - <slot id="slot3">
-          </slot>
-  </div>
-
-Running: createChild1
-- <div id="host1">
-    - #shadow-root (open)
-        - <slot id="slot1" name="slot1">
-          </slot>
-        - <slot id="slot2" name="slot2">
-              ↪ <span>
-          </slot>
-        - <slot id="slot3">
-          </slot>
-      <span id="child1" slot="slot2"></span>
-  </div>
-
-Running: createChild2
-- <div id="host1">
-    - #shadow-root (open)
-        - <slot id="slot1" name="slot1">
-          </slot>
-        - <slot id="slot2" name="slot2">
-              ↪ <span>
-          </slot>
-        - <slot id="slot3">
-              ↪ <div>
-          </slot>
-      <span id="child1" slot="slot2"></span>
-      <div id="child2"></div>
-  </div>
-
-Running: createChild3
-- <div id="host1">
-    - #shadow-root (open)
-        - <slot id="slot1" name="slot1">
-          </slot>
-        - <slot id="slot2" name="slot2">
-              ↪ <span>
-              ↪ <h1>
-          </slot>
-        - <slot id="slot3">
-              ↪ <div>
-          </slot>
-      <span id="child1" slot="slot2"></span>
-      <div id="child2"></div>
-      <h1 id="child3" slot="slot2"></h1>
-  </div>
-
-Running: createChild4
-- <div id="host1">
-    - #shadow-root (open)
-        - <slot id="slot1" name="slot1">
-              ↪ <h2>
-          </slot>
-        - <slot id="slot2" name="slot2">
-              ↪ <span>
-              ↪ <h1>
-          </slot>
-        - <slot id="slot3">
-              ↪ <div>
-          </slot>
-      <span id="child1" slot="slot2"></span>
-      <div id="child2"></div>
-      <h1 id="child3" slot="slot2"></h1>
-      <h2 id="child4" slot="slot1"></h2>
-  </div>
-
-Running: createChild5
-- <div id="host1">
-    - #shadow-root (open)
-        - <slot id="slot1" name="slot1">
-              ↪ <h2>
-          </slot>
-        - <slot id="slot2" name="slot2">
-              ↪ <span>
-              ↪ <h1>
-          </slot>
-        - <slot id="slot3">
-              ↪ <div>
-          </slot>
-      <span id="child1" slot="slot2"></span>
-      <div id="child2"></div>
-      <h1 id="child3" slot="slot2"></h1>
-      <h2 id="child4" slot="slot1"></h2>
-      <h3 id="child5" slot="slot3"></h3>
-  </div>
-
-Running: modifyChild1
-- <div id="host1">
-    - #shadow-root (open)
-        - <slot id="slot1" name="slot1">
-              ↪ <h2>
-          </slot>
-        - <slot id="slot2" name="slot2">
-              ↪ <span>
-              ↪ <h1>
-          </slot>
-        - <slot id="slot3">
-              ↪ <div>
-          </slot>
-      <span id="child1" slot="slot1"></span>
-      <div id="child2"></div>
-      <h1 id="child3" slot="slot2"></h1>
-      <h2 id="child4" slot="slot1"></h2>
-      <h3 id="child5" slot="slot3"></h3>
-  </div>
-
-Running: modifyChild4
-- <div id="host1">
-    - #shadow-root (open)
-        - <slot id="slot1" name="slot1">
-              ↪ <h2>
-          </slot>
-        - <slot id="slot2" name="slot2">
-              ↪ <span>
-              ↪ <h1>
-          </slot>
-        - <slot id="slot3">
-              ↪ <div>
-          </slot>
-      <span id="child1" slot="slot1"></span>
-      <div id="child2"></div>
-      <h1 id="child3" slot="slot2"></h1>
-      <h2 id="child4"></h2>
-      <h3 id="child5" slot="slot3"></h3>
-  </div>
-
-Running: modifySlot1
-- <div id="host1">
-    - #shadow-root (open)
-        - <slot id="slot1" name="slot3">
-              ↪ <h2>
-          </slot>
-        - <slot id="slot2" name="slot2">
-              ↪ <span>
-              ↪ <h1>
-          </slot>
-        - <slot id="slot3">
-              ↪ <div>
-          </slot>
-      <span id="child1" slot="slot1"></span>
-      <div id="child2"></div>
-      <h1 id="child3" slot="slot2"></h1>
-      <h2 id="child4"></h2>
-      <h3 id="child5" slot="slot3"></h3>
-  </div>
-
-Running: modifySlot2
-- <div id="host1">
-    - #shadow-root (open)
-        - <slot id="slot1" name="slot3">
-              ↪ <h2>
-          </slot>
-        - <slot id="slot2" name="slot1">
-              ↪ <span>
-              ↪ <h1>
-          </slot>
-        - <slot id="slot3">
-              ↪ <div>
-          </slot>
-      <span id="child1" slot="slot1"></span>
-      <div id="child2"></div>
-      <h1 id="child3" slot="slot2"></h1>
-      <h2 id="child4"></h2>
-      <h3 id="child5" slot="slot3"></h3>
-  </div>
-
-Running: removeChild3
-- <div id="host1">
-    - #shadow-root (open)
-        - <slot id="slot1" name="slot3">
-              ↪ <h3>
-          </slot>
-        - <slot id="slot2" name="slot1">
-              ↪ <span>
-          </slot>
-        - <slot id="slot3">
-              ↪ <div>
-              ↪ <h2>
-          </slot>
-      <span id="child1" slot="slot1"></span>
-      <div id="child2"></div>
-      <h2 id="child4"></h2>
-      <h3 id="child5" slot="slot3"></h3>
-  </div>
-
-Running: removeChild1
-- <div id="host1">
-    - #shadow-root (open)
-        - <slot id="slot1" name="slot3">
-              ↪ <h3>
-          </slot>
-        - <slot id="slot2" name="slot1">
-          </slot>
-        - <slot id="slot3">
-              ↪ <div>
-              ↪ <h2>
-          </slot>
-      <div id="child2"></div>
-      <h2 id="child4"></h2>
-      <h3 id="child5" slot="slot3"></h3>
-  </div>
-
-Running: removeSlot1
-- <div id="host1">
-    - #shadow-root (open)
-        - <slot id="slot2" name="slot1">
-          </slot>
-        - <slot id="slot3">
-              ↪ <div>
-              ↪ <h2>
-          </slot>
-      <div id="child2"></div>
-      <h2 id="child4"></h2>
-      <h3 id="child5" slot="slot3"></h3>
-  </div>
-
-Running: createHost2
-- <div id="host2">
-    - #shadow-root (open)
-        - <slot id="slot1" name="slot3">
-          </slot>
-  </div>
-
-Running: moveChild5FromHost1ToHost2
-- <div id="host2">
-    - #shadow-root (open)
-        - <slot id="slot1" name="slot3">
-              ↪ <h3>
-          </slot>
-      <h3 id="child5" slot="slot3"></h3>
-  </div>
-
-Running: modifyChild4
-- <div id="host1">
-    - #shadow-root (open)
-        - <slot id="slot2" name="slot1">
-          </slot>
-        - <slot id="slot3">
-              ↪ <div>
-              ↪ <h2>
-          </slot>
-      <div id="child2"></div>
-      <h2 id="child4" slot="slot1"></h2>
-  </div>
-
diff --git a/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/media/controls/README.txt b/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/media/controls/README.txt
deleted file mode 100644
index a39b9612..0000000
--- a/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/media/controls/README.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-# This suite runs the tests in media/controls/ with
-# --enable-blink-features=IncrementalShadowDOM.
-# See crbug.com/776656 for details.
diff --git a/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/shadow-dom/README.txt b/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/shadow-dom/README.txt
deleted file mode 100644
index d8ff258..0000000
--- a/third_party/WebKit/LayoutTests/virtual/incremental-shadow-dom/shadow-dom/README.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-# This suite runs the tests in shadow-dom with
-# --enable-blink-features=IncrementalShadowDOM.
-# See crbug.com/776656 for details.
diff --git a/third_party/abseil-cpp/BUILD.gn b/third_party/abseil-cpp/BUILD.gn
index 8dcb9e9..eaaf015 100644
--- a/third_party/abseil-cpp/BUILD.gn
+++ b/third_party/abseil-cpp/BUILD.gn
@@ -19,13 +19,23 @@
 }
 
 config("absl_include_config") {
-  # Using -isystem instead of include_dirs (-I), so we don't need to suppress
-  # warnings coming from Abseil. Doing so would mask warnings in our own code.
-  if (!is_clang && is_win) {
-    # MSVC doesn't have -isystem, in that case we fallback to include_dirs and
-    # we use the warning suppression flags defined in :absl_default_cflags_cc.
-    include_dirs = [ "." ]
+  # Using -isystem (with clang and GCC) and -imsvc (with clang-cl) instead of
+  # include_dirs (-I), so we don't need to suppress warnings coming from
+  # Abseil. Doing so would mask warnings in our own code.
+  if (is_win) {
+    if (is_clang) {
+      # clang-cl:
+      cflags = [
+        "-imsvc",
+        rebase_path(".", root_build_dir),
+      ]
+    } else {
+      # MSVC doesn't have -isystem, in that case we fallback to include_dirs and
+      # we use the warning suppression flags defined in :absl_default_cflags_cc.
+      include_dirs = [ "." ]
+    }
   } else {
+    # GCC or clang:
     cflags = [
       "-isystem",
       rebase_path(".", root_build_dir),
@@ -111,6 +121,7 @@
       cflags_cc = [
         "/wd4005",  # macro-redefinition
         "/wd4068",  # unknown pragma
+        "/wd4702",  # unreachable code
       ]
     }
   }
diff --git a/third_party/blink/public/BUILD.gn b/third_party/blink/public/BUILD.gn
index f2aacce..48c0839 100644
--- a/third_party/blink/public/BUILD.gn
+++ b/third_party/blink/public/BUILD.gn
@@ -51,6 +51,11 @@
 }
 
 config("blink_headers_config") {
+  include_dirs = [
+    "..",
+    "$root_gen_dir/third_party/blink",
+  ]
+
   # Allow :blink_headers to include v8.h without linking to it.
   configs = [ "//v8:external_config" ]
 }
diff --git a/third_party/blink/public/mojom/BUILD.gn b/third_party/blink/public/mojom/BUILD.gn
index 945c74ff..79c32790 100644
--- a/third_party/blink/public/mojom/BUILD.gn
+++ b/third_party/blink/public/mojom/BUILD.gn
@@ -17,6 +17,7 @@
     "blob/serialized_blob.mojom",
     "clipboard/clipboard.mojom",
     "color_chooser/color_chooser.mojom",
+    "cookie_store/cookie_store.mojom",
     "feature_policy/feature_policy.mojom",
     "file/file_utilities.mojom",
     "frame/find_in_page.mojom",
diff --git a/third_party/blink/public/mojom/cookie_store/OWNERS b/third_party/blink/public/mojom/cookie_store/OWNERS
new file mode 100644
index 0000000..ef6dfad9e
--- /dev/null
+++ b/third_party/blink/public/mojom/cookie_store/OWNERS
@@ -0,0 +1,7 @@
+file://content/browser/cookie_store/OWNERS
+
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
+
+# TEAM: storage-dev@chromium.org
+# COMPONENT: Blink>Storage>CookiesAPI
diff --git a/third_party/blink/public/mojom/cookie_store/cookie_store.mojom b/third_party/blink/public/mojom/cookie_store/cookie_store.mojom
new file mode 100644
index 0000000..5e32abe
--- /dev/null
+++ b/third_party/blink/public/mojom/cookie_store/cookie_store.mojom
@@ -0,0 +1,33 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module blink.mojom;
+
+import "services/network/public/mojom/restricted_cookie_manager.mojom";
+import "url/mojom/url.mojom";
+
+struct CookieChangeSubscription {
+  url.mojom.Url url;
+  network.mojom.CookieMatchType match_type;
+  string name;
+};
+
+// Tracks ServiceWorker subscriptions to cookie changes.
+//
+// Most code should access the cookie store via the RestrictedCookieManager
+// interface in //services/network. This interface manages service worker
+// subscriptions to cookie changes, which currently fall outside the network
+// service's scope. The implementation lives in the browser process, because it
+// has the ability to wake up service workers in order to dispatch events to
+// them.
+interface CookieStore {
+  // Adds the given subscriptions to the registration's subscription list.
+  AppendSubscriptions(
+      int64 service_worker_registration_id,
+      array<CookieChangeSubscription> subscriptions) => (bool success);
+
+  // Returns all cookie change subscriptions for a service worker registration.
+  GetSubscriptions(int64 service_worker_registration_id)
+      => (array<CookieChangeSubscription> subscriptions, bool success);
+};
diff --git a/third_party/blink/public/platform/web_media_stream.h b/third_party/blink/public/platform/web_media_stream.h
index 4ab4d49..244875decb 100644
--- a/third_party/blink/public/platform/web_media_stream.h
+++ b/third_party/blink/public/platform/web_media_stream.h
@@ -38,9 +38,12 @@
 class BLINK_PLATFORM_EXPORT WebMediaStreamObserver {
  public:
   // TrackAdded is called when |track| is added to the observed MediaStream.
-  virtual void TrackAdded(const blink::WebMediaStreamTrack&) = 0;
+  virtual void TrackAdded(const blink::WebMediaStreamTrack&) {}
   // TrackRemoved is called when |track| is added to the observed MediaStream.
-  virtual void TrackRemoved(const blink::WebMediaStreamTrack&) = 0;
+  virtual void TrackRemoved(const blink::WebMediaStreamTrack&) {}
+  // ActiveStateChanged is called when the observed MediaStream becomes either
+  // active or inactive.
+  virtual void ActiveStateChanged(bool is_active) {}
 
  protected:
   virtual ~WebMediaStreamObserver() = default;
diff --git a/third_party/blink/public/web/modules/serviceworker/web_service_worker_context_client.h b/third_party/blink/public/web/modules/serviceworker/web_service_worker_context_client.h
index 7e3f00d8..53d6787 100644
--- a/third_party/blink/public/web/modules/serviceworker/web_service_worker_context_client.h
+++ b/third_party/blink/public/web/modules/serviceworker/web_service_worker_context_client.h
@@ -175,6 +175,11 @@
                                                mojom::ServiceWorkerEventStatus,
                                                double event_dispatch_time) {}
 
+  // Called after 'cookiechange' events are handled by the service worker.
+  virtual void DidHandleCookieChangeEvent(int event_id,
+                                          mojom::ServiceWorkerEventStatus,
+                                          double event_dispatch_time) {}
+
   // Called after ExtendableMessageEvent was handled by the service worker.
   virtual void DidHandleExtendableMessageEvent(int event_id,
                                                mojom::ServiceWorkerEventStatus,
diff --git a/third_party/blink/public/web/modules/serviceworker/web_service_worker_context_proxy.h b/third_party/blink/public/web/modules/serviceworker/web_service_worker_context_proxy.h
index 9b93a0cd..70c82df 100644
--- a/third_party/blink/public/web/modules/serviceworker/web_service_worker_context_proxy.h
+++ b/third_party/blink/public/web/modules/serviceworker/web_service_worker_context_proxy.h
@@ -87,6 +87,12 @@
       const WebString& developer_id,
       const WebString& unique_id,
       const WebVector<WebBackgroundFetchSettledFetch>& fetches) = 0;
+  // TODO(pwnall): Use blink::CanonicalCookie, after https://crrev.com/c/991196
+  //               lands.
+  virtual void DispatchCookieChangeEvent(int event_id,
+                                         const WebString& cookie_name,
+                                         const WebString& cookie_value,
+                                         bool is_cookie_delete) = 0;
   virtual void DispatchExtendableMessageEvent(
       int event_id,
       TransferableMessage,
diff --git a/third_party/blink/renderer/BUILD.gn b/third_party/blink/renderer/BUILD.gn
index 2b61563..4b32bb2 100644
--- a/third_party/blink/renderer/BUILD.gn
+++ b/third_party/blink/renderer/BUILD.gn
@@ -77,6 +77,13 @@
 # config -----------------------------------------------------------------------
 
 config("config") {
+  include_dirs = [
+    ".",
+    "..",
+    "$root_gen_dir/third_party/blink/renderer",
+    "$root_gen_dir/third_party/blink",
+  ]
+
   cflags = []
   defines = []
 
diff --git a/third_party/blink/renderer/bindings/core/v8/BUILD.gn b/third_party/blink/renderer/bindings/core/v8/BUILD.gn
index 448e39f48..d70c1ed8 100644
--- a/third_party/blink/renderer/bindings/core/v8/BUILD.gn
+++ b/third_party/blink/renderer/bindings/core/v8/BUILD.gn
@@ -169,7 +169,7 @@
   # link.exe. But link.exe cannot make binary smaller than its size limit from
   # many obj files for bindings_core_impl.lib.
   bindings_core_generated_interface_files =
-      [ "$bindings_core_v8_output_dir/V8GeneratedCoreBindings.cpp" ]
+      [ "$bindings_core_v8_output_dir/v8_generated_core_bindings.cc" ]
 } else {
   bindings_core_generated_interface_files =
       process_file_template(
@@ -204,7 +204,7 @@
 aggregate_generated_bindings("generate_bindings_core_v8_all_interfaces") {
   sources = core_definition_idl_files
   outputs = [
-    "$bindings_core_v8_output_dir/V8GeneratedCoreBindings.cpp",
+    "$bindings_core_v8_output_dir/v8_generated_core_bindings.cc",
   ]
   component = "core"
   public_deps = [
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_gc_controller.cc b/third_party/blink/renderer/bindings/core/v8/v8_gc_controller.cc
index 5cdb87f..44f53ee 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_gc_controller.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_gc_controller.cc
@@ -158,9 +158,6 @@
   v8::HandleScope scope(isolate);
   switch (type) {
     case v8::kGCTypeScavenge:
-      if (ThreadState::Current())
-        ThreadState::Current()->WillStartV8GC(BlinkGC::kV8MinorGC);
-
       TRACE_EVENT_BEGIN1("devtools.timeline,v8", "MinorGC",
                          "usedHeapSizeBefore", UsedHeapSize(isolate));
       VisitWeakHandlesForMinorGC(isolate);
@@ -211,10 +208,6 @@
     case v8::kGCTypeScavenge:
       TRACE_EVENT_END1("devtools.timeline,v8", "MinorGC", "usedHeapSizeAfter",
                        UsedHeapSize(isolate));
-      // TODO(haraken): Remove this. See the comment in gcPrologue.
-      if (ThreadState::Current())
-        ThreadState::Current()->ScheduleV8FollowupGCIfNeeded(
-            BlinkGC::kV8MinorGC);
       break;
     case v8::kGCTypeMarkSweepCompact:
       TRACE_EVENT_END1("devtools.timeline,v8", "MajorGC", "usedHeapSizeAfter",
diff --git a/third_party/blink/renderer/bindings/modules/BUILD.gn b/third_party/blink/renderer/bindings/modules/BUILD.gn
index 443c728..d851317 100644
--- a/third_party/blink/renderer/bindings/modules/BUILD.gn
+++ b/third_party/blink/renderer/bindings/modules/BUILD.gn
@@ -25,6 +25,7 @@
     "//third_party/blink/renderer/modules/background_fetch/background_fetched_event.idl",
     "//third_party/blink/renderer/modules/background_sync/sync_event.idl",
     "//third_party/blink/renderer/modules/cookie_store/cookie_change_event.idl",
+    "//third_party/blink/renderer/modules/cookie_store/extendable_cookie_change_event.idl",
     "//third_party/blink/renderer/modules/device_orientation/device_motion_event.idl",
     "//third_party/blink/renderer/modules/device_orientation/device_orientation_event.idl",
     "//third_party/blink/renderer/modules/encryptedmedia/media_encrypted_event.idl",
diff --git a/third_party/blink/renderer/bindings/modules/v8/generated.gni b/third_party/blink/renderer/bindings/modules/v8/generated.gni
index e80ffce0..271c55a 100644
--- a/third_party/blink/renderer/bindings/modules/v8/generated.gni
+++ b/third_party/blink/renderer/bindings/modules/v8/generated.gni
@@ -10,7 +10,7 @@
 bindings_modules_v8_output_dir = "$bindings_output_dir/modules/v8"
 
 bindings_modules_generated_init_partial_interfaces_file =
-    "$bindings_modules_v8_output_dir/initPartialInterfacesInModules.cpp"
+    "$bindings_modules_v8_output_dir/init_partial_interfaces_in_modules.cc"
 
 # TODO(bashi): It would be better to have a way to update this list automatically.
 bindings_modules_generated_union_type_files = [
@@ -120,4 +120,4 @@
 ]
 
 bindings_generated_v8_context_snapshot_external_references_file =
-    "$bindings_modules_v8_output_dir/V8ContextSnapshotExternalReferences.cpp"
+    "$bindings_modules_v8_output_dir/v8_context_snapshot_external_references.cc"
diff --git a/third_party/blink/renderer/bindings/modules/v8/v8_context_snapshot_external_references.h b/third_party/blink/renderer/bindings/modules/v8/v8_context_snapshot_external_references.h
index 30dcbc8..5dd7b09a 100644
--- a/third_party/blink/renderer/bindings/modules/v8/v8_context_snapshot_external_references.h
+++ b/third_party/blink/renderer/bindings/modules/v8/v8_context_snapshot_external_references.h
@@ -20,7 +20,7 @@
 
  public:
   // The definition of this method is auto-generated in
-  // V8ContextSnapshotExternalReferences.cpp.
+  // v8_context_snapshot_external_references.cc.
   static const intptr_t* GetTable();
 };
 
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index ae1b1fa..13eb3dc6 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -41,7 +41,10 @@
 }
 
 config("core_include_dirs") {
-  include_dirs = []
+  include_dirs = [
+    "..",
+    "$root_gen_dir/third_party/blink/renderer",
+  ]
   if (is_android && use_openmax_dl_fft) {
     include_dirs += [ "//third_party/openmax_dl" ]
   }
@@ -1577,6 +1580,7 @@
     ":core_include_dirs",
     "//tools/v8_context_snapshot:use_v8_context_snapshot",
   ]
+  include_dirs = [ "$root_gen_dir/third_party/blink/renderer" ]
 
   cflags = []
   defines = []
diff --git a/third_party/blink/renderer/core/css/CSSProperties.json5 b/third_party/blink/renderer/core/css/CSSProperties.json5
index 4871182..f53c8dfb 100644
--- a/third_party/blink/renderer/core/css/CSSProperties.json5
+++ b/third_party/blink/renderer/core/css/CSSProperties.json5
@@ -741,7 +741,6 @@
       field_template: "primitive",
       default_value: "1.0",
       type_name: "float",
-      computed_style_custom_functions: ["setter"],
       custom_apply_functions_all: true,
       priority: "High",
     },
diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h
index 7aec7b3..9dab6e1 100644
--- a/third_party/blink/renderer/core/dom/element.h
+++ b/third_party/blink/renderer/core/dom/element.h
@@ -966,7 +966,6 @@
   scoped_refptr<ComputedStyle> PropagateInheritedProperties(StyleRecalcChange);
 
   StyleRecalcChange RecalcOwnStyle(StyleRecalcChange);
-  void RecalcOwnStyleForReattach();
   void RecalcShadowIncludingDescendantStylesForReattach();
   void RecalcShadowRootStylesForReattach();
 
diff --git a/third_party/blink/renderer/core/events/event_type_names.json5 b/third_party/blink/renderer/core/events/event_type_names.json5
index dd857e3e..50a3684f 100644
--- a/third_party/blink/renderer/core/events/event_type_names.json5
+++ b/third_party/blink/renderer/core/events/event_type_names.json5
@@ -77,6 +77,7 @@
     "contextmenu",
     "contextrestored",
     "controllerchange",
+    "cookiechange",
     "copy",
     "crossoriginmessage",
     "cuechange",
diff --git a/third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.cc b/third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.cc
index 785db8e8..628c385 100644
--- a/third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.cc
+++ b/third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.cc
@@ -109,6 +109,11 @@
   ToHTMLElement(node)->setTextContent(display_value);
 }
 
+void ChooserOnlyTemporalInputTypeView::ValueAttributeChanged() {
+  if (!GetElement().HasDirtyValue())
+    UpdateView();
+}
+
 void ChooserOnlyTemporalInputTypeView::DidSetValue(const String& value,
                                                    bool value_changed) {
   if (value_changed)
diff --git a/third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.h b/third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.h
index 9dbfb0a..a69e2d8 100644
--- a/third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.h
+++ b/third_party/blink/renderer/core/html/forms/chooser_only_temporal_input_type_view.h
@@ -54,6 +54,7 @@
   // InputTypeView functions:
   void CreateShadowSubtree() override;
   void ClosePopupView() override;
+  void ValueAttributeChanged() override;
   void DidSetValue(const String&, bool value_changed) override;
   void HandleDOMActivateEvent(Event*) override;
   void UpdateView() override;
diff --git a/third_party/blink/renderer/core/style/computed_style.h b/third_party/blink/renderer/core/style/computed_style.h
index c32ad2d..8f37a92 100644
--- a/third_party/blink/renderer/core/style/computed_style.h
+++ b/third_party/blink/renderer/core/style/computed_style.h
@@ -795,8 +795,6 @@
     SetZIndexInternal(0);
   }
 
-  // zoom
-  bool SetZoom(float);
   bool SetEffectiveZoom(float);
 
   // -webkit-clip-path
@@ -2543,13 +2541,6 @@
       UpdatePropertySpecificDifferencesCompositingReasonsUsedStylePreserve3D);
 };
 
-inline bool ComputedStyle::SetZoom(float f) {
-  if (Zoom() == f)
-    return false;
-  SetZoomInternal(f);
-  return true;
-}
-
 inline bool ComputedStyle::SetEffectiveZoom(float f) {
   // Clamp the effective zoom value to a smaller (but hopeful still large
   // enough) range, to avoid overflow in derived computations.
diff --git a/third_party/blink/renderer/modules/cookie_store/BUILD.gn b/third_party/blink/renderer/modules/cookie_store/BUILD.gn
index 49ff14f..378a0479 100644
--- a/third_party/blink/renderer/modules/cookie_store/BUILD.gn
+++ b/third_party/blink/renderer/modules/cookie_store/BUILD.gn
@@ -10,6 +10,8 @@
     "cookie_change_event.h",
     "cookie_store.cc",
     "cookie_store.h",
+    "extendable_cookie_change_event.cc",
+    "extendable_cookie_change_event.h",
     "global_cookie_store.cc",
     "global_cookie_store.h",
   ]
diff --git a/third_party/blink/renderer/modules/cookie_store/OWNERS b/third_party/blink/renderer/modules/cookie_store/OWNERS
index 6b2cb0f..b23c10fd 100644
--- a/third_party/blink/renderer/modules/cookie_store/OWNERS
+++ b/third_party/blink/renderer/modules/cookie_store/OWNERS
@@ -1,8 +1,8 @@
 # Primary
 pwnall@chromium.org
 
-# Seconday
+# Secondary
 jsbell@chromium.org
 
 # TEAM: storage-dev@chromium.org
-# COMPONENT: Blink>Storage
+# COMPONENT: Blink>Storage>CookiesAPI
diff --git a/third_party/blink/renderer/modules/cookie_store/cookie_change_event.h b/third_party/blink/renderer/modules/cookie_store/cookie_change_event.h
index 7fdf59b..97842405 100644
--- a/third_party/blink/renderer/modules/cookie_store/cookie_change_event.h
+++ b/third_party/blink/renderer/modules/cookie_store/cookie_change_event.h
@@ -8,6 +8,7 @@
 #include "third_party/blink/renderer/modules/cookie_store/cookie_list_item.h"
 #include "third_party/blink/renderer/modules/event_modules.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
@@ -22,7 +23,7 @@
 
   // Used by Blink.
   //
-  // The caller is expected to create a HeapVector and std::move() it into this
+  // The caller is expected to create HeapVectors and std::move() them into this
   // method.
   static CookieChangeEvent* Create(const AtomicString& type,
                                    HeapVector<CookieListItem> changed,
diff --git a/third_party/blink/renderer/modules/cookie_store/cookie_change_event.idl b/third_party/blink/renderer/modules/cookie_store/cookie_change_event.idl
index b9a93ed7..629a4a6 100644
--- a/third_party/blink/renderer/modules/cookie_store/cookie_change_event.idl
+++ b/third_party/blink/renderer/modules/cookie_store/cookie_change_event.idl
@@ -2,10 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// Used to signal cookie changes to Document contexts.
 // https://github.com/WICG/async-cookies-api/blob/gh-pages/explainer.md
+//
+// See extendable_cookie_change_event.idl for the equivalent event in
+// ServiceWorker contexts.
 
 [
-  Exposed=(ServiceWorker,Window),
+  Exposed=Window,
   RuntimeEnabled=AsyncCookies,
   Constructor(DOMString type, optional CookieChangeEventInit eventInitDict)
 ] interface CookieChangeEvent : Event {
diff --git a/third_party/blink/renderer/modules/cookie_store/cookie_store.cc b/third_party/blink/renderer/modules/cookie_store/cookie_store.cc
index 341e662..e54686eb5 100644
--- a/third_party/blink/renderer/modules/cookie_store/cookie_store.cc
+++ b/third_party/blink/renderer/modules/cookie_store/cookie_store.cc
@@ -19,6 +19,7 @@
 #include "third_party/blink/renderer/modules/event_modules.h"
 #include "third_party/blink/renderer/modules/event_target_modules.h"
 #include "third_party/blink/renderer/modules/serviceworkers/service_worker_global_scope.h"
+#include "third_party/blink/renderer/modules/serviceworkers/service_worker_registration.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
@@ -178,6 +179,44 @@
   return canonical_cookie;
 }
 
+// Returns null if and only if an exception is thrown.
+blink::mojom::blink::CookieChangeSubscriptionPtr ToBackendSubscription(
+    const KURL& default_cookie_url,
+    const CookieStoreGetOptions& subscription,
+    ExceptionState& exception_state) {
+  auto backend_subscription =
+      blink::mojom::blink::CookieChangeSubscription::New();
+
+  if (subscription.hasURL()) {
+    KURL subscription_url(default_cookie_url, subscription.url());
+    // TODO(crbug.com/729800): Check that the URL is under default_cookie_url.
+    backend_subscription->url = subscription_url;
+  } else {
+    backend_subscription->url = default_cookie_url;
+  }
+
+  if (subscription.matchType() == "startsWith") {
+    backend_subscription->match_type =
+        network::mojom::blink::CookieMatchType::STARTS_WITH;
+  } else {
+    DCHECK_EQ(subscription.matchType(), WTF::String("equals"));
+    backend_subscription->match_type =
+        network::mojom::blink::CookieMatchType::EQUALS;
+  }
+
+  if (subscription.hasName()) {
+    backend_subscription->name = subscription.name();
+  } else {
+    // No name provided. Use a filter that matches all cookies. This overrides
+    // a user-provided matchType.
+    backend_subscription->match_type =
+        network::mojom::blink::CookieMatchType::STARTS_WITH;
+    backend_subscription->name = g_empty_string;
+  }
+
+  return backend_subscription;
+}
+
 void ToCookieListItem(
     const network::mojom::blink::CanonicalCookiePtr& canonical_cookie,
     bool is_deleted,  // True for the information from a cookie deletion event.
@@ -187,6 +226,29 @@
     cookie.setValue(canonical_cookie->value);
 }
 
+void ToCookieChangeSubscription(
+    const blink::mojom::blink::CookieChangeSubscription& backend_subscription,
+    CookieStoreGetOptions& subscription) {
+  subscription.setURL(backend_subscription.url);
+
+  if (backend_subscription.match_type !=
+          network::mojom::blink::CookieMatchType::STARTS_WITH ||
+      !backend_subscription.name.IsEmpty()) {
+    subscription.setName(backend_subscription.name);
+  }
+
+  switch (backend_subscription.match_type) {
+    case network::mojom::blink::CookieMatchType::STARTS_WITH:
+      subscription.setMatchType(WTF::String("startsWith"));
+      break;
+    case network::mojom::blink::CookieMatchType::EQUALS:
+      subscription.setMatchType(WTF::String("equals"));
+      break;
+  }
+
+  subscription.setURL(backend_subscription.url);
+}
+
 const KURL& DefaultCookieURL(ExecutionContext* execution_context) {
   DCHECK(execution_context);
 
@@ -201,7 +263,7 @@
   return scope->Url();
 }
 
-const KURL DefaultSiteForCookies(ExecutionContext* execution_context) {
+KURL DefaultSiteForCookies(ExecutionContext* execution_context) {
   DCHECK(execution_context);
 
   if (execution_context->IsDocument()) {
@@ -215,7 +277,7 @@
   return scope->Url();
 }
 
-}  // anonymous namespace
+}  // namespace
 
 CookieStore::~CookieStore() = default;
 
@@ -291,6 +353,71 @@
                  true /* is_deletion */, exception_state);
 }
 
+ScriptPromise CookieStore::subscribeToChanges(
+    ScriptState* script_state,
+    const HeapVector<CookieStoreGetOptions>& subscriptions,
+    ExceptionState& exception_state) {
+  DCHECK(GetExecutionContext()->IsServiceWorkerGlobalScope());
+
+  Vector<blink::mojom::blink::CookieChangeSubscriptionPtr>
+      backend_subscriptions;
+  backend_subscriptions.ReserveInitialCapacity(subscriptions.size());
+  for (const CookieStoreGetOptions& subscription : subscriptions) {
+    blink::mojom::blink::CookieChangeSubscriptionPtr backend_subscription =
+        ToBackendSubscription(default_cookie_url_, subscription,
+                              exception_state);
+    if (backend_subscription.is_null())
+      return ScriptPromise();  // ToBackendSubscription has thrown an exception.
+    backend_subscriptions.emplace_back(std::move(backend_subscription));
+  }
+
+  if (!subscription_backend_) {
+    exception_state.ThrowDOMException(kInvalidStateError,
+                                      "CookieStore backend went away");
+    return ScriptPromise();
+  }
+
+  ServiceWorkerGlobalScope* scope =
+      ToServiceWorkerGlobalScope(GetExecutionContext());
+
+  if (!scope->IsInstalling()) {
+    exception_state.ThrowTypeError("Outside the installation phase");
+    return ScriptPromise();
+  }
+
+  ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
+  int64_t service_worker_registration_id =
+      scope->registration()->WebRegistration()->RegistrationId();
+  subscription_backend_->AppendSubscriptions(
+      service_worker_registration_id, std::move(backend_subscriptions),
+      WTF::Bind(&CookieStore::OnSubscribeToCookieChangesResult,
+                WrapPersistent(resolver)));
+  return resolver->Promise();
+}
+
+ScriptPromise CookieStore::getChangeSubscriptions(
+    ScriptState* script_state,
+    ExceptionState& exception_state) {
+  DCHECK(GetExecutionContext()->IsServiceWorkerGlobalScope());
+
+  if (!subscription_backend_) {
+    exception_state.ThrowDOMException(kInvalidStateError,
+                                      "CookieStore backend went away");
+    return ScriptPromise();
+  }
+
+  ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
+  ServiceWorkerGlobalScope* scope =
+      ToServiceWorkerGlobalScope(GetExecutionContext());
+  int64_t service_worker_registration_id =
+      scope->registration()->WebRegistration()->RegistrationId();
+  subscription_backend_->GetSubscriptions(
+      service_worker_registration_id,
+      WTF::Bind(&CookieStore::OnGetCookieChangeSubscriptionResult,
+                WrapPersistent(resolver)));
+  return resolver->Promise();
+}
+
 void CookieStore::ContextDestroyed(ExecutionContext* execution_context) {
   StopObserving();
   backend_.reset();
@@ -365,9 +492,11 @@
 
 CookieStore::CookieStore(
     ExecutionContext* execution_context,
-    network::mojom::blink::RestrictedCookieManagerPtr backend)
+    network::mojom::blink::RestrictedCookieManagerPtr backend,
+    blink::mojom::blink::CookieStorePtr subscription_backend)
     : ContextLifecycleObserver(execution_context),
       backend_(std::move(backend)),
+      subscription_backend_(std::move(subscription_backend)),
       change_listener_binding_(this),
       default_cookie_url_(DefaultCookieURL(execution_context)),
       default_site_for_cookies_(DefaultSiteForCookies(execution_context)) {
@@ -489,6 +618,49 @@
   resolver->Resolve();
 }
 
+// static
+void CookieStore::OnSubscribeToCookieChangesResult(
+    ScriptPromiseResolver* resolver,
+    bool backend_success) {
+  ScriptState* script_state = resolver->GetScriptState();
+  if (!script_state->ContextIsValid())
+    return;
+
+  if (!backend_success) {
+    resolver->Reject(DOMException::Create(
+        kUnknownError,
+        "An unknown error occured while subscribing to cookie changes."));
+    return;
+  }
+  resolver->Resolve();
+}
+
+// static
+void CookieStore::OnGetCookieChangeSubscriptionResult(
+    ScriptPromiseResolver* resolver,
+    Vector<blink::mojom::blink::CookieChangeSubscriptionPtr> backend_result,
+    bool backend_success) {
+  ScriptState* script_state = resolver->GetScriptState();
+  if (!script_state->ContextIsValid())
+    return;
+
+  if (!backend_success) {
+    resolver->Reject(DOMException::Create(
+        kUnknownError,
+        "An unknown error occured while reading cookie change subscriptions."));
+    return;
+  }
+
+  HeapVector<CookieStoreGetOptions> subscriptions;
+  subscriptions.ReserveInitialCapacity(backend_result.size());
+  for (const auto& backend_subscription : backend_result) {
+    CookieStoreGetOptions& subscription = subscriptions.emplace_back();
+    ToCookieChangeSubscription(*backend_subscription, subscription);
+  }
+
+  resolver->Resolve(std::move(subscriptions));
+}
+
 void CookieStore::StartObserving() {
   if (change_listener_binding_ || !backend_)
     return;
diff --git a/third_party/blink/renderer/modules/cookie_store/cookie_store.h b/third_party/blink/renderer/modules/cookie_store/cookie_store.h
index bc0ce11..8a9bd420 100644
--- a/third_party/blink/renderer/modules/cookie_store/cookie_store.h
+++ b/third_party/blink/renderer/modules/cookie_store/cookie_store.h
@@ -7,6 +7,7 @@
 
 #include "mojo/public/cpp/bindings/binding.h"
 #include "services/network/public/mojom/restricted_cookie_manager.mojom-blink.h"
+#include "third_party/blink/public/mojom/cookie_store/cookie_store.mojom-blink.h"
 #include "third_party/blink/renderer/bindings/core/v8/exception_state.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/core/dom/context_lifecycle_observer.h"
@@ -35,8 +36,10 @@
 
   static CookieStore* Create(
       ExecutionContext* execution_context,
-      network::mojom::blink::RestrictedCookieManagerPtr backend) {
-    return new CookieStore(execution_context, std::move(backend));
+      network::mojom::blink::RestrictedCookieManagerPtr backend,
+      blink::mojom::blink::CookieStorePtr subscription_backend) {
+    return new CookieStore(execution_context, std::move(backend),
+                           std::move(subscription_backend));
   }
 
   ScriptPromise getAll(ScriptState*,
@@ -76,6 +79,11 @@
                        const String& name,
                        const CookieStoreSetOptions&,
                        ExceptionState&);
+  ScriptPromise subscribeToChanges(
+      ScriptState*,
+      const HeapVector<CookieStoreGetOptions>& subscriptions,
+      ExceptionState&);
+  ScriptPromise getChangeSubscriptions(ScriptState*, ExceptionState&);
 
   // GarbageCollected
   void Trace(blink::Visitor* visitor) override {
@@ -109,7 +117,8 @@
                Vector<network::mojom::blink::CanonicalCookiePtr>);
 
   CookieStore(ExecutionContext*,
-              network::mojom::blink::RestrictedCookieManagerPtr backend);
+              network::mojom::blink::RestrictedCookieManagerPtr backend,
+              blink::mojom::blink::CookieStorePtr subscription_backend);
 
   // Common code in CookieStore::{get,getAll,has}.
   //
@@ -152,6 +161,13 @@
   static void OnSetCanonicalCookieResult(ScriptPromiseResolver*,
                                          bool backend_result);
 
+  static void OnSubscribeToCookieChangesResult(ScriptPromiseResolver*,
+                                               bool backend_result);
+  static void OnGetCookieChangeSubscriptionResult(
+      ScriptPromiseResolver*,
+      Vector<blink::mojom::blink::CookieChangeSubscriptionPtr> backend_result,
+      bool backend_success);
+
   // Called when a change event listener is added.
   //
   // This is idempotent during the time intervals between StopObserving() calls.
@@ -163,6 +179,12 @@
   // Wraps an always-on Mojo pipe for sending requests to the Network Service.
   network::mojom::blink::RestrictedCookieManagerPtr backend_;
 
+  // Wraps a Mojo pipe for managing service worker cookie change subscriptions.
+  //
+  // This pipe is always connected in service worker execution contexts, and
+  // never connected in document contexts.
+  blink::mojom::blink::CookieStorePtr subscription_backend_;
+
   // Wraps a Mojo pipe used to receive cookie change notifications.
   //
   // This binding is set up on-demand, when the cookie store has at least one
diff --git a/third_party/blink/renderer/modules/cookie_store/cookie_store.idl b/third_party/blink/renderer/modules/cookie_store/cookie_store.idl
index 0c633bc..b6692ec 100644
--- a/third_party/blink/renderer/modules/cookie_store/cookie_store.idl
+++ b/third_party/blink/renderer/modules/cookie_store/cookie_store.idl
@@ -33,5 +33,12 @@
   [CallWith=ScriptState, ImplementedAs=Delete, RaisesException] Promise<void> delete(
       CookieStoreSetOptions options);
 
-  attribute EventHandler onchange;
+  [Exposed=ServiceWorker, CallWith=ScriptState, RaisesException]
+  Promise<void> subscribeToChanges(
+      sequence<CookieStoreGetOptions> subscriptions);
+
+  [Exposed=ServiceWorker, CallWith=ScriptState, RaisesException]
+  Promise<sequence<CookieStoreGetOptions>> getChangeSubscriptions();
+
+  [Exposed=Window] attribute EventHandler onchange;
 };
diff --git a/third_party/blink/renderer/modules/cookie_store/extendable_cookie_change_event.cc b/third_party/blink/renderer/modules/cookie_store/extendable_cookie_change_event.cc
new file mode 100644
index 0000000..6de8c05
--- /dev/null
+++ b/third_party/blink/renderer/modules/cookie_store/extendable_cookie_change_event.cc
@@ -0,0 +1,65 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/modules/cookie_store/extendable_cookie_change_event.h"
+
+#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/renderer/modules/cookie_store/cookie_list_item.h"
+#include "third_party/blink/renderer/modules/cookie_store/extendable_cookie_change_event_init.h"
+#include "third_party/blink/renderer/modules/event_modules.h"
+#include "third_party/blink/renderer/modules/serviceworkers/extendable_event_init.h"
+#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+ExtendableCookieChangeEvent::~ExtendableCookieChangeEvent() = default;
+
+const AtomicString& ExtendableCookieChangeEvent::InterfaceName() const {
+  return EventNames::ExtendableCookieChangeEvent;
+}
+
+void ExtendableCookieChangeEvent::Trace(blink::Visitor* visitor) {
+  ExtendableEvent::Trace(visitor);
+  visitor->Trace(changed_);
+  visitor->Trace(deleted_);
+}
+
+ExtendableCookieChangeEvent::ExtendableCookieChangeEvent(
+    const AtomicString& type,
+    HeapVector<CookieListItem> changed,
+    HeapVector<CookieListItem> deleted,
+    WaitUntilObserver* wait_until_observer)
+    : ExtendableEvent(type, ExtendableEventInit(), wait_until_observer),
+      changed_(std::move(changed)),
+      deleted_(std::move(deleted)) {}
+
+ExtendableCookieChangeEvent::ExtendableCookieChangeEvent(
+    const AtomicString& type,
+    const ExtendableCookieChangeEventInit& initializer)
+    : ExtendableEvent(type, initializer) {
+  if (initializer.hasChanged())
+    changed_ = initializer.changed();
+  if (initializer.hasDeleted())
+    deleted_ = initializer.deleted();
+}
+
+// static
+void ExtendableCookieChangeEvent::ToCookieChangeListItem(
+    const WebString& cookie_name,
+    const WebString& cookie_value,
+    bool is_cookie_delete,
+    HeapVector<CookieListItem>& changed,
+    HeapVector<CookieListItem>& deleted) {
+  if (is_cookie_delete) {
+    CookieListItem& cookie = deleted.emplace_back();
+    cookie.setName(cookie_name);
+  } else {
+    CookieListItem& cookie = changed.emplace_back();
+    cookie.setName(cookie_name);
+    cookie.setValue(cookie_value);
+  }
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/cookie_store/extendable_cookie_change_event.h b/third_party/blink/renderer/modules/cookie_store/extendable_cookie_change_event.h
new file mode 100644
index 0000000..af810bf
--- /dev/null
+++ b/third_party/blink/renderer/modules/cookie_store/extendable_cookie_change_event.h
@@ -0,0 +1,81 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_COOKIE_STORE_EXTENDABLE_COOKIE_CHANGE_EVENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_COOKIE_STORE_EXTENDABLE_COOKIE_CHANGE_EVENT_H_
+
+#include "third_party/blink/renderer/modules/cookie_store/cookie_list_item.h"
+#include "third_party/blink/renderer/modules/event_modules.h"
+#include "third_party/blink/renderer/modules/serviceworkers/extendable_event.h"
+#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+class ExtendableCookieChangeEventInit;
+class WaitUntilObserver;
+class WebString;
+
+class ExtendableCookieChangeEvent final : public ExtendableEvent {
+  DEFINE_WRAPPERTYPEINFO();
+
+ public:
+  // Used by Blink.
+  //
+  // The caller is expected to create HeapVectors and std::move() them into this
+  // method.
+  static ExtendableCookieChangeEvent* Create(
+      const AtomicString& type,
+      HeapVector<CookieListItem> changed,
+      HeapVector<CookieListItem> deleted,
+      WaitUntilObserver* wait_until_observer) {
+    return new ExtendableCookieChangeEvent(
+        type, std::move(changed), std::move(deleted), wait_until_observer);
+  }
+
+  // Used by JavaScript, via the V8 bindings.
+  static ExtendableCookieChangeEvent* Create(
+      const AtomicString& type,
+      const ExtendableCookieChangeEventInit& initializer) {
+    return new ExtendableCookieChangeEvent(type, initializer);
+  }
+
+  ~ExtendableCookieChangeEvent() override;
+
+  const HeapVector<CookieListItem>& changed() const { return changed_; }
+  const HeapVector<CookieListItem>& deleted() const { return deleted_; }
+
+  // Event
+  const AtomicString& InterfaceName() const override;
+
+  // GarbageCollected
+  void Trace(blink::Visitor*) override;
+
+  // Helper for converting backend event information into a CookieChangeEvent.
+  //
+  // TODO(pwnall): Switch to blink::CanonicalCookie when
+  //               https://crrev.com/c/991196 lands.
+  static void ToCookieChangeListItem(const WebString& cookie_name,
+                                     const WebString& cookie_value,
+                                     bool is_cookie_delete,
+                                     HeapVector<CookieListItem>& changed,
+                                     HeapVector<CookieListItem>& deleted);
+
+ private:
+  ExtendableCookieChangeEvent(const AtomicString& type,
+                              HeapVector<CookieListItem> changed,
+                              HeapVector<CookieListItem> deleted,
+                              WaitUntilObserver*);
+  ExtendableCookieChangeEvent(
+      const AtomicString& type,
+      const ExtendableCookieChangeEventInit& initializer);
+
+  HeapVector<CookieListItem> changed_;
+  HeapVector<CookieListItem> deleted_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_COOKIE_STORE_EXTENDABLE_COOKIE_CHANGE_EVENT_H_
diff --git a/third_party/blink/renderer/modules/cookie_store/extendable_cookie_change_event.idl b/third_party/blink/renderer/modules/cookie_store/extendable_cookie_change_event.idl
new file mode 100644
index 0000000..1901997
--- /dev/null
+++ b/third_party/blink/renderer/modules/cookie_store/extendable_cookie_change_event.idl
@@ -0,0 +1,17 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Used to signal cookie changes to ServiceWorker contexts.
+// https://github.com/WICG/async-cookies-api/blob/gh-pages/explainer.md
+//
+// See cookie_change_event.idl for the equivalent event in Document contexts.
+
+[
+  Exposed=ServiceWorker,
+  RuntimeEnabled=AsyncCookies,
+  Constructor(DOMString type, optional ExtendableCookieChangeEventInit eventInitDict)
+] interface ExtendableCookieChangeEvent : ExtendableEvent {
+  readonly attribute CookieList changed;
+  readonly attribute CookieList deleted;
+};
diff --git a/third_party/blink/renderer/modules/cookie_store/extendable_cookie_change_event_init.idl b/third_party/blink/renderer/modules/cookie_store/extendable_cookie_change_event_init.idl
new file mode 100644
index 0000000..27cab8c75
--- /dev/null
+++ b/third_party/blink/renderer/modules/cookie_store/extendable_cookie_change_event_init.idl
@@ -0,0 +1,10 @@
+// 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.
+
+// https://github.com/WICG/async-cookies-api/blob/gh-pages/explainer.md
+
+dictionary ExtendableCookieChangeEventInit : ExtendableEventInit {
+  CookieList changed;
+  CookieList deleted;
+};
diff --git a/third_party/blink/renderer/modules/cookie_store/global_cookie_store.cc b/third_party/blink/renderer/modules/cookie_store/global_cookie_store.cc
index 4e5316e..816e45a 100644
--- a/third_party/blink/renderer/modules/cookie_store/global_cookie_store.cc
+++ b/third_party/blink/renderer/modules/cookie_store/global_cookie_store.cc
@@ -43,18 +43,18 @@
     if (!cookie_store_) {
       ExecutionContext* execution_context = scope.GetExecutionContext();
 
-      network::mojom::blink::RestrictedCookieManagerPtr cookie_manager_ptr;
       service_manager::InterfaceProvider* interface_provider =
           execution_context->GetInterfaceProvider();
       if (!interface_provider)
         return nullptr;
-      interface_provider->GetInterface(mojo::MakeRequest(&cookie_manager_ptr));
-      cookie_store_ =
-          CookieStore::Create(execution_context, std::move(cookie_manager_ptr));
+      cookie_store_ = BuildCookieStore(execution_context, interface_provider);
     }
     return cookie_store_;
   }
 
+  CookieStore* BuildCookieStore(ExecutionContext*,
+                                service_manager::InterfaceProvider*);
+
   void Trace(blink::Visitor* visitor) override {
     visitor->Trace(cookie_store_);
     Supplement<T>::Trace(visitor);
@@ -67,6 +67,31 @@
   Member<CookieStore> cookie_store_;
 };
 
+template <>
+CookieStore* GlobalCookieStoreImpl<LocalDOMWindow>::BuildCookieStore(
+    ExecutionContext* execution_context,
+    service_manager::InterfaceProvider* interface_provider) {
+  network::mojom::blink::RestrictedCookieManagerPtr cookie_manager_ptr;
+  interface_provider->GetInterface(mojo::MakeRequest(&cookie_manager_ptr));
+
+  return CookieStore::Create(execution_context, std::move(cookie_manager_ptr),
+                             blink::mojom::blink::CookieStorePtr());
+}
+
+template <>
+CookieStore* GlobalCookieStoreImpl<WorkerGlobalScope>::BuildCookieStore(
+    ExecutionContext* execution_context,
+    service_manager::InterfaceProvider* interface_provider) {
+  network::mojom::blink::RestrictedCookieManagerPtr cookie_manager_ptr;
+  interface_provider->GetInterface(mojo::MakeRequest(&cookie_manager_ptr));
+
+  blink::mojom::blink::CookieStorePtr cookie_store_ptr;
+  interface_provider->GetInterface(mojo::MakeRequest(&cookie_store_ptr));
+
+  return CookieStore::Create(execution_context, std::move(cookie_manager_ptr),
+                             std::move(cookie_store_ptr));
+}
+
 // static
 template <typename T>
 const char GlobalCookieStoreImpl<T>::kSupplementName[] =
diff --git a/third_party/blink/renderer/modules/cookie_store/service_worker_global_scope_cookie_store.idl b/third_party/blink/renderer/modules/cookie_store/service_worker_global_scope_cookie_store.idl
index eebb8b7..118b777 100644
--- a/third_party/blink/renderer/modules/cookie_store/service_worker_global_scope_cookie_store.idl
+++ b/third_party/blink/renderer/modules/cookie_store/service_worker_global_scope_cookie_store.idl
@@ -8,5 +8,5 @@
     RuntimeEnabled=AsyncCookies,
     ImplementedAs=GlobalCookieStore
 ] partial interface ServiceWorkerGlobalScope {
-    [Replaceable, SameObject] readonly attribute CookieStore cookieStore;
+  [Replaceable, SameObject] readonly attribute CookieStore cookieStore;
 };
diff --git a/third_party/blink/renderer/modules/exported/BUILD.gn b/third_party/blink/renderer/modules/exported/BUILD.gn
index 686411e..0e7590d5 100644
--- a/third_party/blink/renderer/modules/exported/BUILD.gn
+++ b/third_party/blink/renderer/modules/exported/BUILD.gn
@@ -44,4 +44,6 @@
     "//third_party/blink/renderer:config",
     "//third_party/blink/renderer/core:blink_core_pch",
   ]
+
+  include_dirs = [ "$root_gen_dir/third_party/blink/renderer" ]
 }
diff --git a/third_party/blink/renderer/modules/modules_idl_files.gni b/third_party/blink/renderer/modules/modules_idl_files.gni
index 4c50a588..961d9f0 100644
--- a/third_party/blink/renderer/modules/modules_idl_files.gni
+++ b/third_party/blink/renderer/modules/modules_idl_files.gni
@@ -97,6 +97,7 @@
           "clipboard/clipboard.idl",
           "cookie_store/cookie_change_event.idl",
           "cookie_store/cookie_store.idl",
+          "cookie_store/extendable_cookie_change_event.idl",
           "credentialmanager/authenticator_assertion_response.idl",
           "credentialmanager/authenticator_attestation_response.idl",
           "credentialmanager/authenticator_response.idl",
@@ -472,6 +473,7 @@
           "cookie_store/cookie_list_item.idl",
           "cookie_store/cookie_store_get_options.idl",
           "cookie_store/cookie_store_set_options.idl",
+          "cookie_store/extendable_cookie_change_event_init.idl",
           "credentialmanager/authentication_extensions_client_inputs.idl",
           "credentialmanager/authentication_extensions_client_outputs.idl",
           "credentialmanager/authenticator_selection_criteria.idl",
diff --git a/third_party/blink/renderer/modules/serviceworkers/DEPS b/third_party/blink/renderer/modules/serviceworkers/DEPS
index 41e9842..8c6d4bf9 100644
--- a/third_party/blink/renderer/modules/serviceworkers/DEPS
+++ b/third_party/blink/renderer/modules/serviceworkers/DEPS
@@ -12,6 +12,7 @@
     "service_worker_global_scope_proxy\.cc": [
         "+third_party/blink/renderer/modules/background_fetch",
         "+third_party/blink/renderer/modules/background_sync",
+        "+third_party/blink/renderer/modules/cookie_store",
         "+third_party/blink/renderer/modules/exported",
         "+third_party/blink/renderer/modules/notifications",
         "+third_party/blink/renderer/modules/payments",
diff --git a/third_party/blink/renderer/modules/serviceworkers/service_worker_global_scope_client.cc b/third_party/blink/renderer/modules/serviceworkers/service_worker_global_scope_client.cc
index d22b142..3a6fe4c 100644
--- a/third_party/blink/renderer/modules/serviceworkers/service_worker_global_scope_client.cc
+++ b/third_party/blink/renderer/modules/serviceworkers/service_worker_global_scope_client.cc
@@ -119,6 +119,13 @@
                                           event_dispatch_time);
 }
 
+void ServiceWorkerGlobalScopeClient::DidHandleCookieChangeEvent(
+    int event_id,
+    mojom::ServiceWorkerEventStatus status,
+    double event_dispatch_time) {
+  client_.DidHandleCookieChangeEvent(event_id, status, event_dispatch_time);
+}
+
 void ServiceWorkerGlobalScopeClient::DidHandleExtendableMessageEvent(
     int event_id,
     mojom::ServiceWorkerEventStatus status,
diff --git a/third_party/blink/renderer/modules/serviceworkers/service_worker_global_scope_client.h b/third_party/blink/renderer/modules/serviceworkers/service_worker_global_scope_client.h
index 60364bb..21fbacc 100644
--- a/third_party/blink/renderer/modules/serviceworkers/service_worker_global_scope_client.h
+++ b/third_party/blink/renderer/modules/serviceworkers/service_worker_global_scope_client.h
@@ -95,6 +95,9 @@
   void DidHandleBackgroundFetchedEvent(int event_id,
                                        mojom::ServiceWorkerEventStatus,
                                        double event_dispatch_time);
+  void DidHandleCookieChangeEvent(int event_id,
+                                  mojom::ServiceWorkerEventStatus,
+                                  double event_dispatch_time);
   void DidHandleExtendableMessageEvent(int event_id,
                                        mojom::ServiceWorkerEventStatus,
                                        double event_dispatch_time);
diff --git a/third_party/blink/renderer/modules/serviceworkers/service_worker_global_scope_proxy.cc b/third_party/blink/renderer/modules/serviceworkers/service_worker_global_scope_proxy.cc
index 4522320..91f1dbc 100644
--- a/third_party/blink/renderer/modules/serviceworkers/service_worker_global_scope_proxy.cc
+++ b/third_party/blink/renderer/modules/serviceworkers/service_worker_global_scope_proxy.cc
@@ -61,6 +61,7 @@
 #include "third_party/blink/renderer/modules/background_fetch/background_fetch_settled_event_init.h"
 #include "third_party/blink/renderer/modules/background_fetch/background_fetch_update_event.h"
 #include "third_party/blink/renderer/modules/background_sync/sync_event.h"
+#include "third_party/blink/renderer/modules/cookie_store/extendable_cookie_change_event.h"
 #include "third_party/blink/renderer/modules/exported/web_embedded_worker_impl.h"
 #include "third_party/blink/renderer/modules/notifications/notification.h"
 #include "third_party/blink/renderer/modules/notifications/notification_event.h"
@@ -246,6 +247,30 @@
   WorkerGlobalScope()->DispatchExtendableEvent(event, observer);
 }
 
+void ServiceWorkerGlobalScopeProxy::DispatchCookieChangeEvent(
+    int event_id,
+    const WebString& cookie_name,
+    const WebString& cookie_value,
+    bool is_cookie_delete) {
+  DCHECK(WorkerGlobalScope()->IsContextThread());
+  WaitUntilObserver* observer = WaitUntilObserver::Create(
+      WorkerGlobalScope(), WaitUntilObserver::kCookieChange, event_id);
+
+  HeapVector<CookieListItem> changed;
+  HeapVector<CookieListItem> deleted;
+  ExtendableCookieChangeEvent::ToCookieChangeListItem(
+      cookie_name, cookie_value, is_cookie_delete, changed, deleted);
+  Event* event = ExtendableCookieChangeEvent::Create(
+      EventTypeNames::cookiechange, std::move(changed), std::move(deleted),
+      observer);
+
+  // TODO(pwnall): When switching to blink::CanonicalCookie, handle the case
+  //               when (changed.IsEmpty() && deleted.IsEmpty()).
+
+  // TODO(pwnall): Investigate dispatching this on cookieStore.
+  WorkerGlobalScope()->DispatchExtendableEvent(event, observer);
+}
+
 void ServiceWorkerGlobalScopeProxy::DispatchExtendableMessageEvent(
     int event_id,
     TransferableMessage message,
diff --git a/third_party/blink/renderer/modules/serviceworkers/service_worker_global_scope_proxy.h b/third_party/blink/renderer/modules/serviceworkers/service_worker_global_scope_proxy.h
index 365d5d2..a012b73a 100644
--- a/third_party/blink/renderer/modules/serviceworkers/service_worker_global_scope_proxy.h
+++ b/third_party/blink/renderer/modules/serviceworkers/service_worker_global_scope_proxy.h
@@ -99,6 +99,10 @@
       const WebString& developer_id,
       const WebString& unique_id,
       const WebVector<WebBackgroundFetchSettledFetch>& fetches) override;
+  void DispatchCookieChangeEvent(int event_id,
+                                 const WebString& cookie_name,
+                                 const WebString& cookie_value,
+                                 bool is_cookie_delete) override;
   void DispatchExtendableMessageEvent(
       int event_id,
       TransferableMessage,
diff --git a/third_party/blink/renderer/modules/serviceworkers/wait_until_observer.cc b/third_party/blink/renderer/modules/serviceworkers/wait_until_observer.cc
index 1493899..28d48f76 100644
--- a/third_party/blink/renderer/modules/serviceworkers/wait_until_observer.cc
+++ b/third_party/blink/renderer/modules/serviceworkers/wait_until_observer.cc
@@ -259,6 +259,10 @@
       client->DidHandleCanMakePaymentEvent(event_id_, status,
                                            event_dispatch_time_);
       break;
+    case kCookieChange:
+      client->DidHandleCookieChangeEvent(event_id_, status,
+                                         event_dispatch_time_);
+      break;
     case kFetch:
       client->DidHandleFetchEvent(event_id_, status, event_dispatch_time_);
       break;
diff --git a/third_party/blink/renderer/modules/serviceworkers/wait_until_observer.h b/third_party/blink/renderer/modules/serviceworkers/wait_until_observer.h
index 0005c02..b1ef6e6 100644
--- a/third_party/blink/renderer/modules/serviceworkers/wait_until_observer.h
+++ b/third_party/blink/renderer/modules/serviceworkers/wait_until_observer.h
@@ -29,6 +29,7 @@
     kAbortPayment,
     kActivate,
     kCanMakePayment,
+    kCookieChange,
     kFetch,
     kInstall,
     kMessage,
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 185d207a..4c11cc9e 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -166,6 +166,11 @@
 import("//build/config/pch.gni")
 
 config("blink_platform_config") {
+  include_dirs = [
+    #"$angle_path/include",
+    "$root_gen_dir/third_party/blink/renderer",
+  ]
+
   configs = [
     "//third_party/blink/renderer:config",
     "//third_party/blink/renderer:inside_blink",
@@ -1973,6 +1978,8 @@
   ]
 
   defines = [ "INSIDE_BLINK" ]
+
+  include_dirs = [ "$root_gen_dir/third_party/blink/renderer" ]
 }
 
 executable("image_decode_bench") {
diff --git a/third_party/blink/renderer/platform/heap/heap_test.cc b/third_party/blink/renderer/platform/heap/heap_test.cc
index 9d108d9..e902bb28 100644
--- a/third_party/blink/renderer/platform/heap/heap_test.cc
+++ b/third_party/blink/renderer/platform/heap/heap_test.cc
@@ -2370,7 +2370,13 @@
   PreciselyCollectGarbage();
 }
 
-TEST(HeapTest, LargeHashMap) {
+// This test often fails on Android (https://crbug.com/843032).
+#if defined(OS_ANDROID)
+#define MAYBE_LargeHashMap DISABLED_LargeHashMap
+#else
+#define MAYBE_LargeHashMap LargeHashMap
+#endif
+TEST(HeapTest, MAYBE_LargeHashMap) {
   ClearOutOldGarbage();
   size_t size = (1 << 27) / sizeof(Member<IntWrapper>);
   Persistent<HeapHashMap<int, Member<IntWrapper>>> map =
diff --git a/third_party/blink/renderer/platform/heap/thread_state.cc b/third_party/blink/renderer/platform/heap/thread_state.cc
index 7e6c794..9aa09e4 100644
--- a/third_party/blink/renderer/platform/heap/thread_state.cc
+++ b/third_party/blink/renderer/platform/heap/thread_state.cc
@@ -478,6 +478,7 @@
   VLOG(2) << "[state:" << this << "] ScheduleV8FollowupGCIfNeeded: v8_gc_type="
           << ((gc_type == BlinkGC::kV8MajorGC) ? "MajorGC" : "MinorGC");
   DCHECK(CheckThread());
+  DCHECK_EQ(BlinkGC::kV8MajorGC, gc_type);
   ThreadHeap::ReportMemoryUsageForTracing();
 
   if (IsGCForbidden())
@@ -489,14 +490,13 @@
   DCHECK(!IsSweepingInProgress());
   DCHECK(!SweepForbidden());
 
-  if ((gc_type == BlinkGC::kV8MajorGC && ShouldForceMemoryPressureGC()) ||
-      ShouldScheduleV8FollowupGC()) {
+  if (ShouldForceMemoryPressureGC() || ShouldScheduleV8FollowupGC()) {
     VLOG(2) << "[state:" << this << "] "
             << "ScheduleV8FollowupGCIfNeeded: Scheduled precise GC";
     SchedulePreciseGC();
     return;
   }
-  if (gc_type == BlinkGC::kV8MajorGC && ShouldScheduleIdleGC()) {
+  if (ShouldScheduleIdleGC()) {
     VLOG(2) << "[state:" << this << "] "
             << "ScheduleV8FollowupGCIfNeeded: Scheduled idle GC";
     ScheduleIdleGC();
@@ -512,8 +512,8 @@
   // completeSweep() here, because gcPrologue for a major GC is called
   // not at the point where the major GC started but at the point where
   // the major GC requests object grouping.
-  if (gc_type == BlinkGC::kV8MajorGC)
-    CompleteSweep();
+  DCHECK_EQ(BlinkGC::kV8MajorGC, gc_type);
+  CompleteSweep();
 }
 
 void ThreadState::SchedulePageNavigationGCIfNeeded(
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_descriptor.cc b/third_party/blink/renderer/platform/mediastream/media_stream_descriptor.cc
index 6c80a55..d2373f3 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_descriptor.cc
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_descriptor.cc
@@ -80,9 +80,10 @@
       break;
   }
 
-  for (auto*& observer : observers_) {
+  // Iterate over a copy of |observers_| to avoid re-entrancy issues.
+  Vector<WebMediaStreamObserver*> observers = observers_;
+  for (auto*& observer : observers)
     observer->TrackAdded(component);
-  }
 }
 
 void MediaStreamDescriptor::RemoveComponent(MediaStreamComponent* component) {
@@ -100,9 +101,10 @@
       break;
   }
 
-  for (auto*& observer : observers_) {
+  // Iterate over a copy of |observers_| to avoid re-entrancy issues.
+  Vector<WebMediaStreamObserver*> observers = observers_;
+  for (auto*& observer : observers)
     observer->TrackRemoved(component);
-  }
 }
 
 void MediaStreamDescriptor::AddRemoteTrack(MediaStreamComponent* component) {
@@ -119,6 +121,17 @@
     RemoveComponent(component);
 }
 
+void MediaStreamDescriptor::SetActive(bool active) {
+  if (active == active_)
+    return;
+
+  active_ = active;
+  // Iterate over a copy of |observers_| to avoid re-entrancy issues.
+  Vector<WebMediaStreamObserver*> observers = observers_;
+  for (auto*& observer : observers)
+    observer->ActiveStateChanged(active_);
+}
+
 void MediaStreamDescriptor::AddObserver(WebMediaStreamObserver* observer) {
   DCHECK_EQ(observers_.Find(observer), kNotFound);
   observers_.push_back(observer);
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_descriptor.h b/third_party/blink/renderer/platform/mediastream/media_stream_descriptor.h
index 5ab2a53..7f12f28 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_descriptor.h
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_descriptor.h
@@ -102,7 +102,7 @@
   void RemoveRemoteTrack(MediaStreamComponent*);
 
   bool Active() const { return active_; }
-  void SetActive(bool active) { active_ = active; }
+  void SetActive(bool active);
 
   void AddObserver(WebMediaStreamObserver*);
   void RemoveObserver(WebMediaStreamObserver*);
diff --git a/third_party/blink/renderer/platform/scheduler/base/task_queue_manager.h b/third_party/blink/renderer/platform/scheduler/base/task_queue_manager.h
index 252811a6..9881945 100644
--- a/third_party/blink/renderer/platform/scheduler/base/task_queue_manager.h
+++ b/third_party/blink/renderer/platform/scheduler/base/task_queue_manager.h
@@ -5,6 +5,9 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_BASE_TASK_QUEUE_MANAGER_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_BASE_TASK_QUEUE_MANAGER_H_
 
+#include <memory>
+#include <utility>
+
 #include "base/message_loop/message_loop.h"
 #include "base/single_thread_task_runner.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
@@ -79,6 +82,10 @@
   virtual void EnableCrashKeys(const char* file_name_crash_key,
                                const char* function_name_crash_key) = 0;
 
+  // Returns the portion of tasks for which CPU time is recorded or 0 if not
+  // sampled.
+  virtual double GetSamplingRateForRecordingCPUTime() const = 0;
+
   // Creates a task queue with the given type, |spec| and args. Must be called
   // on the thread this class was created on.
   // TODO(altimin): TaskQueueManager should not create TaskQueues.
diff --git a/third_party/blink/renderer/platform/scheduler/base/task_queue_manager_impl.cc b/third_party/blink/renderer/platform/scheduler/base/task_queue_manager_impl.cc
index bafa891..4a6ddf9 100644
--- a/third_party/blink/renderer/platform/scheduler/base/task_queue_manager_impl.cc
+++ b/third_party/blink/renderer/platform/scheduler/base/task_queue_manager_impl.cc
@@ -4,9 +4,8 @@
 
 #include "third_party/blink/renderer/platform/scheduler/base/task_queue_manager_impl.h"
 
-#include <memory>
 #include <queue>
-#include <set>
+#include <vector>
 
 #include "base/bind.h"
 #include "base/bit_cast.h"
@@ -436,9 +435,11 @@
   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("sequence_manager"),
                "TaskQueueManagerImpl::NotifyDidProcessTaskObservers");
 
-  ThreadTicks task_end_thread_time;
-  if (executing_task.should_record_thread_time)
-    task_end_thread_time = ThreadTicks::Now();
+  Optional<TimeDelta> thread_time;
+  if (executing_task.should_record_thread_time) {
+    auto task_end_thread_time = ThreadTicks::Now();
+    thread_time = task_end_thread_time - executing_task.task_start_thread_time;
+  }
 
   if (!executing_task.task_queue->GetShouldNotifyObservers())
     return;
@@ -476,8 +477,7 @@
     if (task_start_time_sec && task_end_time_sec) {
       executing_task.task_queue->OnTaskCompleted(
           executing_task.pending_task, executing_task.task_start_time,
-          time_after_task->Now(),
-          task_end_thread_time - executing_task.task_start_thread_time);
+          time_after_task->Now(), thread_time);
     }
   }
 
@@ -647,6 +647,12 @@
              kSamplingRateForRecordingCPUTime;
 }
 
+double TaskQueueManagerImpl::GetSamplingRateForRecordingCPUTime() const {
+  if (!ThreadTicks::IsSupported())
+    return 0;
+  return kSamplingRateForRecordingCPUTime;
+}
+
 MSVC_DISABLE_OPTIMIZE()
 bool TaskQueueManagerImpl::Validate() {
   return memory_corruption_sentinel_ == kMemoryCorruptionSentinelValue;
diff --git a/third_party/blink/renderer/platform/scheduler/base/task_queue_manager_impl.h b/third_party/blink/renderer/platform/scheduler/base/task_queue_manager_impl.h
index d117491..8a03d9c 100644
--- a/third_party/blink/renderer/platform/scheduler/base/task_queue_manager_impl.h
+++ b/third_party/blink/renderer/platform/scheduler/base/task_queue_manager_impl.h
@@ -5,8 +5,13 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_BASE_TASK_QUEUE_MANAGER_IMPL_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_BASE_TASK_QUEUE_MANAGER_IMPL_H_
 
+#include <list>
 #include <map>
+#include <memory>
 #include <random>
+#include <set>
+#include <unordered_map>
+#include <utility>
 
 #include "base/atomic_sequence_num.h"
 #include "base/cancelable_callback.h"
@@ -103,6 +108,7 @@
   void SetWorkBatchSize(int work_batch_size) override;
   void EnableCrashKeys(const char* file_name_crash_key,
                        const char* function_name_crash_key) override;
+  double GetSamplingRateForRecordingCPUTime() const override;
 
   // Implementation of SequencedTaskSource:
   Optional<PendingTask> TakeTask() override;
diff --git a/third_party/blink/renderer/platform/scheduler/common/scheduler_helper.cc b/third_party/blink/renderer/platform/scheduler/common/scheduler_helper.cc
index 73ef6a6..795459fb0 100644
--- a/third_party/blink/renderer/platform/scheduler/common/scheduler_helper.cc
+++ b/third_party/blink/renderer/platform/scheduler/common/scheduler_helper.cc
@@ -4,6 +4,8 @@
 
 #include "third_party/blink/renderer/platform/scheduler/common/scheduler_helper.h"
 
+#include <utility>
+
 #include "base/time/default_tick_clock.h"
 #include "base/trace_event/trace_event.h"
 #include "base/trace_event/trace_event_argument.h"
@@ -135,5 +137,11 @@
   return base::TimeTicks::Now();
 }
 
+double SchedulerHelper::GetSamplingRateForRecordingCPUTime() const {
+  if (task_queue_manager_)
+    return task_queue_manager_->GetSamplingRateForRecordingCPUTime();
+  return 0;
+}
+
 }  // namespace scheduler
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/scheduler/common/scheduler_helper.h b/third_party/blink/renderer/platform/scheduler/common/scheduler_helper.h
index 75780415..f93b258 100644
--- a/third_party/blink/renderer/platform/scheduler/common/scheduler_helper.h
+++ b/third_party/blink/renderer/platform/scheduler/common/scheduler_helper.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_COMMON_SCHEDULER_HELPER_H_
 
 #include <stddef.h>
+#include <memory>
 
 #include "base/macros.h"
 #include "base/message_loop/message_loop.h"
@@ -89,6 +90,7 @@
   void RegisterTimeDomain(base::sequence_manager::TimeDomain* time_domain);
   void UnregisterTimeDomain(base::sequence_manager::TimeDomain* time_domain);
   bool GetAndClearSystemIsQuiescentBit();
+  double GetSamplingRateForRecordingCPUTime() const;
 
   // Test helpers.
   void SetWorkBatchSizeForTesting(size_t work_batch_size);
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
index 9f119e9..87e3483 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
@@ -4,7 +4,8 @@
 
 #include "third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h"
 
-#include <memory>
+#include <algorithm>
+#include <utility>
 
 #include "base/bind.h"
 #include "base/debug/stack_trace.h"
@@ -2475,7 +2476,7 @@
     base::TimeTicks start,
     base::TimeTicks end,
     base::Optional<base::TimeDelta> thread_time) {
-  if (!ShouldRecordTaskUkm())
+  if (!ShouldRecordTaskUkm(thread_time.has_value()))
     return;
 
   if (queue && queue->GetFrameScheduler()) {
@@ -2652,10 +2653,29 @@
   return main_thread_only().is_audio_playing;
 }
 
-bool MainThreadSchedulerImpl::ShouldRecordTaskUkm() {
-  // This function returns true with probability of kSamplingRateForTaskUkm.
+bool MainThreadSchedulerImpl::ShouldIgnoreTaskForUkm(bool has_thread_time,
+                                                     double* sampling_rate) {
+  const double thread_time_sampling_rate =
+      helper_.GetSamplingRateForRecordingCPUTime();
+  if (thread_time_sampling_rate && *sampling_rate < thread_time_sampling_rate) {
+    if (!has_thread_time)
+      return true;
+    *sampling_rate /= thread_time_sampling_rate;
+  }
+  return false;
+}
+
+bool MainThreadSchedulerImpl::ShouldRecordTaskUkm(bool has_thread_time) {
+  double sampling_rate = kSamplingRateForTaskUkm;
+
+  // If thread_time is sampled as well, try to align UKM sampling with it so
+  // that we only record UKMs for tasks that also record thread_time.
+  if (ShouldIgnoreTaskForUkm(has_thread_time, &sampling_rate)) {
+    return false;
+  }
+
   return main_thread_only().uniform_distribution(
-             main_thread_only().random_generator) < kSamplingRateForTaskUkm;
+             main_thread_only().random_generator) < sampling_rate;
 }
 
 // static
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
index 549cbca..a6b4d33 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
@@ -5,7 +5,11 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_MAIN_THREAD_MAIN_THREAD_SCHEDULER_IMPL_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_MAIN_THREAD_MAIN_THREAD_SCHEDULER_IMPL_H_
 
+#include <map>
+#include <memory>
 #include <random>
+#include <set>
+#include <string>
 
 #include "base/atomicops.h"
 #include "base/gtest_prod_util.h"
@@ -50,6 +54,7 @@
 namespace main_thread_scheduler_impl_unittest {
 class MainThreadSchedulerImplForTest;
 class MainThreadSchedulerImplTest;
+FORWARD_DECLARE_TEST(MainThreadSchedulerImplTest, ShouldIgnoreTaskForUkm);
 FORWARD_DECLARE_TEST(MainThreadSchedulerImplTest, Tracing);
 }  // namespace main_thread_scheduler_impl_unittest
 class PageSchedulerImpl;
@@ -314,6 +319,9 @@
   friend class main_thread_scheduler_impl_unittest::MainThreadSchedulerImplTest;
   FRIEND_TEST_ALL_PREFIXES(
       main_thread_scheduler_impl_unittest::MainThreadSchedulerImplTest,
+      ShouldIgnoreTaskForUkm);
+  FRIEND_TEST_ALL_PREFIXES(
+      main_thread_scheduler_impl_unittest::MainThreadSchedulerImplTest,
       Tracing);
 
   enum class ExpensiveTaskPolicy { kRun, kBlock, kThrottle };
@@ -442,7 +450,7 @@
 
   class PollableNeedsUpdateFlag {
    public:
-    PollableNeedsUpdateFlag(base::Lock* write_lock);
+    explicit PollableNeedsUpdateFlag(base::Lock* write_lock);
     ~PollableNeedsUpdateFlag();
 
     // Set the flag. May only be called if |write_lock| is held.
@@ -589,7 +597,14 @@
   // TaskQueueThrottler.
   void VirtualTimeResumed();
 
-  bool ShouldRecordTaskUkm();
+  // Returns true if the current task should not be reported in UKM because no
+  // thread time was recorded for it. Also updates |sampling_rate| to account
+  // for the ignored tasks by sampling the remaining tasks with higher
+  // probability.
+  bool ShouldIgnoreTaskForUkm(bool has_thread_time, double* sampling_rate);
+
+  // Returns true with probability of kSamplingRateForTaskUkm.
+  bool ShouldRecordTaskUkm(bool has_thread_time);
 
   // Probabilistically record all task metadata for the current task.
   // If task belongs to a per-frame queue, this task is attributed to
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl_unittest.cc b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl_unittest.cc
index fd4b8c0..7fe643df 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl_unittest.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl_unittest.cc
@@ -3919,6 +3919,27 @@
   EXPECT_EQ(base::Time::Now(), base::Time::FromJsTime(1000000.0));
 }
 
+TEST_F(MainThreadSchedulerImplTest, ShouldIgnoreTaskForUkm) {
+  bool supports_thread_ticks = base::ThreadTicks::IsSupported();
+  double sampling_rate;
+
+  sampling_rate = 0.0001;
+  EXPECT_FALSE(scheduler_->ShouldIgnoreTaskForUkm(true, &sampling_rate));
+  if (supports_thread_ticks) {
+    EXPECT_EQ(0.01, sampling_rate);
+  } else {
+    EXPECT_EQ(0.0001, sampling_rate);
+  }
+
+  sampling_rate = 0.0001;
+  if (supports_thread_ticks) {
+    EXPECT_TRUE(scheduler_->ShouldIgnoreTaskForUkm(false, &sampling_rate));
+  } else {
+    EXPECT_FALSE(scheduler_->ShouldIgnoreTaskForUkm(false, &sampling_rate));
+    EXPECT_EQ(0.0001, sampling_rate);
+  }
+}
+
 }  // namespace main_thread_scheduler_impl_unittest
 }  // namespace scheduler
 }  // namespace blink
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 3e242c8..638e8a4 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -40906,6 +40906,7 @@
   <int value="27" label="NAVIGATION_HINT"/>
   <int value="28" label="CAN_MAKE_PAYMENT"/>
   <int value="29" label="ABORT_PAYMENT"/>
+  <int value="30" label="COOKIE_CHANGE"/>
 </enum>
 
 <enum name="ServiceWorkerPreparationType">
@@ -45714,6 +45715,8 @@
   <int value="50" label="kErrorCodeOmahaUpdateIgnoredOverCellular"/>
   <int value="51" label="kErrorCodePayloadTimestampError"/>
   <int value="52" label="kErrorCodeUpdatedButNotActive"/>
+  <int value="53" label="kErrorCodeNoUpdate"/>
+  <int value="54" label="kErrorCodeRollbackNotPossible"/>
 </enum>
 
 <enum name="UpdateEngineInstallDateProvisioningSource">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index d6e4a11..d831889 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -48298,6 +48298,9 @@
 </histogram>
 
 <histogram name="Net.PreconnectMotivation" enum="PreconnectMotivation">
+  <obsolete>
+    Deprecated May 2018
+  </obsolete>
   <owner>csharrison@chromium.org</owner>
   <summary>
     When a preconnection is made, indicate what the motivation was.
@@ -48335,6 +48338,9 @@
 
 <histogram name="Net.PreconnectSubresourceEval"
     enum="PreconnectSubresourceEval">
+  <obsolete>
+    Deprecated May 2018
+  </obsolete>
   <owner>csharrison@chromium.org</owner>
   <summary>
     What did we decide to do about a predicted resource, based on the historical
@@ -48349,6 +48355,9 @@
 </histogram>
 
 <histogram name="Net.PreconnectSubresourceExpectation">
+  <obsolete>
+    Deprecated May 2018
+  </obsolete>
   <owner>csharrison@chromium.org</owner>
   <summary>
     The expected number of connections, times 100, that we'll make to a given
@@ -48416,6 +48425,9 @@
 </histogram>
 
 <histogram name="Net.Predictor.Startup.DBSize" units="bytes">
+  <obsolete>
+    Deprecated May 2018
+  </obsolete>
   <owner>csharrison@chromium.org</owner>
   <summary>
     The approximate size in bytes of the predictor's database on startup.
@@ -84199,6 +84211,15 @@
   </summary>
 </histogram>
 
+<histogram name="ServiceWorker.CookieChangeEvent.Time" units="ms">
+  <owner>pwnall@chromium.org</owner>
+  <summary>
+    The time taken between dispatching a CookieChangeEvent to a Service Worker
+    and receiving a message that it finished handling the event. Includes the
+    time for the waitUntil() promise to settle.
+  </summary>
+</histogram>
+
 <histogram name="ServiceWorker.Database.DestroyDatabaseResult"
     enum="ServiceWorkerDatabaseStatus">
   <owner>nhiroki@chromium.org</owner>
@@ -118041,6 +118062,7 @@
 
 <histogram_suffixes name="ServiceWorker.EventType" separator="_">
   <suffix name="ACTIVATE" label="ACTIVATE"/>
+  <suffix name="COOKIE_CHANGE" label="COOKIE_CHANGE"/>
   <suffix name="FETCH_MAIN_FRAME" label="FETCH_MAIN_FRAME"/>
   <suffix name="FETCH_SHARED_WORKER" label="FETCH_SHARED_WORKER"/>
   <suffix name="FETCH_SUB_FRAME" label="FETCH_SUB_FRAME"/>
@@ -118227,6 +118249,7 @@
   <suffix name="ntp" label="Custom histogram for New Tab Page"/>
   <suffix name="plus" label="Custom histogram for Google+"/>
   <affected-histogram name="ServiceWorker.EventDispatchingDelay_ACTIVATE"/>
+  <affected-histogram name="ServiceWorker.EventDispatchingDelay_COOKIE_CHANGE"/>
   <affected-histogram
       name="ServiceWorker.EventDispatchingDelay_FETCH_MAIN_FRAME"/>
   <affected-histogram
diff --git a/ui/file_manager/file_manager/background/js/background.js b/ui/file_manager/file_manager/background/js/background.js
index 6119aeb..c8febdb90 100644
--- a/ui/file_manager/file_manager/background/js/background.js
+++ b/ui/file_manager/file_manager/background/js/background.js
@@ -491,21 +491,19 @@
 FileBrowserBackgroundImpl.prototype.onMountCompletedInternal_ = function(
     event) {
   // If there is no focused window, then create a new one opened on the
-  // mounted FSP volume.
+  // mounted volume.
   this.findFocusedWindow_()
       .then(function(key) {
         let statusOK = event.status === 'success' ||
             event.status === 'error_path_already_mounted';
         let volumeTypeOK =
-            event.volumeMetadata.source === VolumeManagerCommon.Source.FILE ||
-            VolumeManagerCommon.getProvidedFileSystemIdFromVolumeId(
-                event.volumeId) ===
-                VolumeManagerCommon.ProvidedFileSystem.CROSTINI;
-        if (key === null && event.eventType === 'mount' && statusOK &&
-            event.volumeMetadata.mountContext === 'user' &&
+            (event.volumeMetadata.volumeType ===
+                 VolumeManagerCommon.VolumeType.PROVIDED &&
+             event.volumeMetadata.source === VolumeManagerCommon.Source.FILE) ||
             event.volumeMetadata.volumeType ===
-                VolumeManagerCommon.VolumeType.PROVIDED &&
-            volumeTypeOK) {
+                VolumeManagerCommon.VolumeType.CROSTINI;
+        if (key === null && event.eventType === 'mount' && statusOK &&
+            event.volumeMetadata.mountContext === 'user' && volumeTypeOK) {
           this.navigateToVolumeWhenReady_(event.volumeMetadata.volumeId);
         }
       }.bind(this))
diff --git a/ui/file_manager/file_manager/background/js/volume_manager_util.js b/ui/file_manager/file_manager/background/js/volume_manager_util.js
index 372579b3..4a7393d 100644
--- a/ui/file_manager/file_manager/background/js/volume_manager_util.js
+++ b/ui/file_manager/file_manager/background/js/volume_manager_util.js
@@ -77,6 +77,9 @@
           break;
       }
       break;
+    case VolumeManagerCommon.VolumeType.CROSTINI:
+      localizedLabel = str('LINUX_FILES_ROOT_LABEL');
+      break;
     default:
       // TODO(mtomasz): Calculate volumeLabel for all types of volumes in the
       // C++ layer.
diff --git a/ui/file_manager/file_manager/common/js/volume_manager_common.js b/ui/file_manager/file_manager/common/js/volume_manager_common.js
index dca5b10b..f95b4a78 100644
--- a/ui/file_manager/file_manager/common/js/volume_manager_common.js
+++ b/ui/file_manager/file_manager/common/js/volume_manager_common.js
@@ -328,26 +328,6 @@
       volumeId.split(':', 2)[1]);
 };
 
-
-/**
- * List of known FSP-provided fileSystemId values.
- *
- * @enum {string}
- * @const
- */
-VolumeManagerCommon.ProvidedFileSystem = {
-  CROSTINI: 'crostini',
-};
-
-/**
- * Obtains fileSystemId from volumeId of FSP-provided mount.
- * @param {string} volumeId Volume ID.
- * @return {string|undefined}
- */
-VolumeManagerCommon.getProvidedFileSystemIdFromVolumeId = function(volumeId) {
-  return volumeId ? volumeId.split(':', 3)[2] : undefined;
-};
-
 /**
  * Fake entries for virtual folders which hold Google Drive offline files,
  * Google Drive "Shared with me" files, and mixed Recent files.
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 efddfa0..ef1680e 100644
--- a/ui/file_manager/file_manager/foreground/css/file_types.css
+++ b/ui/file_manager/file_manager/foreground/css/file_types.css
@@ -431,16 +431,16 @@
       url(../images/volumes/2x/recent_active.png) 2x);
 }
 
-[sftp-mount-icon='linux-files'],
-[volume-subtype='crostini'] {
+[root-type-icon='crostini'],
+[volume-type-icon='crostini'] {
   /* Need !important to override inline style applied to provided volumes. */
   background-image: -webkit-image-set(
       url(../images/volumes/linux_files.png) 1x,
       url(../images/volumes/2x/linux_files.png) 2x) !important;
 }
 
-.tree-row[selected] [sftp-mount-icon='linux-files'],
-.tree-row[selected] [volume-subtype='crostini'] {
+.tree-row[selected] [root-type-icon='crostini'],
+.tree-row[selected] [volume-type-icon='crostini'] {
   /* Need !important to override inline style applied to provided volumes. */
   background-image: -webkit-image-set(
       url(../images/volumes/linux_files_active.png) 1x,
diff --git a/ui/file_manager/file_manager/foreground/js/directory_contents.js b/ui/file_manager/file_manager/foreground/js/directory_contents.js
index dccba56..04c98d2 100644
--- a/ui/file_manager/file_manager/foreground/js/directory_contents.js
+++ b/ui/file_manager/file_manager/foreground/js/directory_contents.js
@@ -316,6 +316,47 @@
 };
 
 /**
+ * Shows an empty list and spinner whilst starting and mounting the
+ * crostini container.
+ *
+ * This function is only called once to start and mount the crostini
+ * container.  When FilesApp starts, the related fake root entry for
+ * crostini is shown which uses this CrostiniMounter as its ContentScanner.
+ *
+ * When the sshfs mount completes, it will show up as a disk volume.
+ * NavigationListModel.reorderNavigationItems_ will detect that crostini
+ * is mounted as a disk volume and hide the fake root item while the
+ * disk volume exists.
+ *
+ * @constructor
+ * @extends {ContentScanner}
+ */
+function CrostiniMounter() {
+  ContentScanner.call(this);
+}
+
+/**
+ * Extends ContentScanner.
+ */
+CrostiniMounter.prototype.__proto__ = ContentScanner.prototype;
+
+/**
+ * @override
+ */
+CrostiniMounter.prototype.scan = function(
+    entriesCallback, successCallback, errorCallback) {
+  chrome.fileManagerPrivate.mountCrostiniContainer(() => {
+    if (chrome.runtime.lastError) {
+      console.error(
+          'mountCrostiniContainer error: ', chrome.runtime.lastError.message);
+      errorCallback(util.createDOMError(util.FileError.NOT_READABLE_ERR));
+      return;
+    }
+    successCallback();
+  });
+};
+
+/**
  * This class manages filters and determines a file should be shown or not.
  * When filters are changed, a 'changed' event is fired.
  *
@@ -941,3 +982,18 @@
     return new RecentContentScanner(query, recentRootEntry.sourceRestriction);
   });
 };
+
+/**
+ * Creates a DirectoryContents instance to show the sshfs crostini files.
+ *
+ * @param {FileListContext} context File list context.
+ * @param {!FakeEntry} crostiniRootEntry Fake directory entry representing the
+ *     root of recent files.
+ * @return {DirectoryContents} Created DirectoryContents instance.
+ */
+DirectoryContents.createForCrostiniMounter = function(
+    context, crostiniRootEntry) {
+  return new DirectoryContents(context, true, crostiniRootEntry, function() {
+    return new CrostiniMounter();
+  });
+};
diff --git a/ui/file_manager/file_manager/foreground/js/directory_model.js b/ui/file_manager/file_manager/foreground/js/directory_model.js
index 86629d0..87a007ac 100644
--- a/ui/file_manager/file_manager/foreground/js/directory_model.js
+++ b/ui/file_manager/file_manager/foreground/js/directory_model.js
@@ -1168,11 +1168,9 @@
   // then redirect to it in the focused window.
   // Note, that this is a temporary solution for https://crbug.com/427776.
   if (window.isFocused() && event.added.length === 1 &&
-      event.added[0].volumeType === VolumeManagerCommon.VolumeType.PROVIDED &&
-      (event.added[0].source === VolumeManagerCommon.Source.FILE ||
-       VolumeManagerCommon.getProvidedFileSystemIdFromVolumeId(
-           event.added[0].volumeId) ===
-           VolumeManagerCommon.ProvidedFileSystem.CROSTINI)) {
+      ((event.added[0].volumeType === VolumeManagerCommon.VolumeType.PROVIDED &&
+        event.added[0].source === VolumeManagerCommon.Source.FILE) ||
+       event.added[0].volumeType === VolumeManagerCommon.VolumeType.CROSTINI)) {
     event.added[0].resolveDisplayRoot().then(function(displayRoot) {
       // Resolving a display root on FSP volumes is instant, despite the
       // asynchronous call.
@@ -1202,6 +1200,10 @@
     return DirectoryContents.createForRecent(
         context, /** @type {!FakeEntry} */ (entry), query);
   }
+  if (entry.rootType == VolumeManagerCommon.RootType.CROSTINI) {
+    return DirectoryContents.createForCrostiniMounter(
+        context, /** @type {!FakeEntry} */ (entry));
+  }
   if (query && canUseDriveSearch) {
     // Drive search.
     return DirectoryContents.createForDriveSearch(
diff --git a/ui/file_manager/file_manager/foreground/js/file_manager.js b/ui/file_manager/file_manager/foreground/js/file_manager.js
index 46ce88b..6c9be25 100644
--- a/ui/file_manager/file_manager/foreground/js/file_manager.js
+++ b/ui/file_manager/file_manager/foreground/js/file_manager.js
@@ -1168,20 +1168,35 @@
         assert(this.volumeManager_), assert(this.folderShortcutsModel_),
         fakeEntriesVisible &&
                 !DialogType.isFolderDialog(this.launchParams_.type) ?
-            new NavigationModelRecentItem(str('RECENT_ROOT_LABEL'), {
-              isDirectory: true,
-              rootType: VolumeManagerCommon.RootType.RECENT,
-              toURL: function() {
-                return 'fake-entry://recent';
-              },
-              sourceRestriction: this.getSourceRestriction_()
-            }) :
+            new NavigationModelFakeItem(
+                str('RECENT_ROOT_LABEL'), NavigationModelItemType.RECENT, {
+                  isDirectory: true,
+                  rootType: VolumeManagerCommon.RootType.RECENT,
+                  toURL: function() {
+                    return 'fake-entry://recent';
+                  },
+                  sourceRestriction: this.getSourceRestriction_()
+                }) :
             null,
         addNewServicesVisible ?
             new NavigationModelMenuItem(
                 str('ADD_NEW_SERVICES_BUTTON_LABEL'), '#add-new-services-menu',
                 'add-new-services') :
             null);
+    // Check if crostini is enabled to create linuxFilesItem.
+    chrome.fileManagerPrivate.isCrostiniEnabled((enabled) => {
+      if (!enabled)
+        return;
+      this.directoryTree.dataModel.linuxFilesItem = new NavigationModelFakeItem(
+          str('LINUX_FILES_ROOT_LABEL'), NavigationModelItemType.CROSTINI, {
+            isDirectory: true,
+            rootType: VolumeManagerCommon.RootType.CROSTINI,
+            toURL: function() {
+              return 'fake-entry://linux-files';
+            },
+          });
+      this.directoryTree.redraw(false);
+    });
     this.ui_.initDirectoryTree(directoryTree);
   };
 
diff --git a/ui/file_manager/file_manager/foreground/js/navigation_list_model.js b/ui/file_manager/file_manager/foreground/js/navigation_list_model.js
index fc555c305..1530fdf 100644
--- a/ui/file_manager/file_manager/foreground/js/navigation_list_model.js
+++ b/ui/file_manager/file_manager/foreground/js/navigation_list_model.js
@@ -10,7 +10,7 @@
   VOLUME: 'volume',
   MENU: 'menu',
   RECENT: 'recent',
-  SFTP_MOUNT: 'sftp_mount',
+  CROSTINI: 'crostini',
 };
 
 /**
@@ -111,20 +111,21 @@
 };
 
 /**
- * Item of NavigationListModel for a Recent view.
+ * Item of NavigationListModel for a fake item such as Recent or Linux Files.
  *
  * @param {string} label Label on the menu button.
- * @param {!FakeEntry} entry Fake entry for the Recent root folder.
+ * @param {NavigationModelItemType} type
+ * @param {!FakeEntry} entry Fake entry for the root folder.
  * @constructor
  * @extends {NavigationModelItem}
  * @struct
  */
-function NavigationModelRecentItem(label, entry) {
-  NavigationModelItem.call(this, label, NavigationModelItemType.RECENT);
+function NavigationModelFakeItem(label, type, entry) {
+  NavigationModelItem.call(this, label, type);
   this.entry_ = entry;
 }
 
-NavigationModelRecentItem.prototype = /** @struct */ {
+NavigationModelFakeItem.prototype = /** @struct */ {
   __proto__: NavigationModelItem.prototype,
   get entry() {
     return this.entry_;
@@ -132,49 +133,11 @@
 };
 
 /**
- * Item of NavigationListModel for an SFTP Mount as used by Linux files.
- *
- * @param {string} label Label on the item.
- * @param {!FakeEntry} entry Fake entry for the SFTP Mount root folder.
- * @param {string} icon CSS icon.
- * @constructor
- * @extends {NavigationModelItem}
- * @struct
- */
-function NavigationModelSFTPMountItem(label, entry, icon) {
-  NavigationModelItem.call(this, label, NavigationModelItemType.SFTP_MOUNT);
-  this.entry_ = entry;
-  this.icon_ = icon;
-}
-
-NavigationModelSFTPMountItem.prototype = /** @struct */ {
-  __proto__: NavigationModelItem.prototype,
-  get entry() {
-    return this.entry_;
-  },
-  get icon() {
-    return this.icon_;
-  },
-  /**
-   * Start crostini container and mount it.
-   */
-  mount: function() {
-    chrome.fileManagerPrivate.mountCrostiniContainer(() => {
-      // TODO(crbug.com/834103): implement crostini error handling.
-      if (chrome.runtime.lastError) {
-        console.error(
-            'mountCrostiniContainer error: ', chrome.runtime.lastError.message);
-      }
-    });
-  },
-};
-
-/**
  * A navigation list model. This model combines multiple models.
  * @param {!VolumeManagerWrapper} volumeManager VolumeManagerWrapper instance.
  * @param {(!cr.ui.ArrayDataModel|!FolderShortcutsDataModel)} shortcutListModel
  *     The list of folder shortcut.
- * @param {NavigationModelRecentItem} recentModelItem Recent folder.
+ * @param {NavigationModelFakeItem} recentModelItem Recent folder.
  * @param {NavigationModelMenuItem} addNewServicesItem Add new services item.
  * @constructor
  * @extends {cr.EventTarget}
@@ -196,7 +159,7 @@
   this.shortcutListModel_ = shortcutListModel;
 
   /**
-   * @private {NavigationModelRecentItem}
+   * @private {NavigationModelFakeItem}
    * @const
    */
   this.recentModelItem_ = recentModelItem;
@@ -205,7 +168,7 @@
    * Root folder for crostini Linux Files.
    * This field will be set asynchronously after calling
    * chrome.fileManagerPrivate.isCrostiniEnabled.
-   * @private {NavigationModelSFTPMountItem}
+   * @private {NavigationModelFakeItem}
    */
   this.linuxFilesItem_ = null;
 
@@ -256,24 +219,6 @@
     this.shortcutList_.push(entryToModelItem(shortcutEntry));
   }
 
-  // Check if crostini is enabled to create linuxFilesItem_.
-  chrome.fileManagerPrivate.isCrostiniEnabled((enabled) => {
-    if (!enabled)
-      return;
-
-    this.linuxFilesItem_ = new NavigationModelSFTPMountItem(
-        str('LINUX_FILES_ROOT_LABEL'), {
-          isDirectory: true,
-          rootType: VolumeManagerCommon.RootType.CROSTINI,
-          toURL: function() {
-            return 'fake-entry://linux-files';
-          },
-        },
-        'linux-files');
-    // Reorder items to ensure Linux Files is shown.
-    this.reorderNavigationItems_();
-  });
-
   // Reorder volumes, shortcuts, and optional items for initial display.
   this.reorderNavigationItems_();
 
@@ -388,8 +333,22 @@
  */
 NavigationListModel.prototype = {
   __proto__: cr.EventTarget.prototype,
-  get length() { return this.length_(); },
-  get folderShortcutList() { return this.shortcutList_; }
+  get length() {
+    return this.length_();
+  },
+  get folderShortcutList() {
+    return this.shortcutList_;
+  },
+  /**
+   * Set the crostini Linux Files root and reorder items.
+   * This setter is provided separate to the constructor since
+   * this field is set async after calling fileManagerPrivate.isCrostiniEnabled.
+   * @param {NavigationModelFakeItem} item Linux Files root.
+   */
+  set linuxFilesItem(item) {
+    this.linuxFilesItem_ = item;
+    this.reorderNavigationItems_();
+  },
 };
 
 /**
@@ -407,9 +366,8 @@
   // Check if Linux files already mounted.
   let linuxFilesMounted = false;
   for (let i = 0; i < this.volumeList_.length; i++) {
-    if (VolumeManagerCommon.getProvidedFileSystemIdFromVolumeId(
-            this.volumeList_[i].volumeInfo.volumeId) ===
-        VolumeManagerCommon.ProvidedFileSystem.CROSTINI) {
+    if (this.volumeList_[i].volumeInfo.volumeType ===
+        VolumeManagerCommon.VolumeType.CROSTINI) {
       linuxFilesMounted = true;
       break;
     }
diff --git a/ui/file_manager/file_manager/foreground/js/navigation_list_model_unittest.js b/ui/file_manager/file_manager/foreground/js/navigation_list_model_unittest.js
index 9ec752ce..b570a2bf8 100644
--- a/ui/file_manager/file_manager/foreground/js/navigation_list_model_unittest.js
+++ b/ui/file_manager/file_manager/foreground/js/navigation_list_model_unittest.js
@@ -20,15 +20,7 @@
   // Override VolumeInfo.prototype.resolveDisplayRoot.
   VolumeInfoImpl.prototype.resolveDisplayRoot = function() {};
 
-  // Mock chrome.fileManagerPrivate.isCrostiniEnabled.
   // TODO(crbug.com/834103): Add integration test for Crostini.
-  chrome.fileManagerPrivate = {
-    crostiniEnabled: false,
-    isCrostiniEnabled: function(callback) {
-      callback(this.crostiniEnabled);
-    },
-  };
-
   drive = new MockFileSystem('drive');
   hoge = new MockFileSystem('removable:hoge');
 }
@@ -37,17 +29,16 @@
   var volumeManager = new MockVolumeManagerWrapper();
   var shortcutListModel = new MockFolderShortcutDataModel(
       [new MockFileEntry(drive, '/root/shortcut')]);
-  var fakeEntry = {
-    toURL: function() {
-      return 'fake-entry://recent';
-    }
-  };
-  var recentItem = new NavigationModelRecentItem('recent-label', fakeEntry);
-  chrome.fileManagerPrivate.crostiniEnabled = true;
+  var recentItem = new NavigationModelFakeItem(
+      'recent-label', NavigationModelItemType.RECENT,
+      {toURL: () => 'fake-entry://recent'});
   var addNewServicesItem = new NavigationModelMenuItem(
       'menu-button-label', '#add-new-services', 'menu-button-icon');
   var model = new NavigationListModel(
       volumeManager, shortcutListModel, recentItem, addNewServicesItem);
+  model.linuxFilesItem = new NavigationModelFakeItem(
+      'linux-files-label', NavigationModelItemType.CROSTINI,
+      {toURL: () => 'fake-entry://linux-files'});
 
   assertEquals(6, model.length);
   assertEquals('drive', model.item(0).volumeInfo.volumeId);
diff --git a/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js b/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js
index d660c78..9031aec1a 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js
@@ -700,13 +700,6 @@
         'volume-subtype',
         VolumeManagerCommon.getMediaViewRootTypeFromVolumeId(
             volumeInfo.volumeId));
-  } else if (
-      volumeInfo.volumeType === VolumeManagerCommon.VolumeType.PROVIDED) {
-    icon.setAttribute(
-        'volume-subtype',
-        VolumeManagerCommon.getProvidedFileSystemIdFromVolumeId(
-            volumeInfo.volumeId) ||
-            '');
   } else {
     icon.setAttribute('volume-subtype', volumeInfo.deviceType || '');
   }
@@ -1116,21 +1109,24 @@
 };
 
 ////////////////////////////////////////////////////////////////////////////////
-// RecentItem
+// FakeItem
 
 /**
- * @param {!NavigationModelRecentItem} modelItem
+ * FakeItem is used by Recent and Linux Files.
+ * @param {!VolumeManagerCommon.RootType} rootType root type.
+ * @param {!NavigationModelFakeItem} modelItem
  * @param {!DirectoryTree} tree Current tree, which contains this item.
  * @extends {cr.ui.TreeItem}
  * @constructor
  */
-function RecentItem(modelItem, tree) {
+function FakeItem(rootType, modelItem, tree) {
   var item = new cr.ui.TreeItem();
   // Get the original label id defined by TreeItem, before overwriting
   // prototype.
   var labelId = item.labelElement.id;
-  item.__proto__ = RecentItem.prototype;
+  item.__proto__ = FakeItem.prototype;
 
+  item.rootType_ = rootType;
   item.parentTree_ = tree;
   item.modelItem_ = modelItem;
   item.dirEntry_ = modelItem.entry;
@@ -1140,12 +1136,12 @@
 
   var icon = queryRequiredElement('.icon', item);
   icon.classList.add('item-icon');
-  icon.setAttribute('root-type-icon', 'recent');
+  icon.setAttribute('root-type-icon', rootType);
 
   return item;
 }
 
-RecentItem.prototype = {
+FakeItem.prototype = {
   __proto__: cr.ui.TreeItem.prototype,
   get entry() {
     return this.dirEntry_;
@@ -1162,24 +1158,24 @@
  * @param {!DirectoryEntry|!FakeEntry} entry
  * @return {boolean} True if the parent item is found.
  */
-RecentItem.prototype.searchAndSelectByEntry = function(entry) {
+FakeItem.prototype.searchAndSelectByEntry = function(entry) {
   return false;
 };
 
 /**
  * @override
  */
-RecentItem.prototype.handleClick = function(e) {
+FakeItem.prototype.handleClick = function(e) {
   this.activate();
 
   DirectoryItemTreeBaseMethods.recordUMASelectedEntry.call(
-      this, e, VolumeManagerCommon.RootType.RECENT, true);
+      this, e, this.rootType_, true);
 };
 
 /**
  * @param {!DirectoryEntry} entry
  */
-RecentItem.prototype.selectByEntry = function(entry) {
+FakeItem.prototype.selectByEntry = function(entry) {
   if (util.isSameEntry(entry, this.entry))
     this.selected = true;
 };
@@ -1187,60 +1183,11 @@
 /**
  * Executes the command.
  */
-RecentItem.prototype.activate = function() {
+FakeItem.prototype.activate = function() {
   this.parentTree_.directoryModel.activateDirectoryEntry(this.entry);
 };
 
 ////////////////////////////////////////////////////////////////////////////////
-// SFTPMountItem
-
-/**
- * A TreeItem which represents a directory to be mounted using SFTP.
- *
- * @param {!NavigationModelSFTPMountItem} modelItem
- * @param {!DirectoryTree} tree Current tree, which contains this item.
- * @extends {cr.ui.TreeItem}
- * @constructor
- */
-function SFTPMountItem(modelItem, tree) {
-  var item = new cr.ui.TreeItem();
-  item.__proto__ = SFTPMountItem.prototype;
-
-  item.parentTree_ = tree;
-  item.modelItem_ = modelItem;
-  item.innerHTML = TREE_ITEM_INNER_HTML;
-  item.label = modelItem.label;
-
-  var icon = queryRequiredElement('.icon', item);
-  icon.classList.add('item-icon');
-  icon.setAttribute('sftp-mount-icon', item.modelItem_.icon);
-
-  return item;
-}
-
-SFTPMountItem.prototype = {
-  __proto__: cr.ui.TreeItem.prototype,
-  get entry() {
-    return null;
-  },
-  get modelItem() {
-    return this.modelItem_;
-  },
-  get labelElement() {
-    return this.firstElementChild.querySelector('.label');
-  }
-};
-
-/**
- * @override
- */
-SFTPMountItem.prototype.handleClick = function(e) {
-  this.selected = true;
-  this.modelItem_.mount();
-};
-
-
-////////////////////////////////////////////////////////////////////////////////
 // DirectoryTree
 
 /**
@@ -1426,10 +1373,16 @@
           this.addAt(new MenuItem(modelItem, this), itemIndex);
           break;
         case NavigationModelItemType.RECENT:
-          this.addAt(new RecentItem(modelItem, this), itemIndex);
+          this.addAt(
+              new FakeItem(
+                  VolumeManagerCommon.RootType.RECENT, modelItem, this),
+              itemIndex);
           break;
-        case NavigationModelItemType.SFTP_MOUNT:
-          this.addAt(new SFTPMountItem(modelItem, this), itemIndex);
+        case NavigationModelItemType.CROSTINI:
+          this.addAt(
+              new FakeItem(
+                  VolumeManagerCommon.RootType.CROSTINI, modelItem, this),
+              itemIndex);
           break;
       }
     }
diff --git a/ui/file_manager/file_manager/test/js/chrome_file_manager.js b/ui/file_manager/file_manager/test/js/chrome_file_manager.js
index d1511fc..50c053d 100644
--- a/ui/file_manager/file_manager/test/js/chrome_file_manager.js
+++ b/ui/file_manager/file_manager/test/js/chrome_file_manager.js
@@ -118,9 +118,16 @@
   grantAccess: (entryUrls, callback) => {
     setTimeout(callback, 0);
   },
+  isCrostiniEnabled: (callback) => {
+    setTimeout(callback, 0, true);
+  },
   isUMAEnabled: (callback) => {
     setTimeout(callback, 0, false);
   },
+  mountCrostiniContainer: (callback) => {
+    // Simulate startup of vm and container by taking 3s.
+    setTimeout(callback, 3000);
+  },
   onAppsUpdated: {
     addListener: () => {},
   },
diff --git a/ui/file_manager/file_manager/test/scripts/create_test_main.py b/ui/file_manager/file_manager/test/scripts/create_test_main.py
index 1b344d9..f2552ac8 100755
--- a/ui/file_manager/file_manager/test/scripts/create_test_main.py
+++ b/ui/file_manager/file_manager/test/scripts/create_test_main.py
@@ -133,14 +133,17 @@
 #  * background/js/background_common_scripts.js
 #  * background/js/background_scripts.js
 # into <script> tags in main.html.
-# Add polymer lib at start.
+# Add polymer libs at start.
 bg_scripts = read('background/js/background_scripts.js').split('\n')
 includes2scripts('foreground/js/main_scripts.js')
 includes2scripts('background/js/background_common_scripts.js')
 includes2scripts('background/js/background_scripts.js')
 main_html = replaceline(main_html, 'foreground/js/main_scripts.js', [
     ('<link rel="import" href="../../../third_party/polymer/v1_0/'
-     'components-chromium/polymer/polymer.html">')] + scripts)
+     'components-chromium/polymer/polymer.html">'),
+    ('<link rel="import" href="../../../third_party/polymer/v1_0/'
+     'components-chromium/paper-progress/paper-progress.html">'),
+    ] + scripts)
 
 # Load QuickView in iframe rather than webview.
 # Change references in files_quick_view.html to use updated