diff --git a/DEPS b/DEPS
index 02ae826..a08e612b 100644
--- a/DEPS
+++ b/DEPS
@@ -40,11 +40,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '1608a1dd17187aeeada376e710ecfafb1e229af2',
+  'skia_revision': '31550dbc9804dafb18a71c968deb513cc8857cf3',
   # 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': 'a5ee59a9d7ab97a46345f11332ceb3783c90978d',
+  'v8_revision': '973d0ea8a1dc71bcf43ffb85757f02232d66d040',
   # 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.
@@ -64,7 +64,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '7dee685df0309401ad37c30c49a56d8523d1f8bb',
+  'pdfium_revision': 'd9d6c29879780db829694d0023a377581bbc9769',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling openmax_dl
   # and whatever else without interference from each other.
@@ -96,7 +96,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': 'dc460e541ad9d8ce33e8adc3aa46bd283ee26e39',
+  'catapult_revision': '3919ea65c283bd0480b5c7fca196acc4d571fad4',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -410,7 +410,7 @@
 
     # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
     'src/third_party/chromite':
-      Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '9a66c4dd0d61dad05b3505c4be8f914f54fb7f20',
+      Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'b97695f8e05e75b773b40d12a5eb9bb3b5872f5d',
 
     # Dependency of chromite.git and skia.
     'src/third_party/pyelftools':
diff --git a/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java b/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java
index f25d41d..785cd6c 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java
@@ -32,7 +32,7 @@
 import org.chromium.base.process_launcher.ChildProcessCreationParams;
 import org.chromium.components.minidump_uploader.CrashFileManager;
 import org.chromium.content.browser.BrowserStartupController;
-import org.chromium.content.browser.ChildProcessLauncher;
+import org.chromium.content.browser.ChildProcessLauncherHelper;
 import org.chromium.policy.CombinedPolicyProvider;
 
 import java.io.File;
@@ -101,7 +101,7 @@
                 boolean multiProcess = CommandLine.getInstance().hasSwitch(
                         AwSwitches.WEBVIEW_SANDBOXED_RENDERER);
                 if (multiProcess) {
-                    ChildProcessLauncher.warmUp(appContext);
+                    ChildProcessLauncherHelper.warmUp(appContext);
                 }
                 // The policies are used by browser startup, so we need to register the policy
                 // providers before starting the browser process. This only registers java objects
diff --git a/android_webview/java/src/org/chromium/android_webview/AwRendererPriorityManager.java b/android_webview/java/src/org/chromium/android_webview/AwRendererPriorityManager.java
index 5246451..9905204 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwRendererPriorityManager.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwRendererPriorityManager.java
@@ -7,7 +7,7 @@
 import org.chromium.android_webview.renderer_priority.RendererPriority;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
-import org.chromium.content.browser.ChildProcessLauncher;
+import org.chromium.content.browser.ChildProcessLauncherHelper;
 
 /**
  * Exposes an interface via which native code can manage the priority
@@ -18,7 +18,7 @@
     @CalledByNative
     private static void setRendererPriority(int pid, @RendererPriority int rendererPriority) {
         // TODO(tobiasjs): handle RendererPriority.LOW separately from WAIVED.
-        ChildProcessLauncher.getBindingManager().setPriority(
+        ChildProcessLauncherHelper.getBindingManager().setPriority(
                 pid, rendererPriority == RendererPriority.HIGH, false /* boostForPendingViews */);
     }
 }
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsTest.java
index 0350cf6..f682f148 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsTest.java
@@ -33,7 +33,7 @@
 import org.chromium.base.test.util.parameter.ParameterizedTest;
 import org.chromium.content.browser.BindingManager;
 import org.chromium.content.browser.ChildProcessConnection;
-import org.chromium.content.browser.ChildProcessLauncher;
+import org.chromium.content.browser.ChildProcessLauncherHelper;
 import org.chromium.content_public.common.ContentUrlConstants;
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.net.test.util.TestWebServer;
@@ -622,7 +622,7 @@
     @ParameterizedTest.Set
     public void testSandboxedRendererWorks() throws Throwable {
         MockBindingManager bindingManager = new MockBindingManager();
-        ChildProcessLauncher.setBindingManagerForTesting(bindingManager);
+        ChildProcessLauncherHelper.setBindingManagerForTesting(bindingManager);
         assertFalse(bindingManager.isChildProcessCreated());
 
         AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
@@ -651,7 +651,7 @@
     @ParameterizedTest.Set
     public void testRendererPriorityStartsHigh() throws Throwable {
         MockBindingManager bindingManager = new MockBindingManager();
-        ChildProcessLauncher.setBindingManagerForTesting(bindingManager);
+        ChildProcessLauncherHelper.setBindingManagerForTesting(bindingManager);
         assertFalse(bindingManager.isChildProcessCreated());
 
         AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
@@ -673,7 +673,7 @@
     @ParameterizedTest.Set
     public void testRendererPriorityLow() throws Throwable {
         MockBindingManager bindingManager = new MockBindingManager();
-        ChildProcessLauncher.setBindingManagerForTesting(bindingManager);
+        ChildProcessLauncherHelper.setBindingManagerForTesting(bindingManager);
         assertFalse(bindingManager.isChildProcessCreated());
 
         final AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
@@ -703,7 +703,7 @@
     @ParameterizedTest.Set
     public void testRendererPriorityManaged() throws Throwable {
         MockBindingManager bindingManager = new MockBindingManager();
-        ChildProcessLauncher.setBindingManagerForTesting(bindingManager);
+        ChildProcessLauncherHelper.setBindingManagerForTesting(bindingManager);
         assertFalse(bindingManager.isChildProcessCreated());
 
         final AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
diff --git a/ash/system/tray/tray_popup_item_style.cc b/ash/system/tray/tray_popup_item_style.cc
index fd869f0..f803785 100644
--- a/ash/system/tray/tray_popup_item_style.cc
+++ b/ash/system/tray/tray_popup_item_style.cc
@@ -70,11 +70,11 @@
   const gfx::FontList& base_font_list = views::Label::GetDefaultFontList();
   switch (font_style_) {
     case FontStyle::TITLE:
-      label->SetFontList(base_font_list.Derive(2, gfx::Font::NORMAL,
+      label->SetFontList(base_font_list.Derive(1, gfx::Font::NORMAL,
                                                gfx::Font::Weight::MEDIUM));
       break;
     case FontStyle::DEFAULT_VIEW_LABEL:
-      label->SetFontList(base_font_list.Derive(2, gfx::Font::NORMAL,
+      label->SetFontList(base_font_list.Derive(1, gfx::Font::NORMAL,
                                                gfx::Font::Weight::NORMAL));
       break;
     case FontStyle::SUB_HEADER:
diff --git a/build/toolchain/mac/compile_xcassets.py b/build/toolchain/mac/compile_xcassets.py
index ac0742e..7e2dbca 100644
--- a/build/toolchain/mac/compile_xcassets.py
+++ b/build/toolchain/mac/compile_xcassets.py
@@ -62,6 +62,12 @@
       continue
     if line == os.path.abspath(output):
       continue
+    # crbug.com/730054 Xcode 9's beta introduced a CoreUI(DEBUG) message and
+    # IBMessageChannelErrorDomain message that can be ignored.
+    if line.startswith('CoreUI(DEBUG)'):
+      continue
+    if 'Error Domain=IBMessageChannelErrorDomain Code=4' in line:
+      continue
     sys.stderr.write(stdout)
     sys.exit(1)
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivitySessionTracker.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivitySessionTracker.java
index 9d37e1e..b5baba6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivitySessionTracker.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivitySessionTracker.java
@@ -33,7 +33,7 @@
 import org.chromium.chrome.browser.share.ShareHelper;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.util.FeatureUtilities;
-import org.chromium.content.browser.ChildProcessLauncher;
+import org.chromium.content.browser.ChildProcessLauncherHelper;
 
 import java.lang.ref.WeakReference;
 
@@ -133,7 +133,7 @@
      */
     private void onForegroundSessionStart() {
         UmaUtils.recordForegroundStartTime();
-        ChildProcessLauncher.onBroughtToForeground();
+        ChildProcessLauncherHelper.onBroughtToForeground();
         updatePasswordEchoState();
         FontSizePrefs.getInstance(mApplication).onSystemFontScaleChanged();
         updateAcceptLanguages();
@@ -160,7 +160,7 @@
         mIsStarted = false;
         mPowerBroadcastReceiver.onForegroundSessionEnd();
 
-        ChildProcessLauncher.onSentToBackground();
+        ChildProcessLauncherHelper.onSentToBackground();
         IntentHandler.clearPendingReferrer();
         IntentHandler.clearPendingIncognitoUrl();
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
index 0caac7e5..eb91020 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
@@ -52,7 +52,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.util.IntentUtils;
 import org.chromium.chrome.browser.util.UrlUtilities;
-import org.chromium.content.browser.ChildProcessLauncher;
+import org.chromium.content.browser.ChildProcessLauncherHelper;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.common.Referrer;
@@ -232,7 +232,7 @@
             System.exit(-1);
         }
         final Context context = app.getApplicationContext();
-        ChildProcessLauncher.warmUp(context);
+        ChildProcessLauncherHelper.warmUp(context);
         ChromeBrowserInitializer.initNetworkChangeNotifier(context);
         WarmupManager.getInstance().initializeViewHierarchy(
                 context, R.layout.custom_tabs_control_container, R.layout.custom_tabs_toolbar);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/GroupedPermissionInfoBar.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/GroupedPermissionInfoBar.java
index c55a1149..144d434 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/infobar/GroupedPermissionInfoBar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/GroupedPermissionInfoBar.java
@@ -16,10 +16,10 @@
     private final String[] mPermissionText;
     private final int[] mPermissionIcons;
 
-    GroupedPermissionInfoBar(Tab tab, int[] contentSettingsTypes, String message, String buttonOk,
-            String buttonCancel, boolean showPersistenceToggle, String[] permissionText,
-            int[] permissionIcons) {
-        super(tab, contentSettingsTypes, 0, null, message, null, buttonOk, buttonCancel,
+    GroupedPermissionInfoBar(Tab tab, int[] contentSettingsTypes, String message, String linkText,
+            String buttonOk, String buttonCancel, boolean showPersistenceToggle,
+            String[] permissionText, int[] permissionIcons) {
+        super(tab, contentSettingsTypes, 0, null, message, linkText, buttonOk, buttonCancel,
                 showPersistenceToggle);
         mPermissionText = permissionText;
         mPermissionIcons = permissionIcons;
@@ -62,10 +62,10 @@
      */
     @CalledByNative
     private static InfoBar create(Tab tab, int[] contentSettingsTypes, String message,
-            String buttonOk, String buttonCancel, boolean showPersistenceToggle,
+            String linkText, String buttonOk, String buttonCancel, boolean showPersistenceToggle,
             String[] permissionText, int[] permissionIcons) {
         GroupedPermissionInfoBar infobar =
-                new GroupedPermissionInfoBar(tab, contentSettingsTypes, message, buttonOk,
+                new GroupedPermissionInfoBar(tab, contentSettingsTypes, message, linkText, buttonOk,
                         buttonCancel, showPersistenceToggle, permissionText, permissionIcons);
         return infobar;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitTaskRunner.java b/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitTaskRunner.java
index 73a6dd3..52f90ab 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitTaskRunner.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitTaskRunner.java
@@ -16,7 +16,7 @@
 import org.chromium.chrome.browser.ChromeActivitySessionTracker;
 import org.chromium.chrome.browser.ChromeVersionInfo;
 import org.chromium.components.variations.firstrun.VariationsSeedFetcher;
-import org.chromium.content.browser.ChildProcessLauncher;
+import org.chromium.content.browser.ChildProcessLauncherHelper;
 
 import java.util.concurrent.Executor;
 
@@ -112,7 +112,7 @@
         }
 
         if (allocateChildConnection) {
-            ChildProcessLauncher.warmUp(ContextUtils.getApplicationContext());
+            ChildProcessLauncherHelper.warmUp(ContextUtils.getApplicationContext());
         }
         mLoadTask = new LoadTask();
         mLoadTask.executeOnExecutor(getExecutor());
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
index ce22e1e9..9057dfe 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
@@ -76,7 +76,7 @@
 import org.chromium.components.background_task_scheduler.BackgroundTaskSchedulerFactory;
 import org.chromium.components.minidump_uploader.CrashFileManager;
 import org.chromium.components.signin.AccountManagerHelper;
-import org.chromium.content.browser.ChildProcessLauncher;
+import org.chromium.content.browser.ChildProcessLauncherHelper;
 import org.chromium.content.common.ContentSwitches;
 import org.chromium.device.geolocation.LocationProviderFactory;
 import org.chromium.printing.PrintDocumentAdapterWrapper;
@@ -627,7 +627,7 @@
     private void startModerateBindingManagementIfNeeded(Context context) {
         // Moderate binding doesn't apply to low end devices.
         if (SysUtils.isLowEndDevice()) return;
-        ChildProcessLauncher.startModerateBindingManagement(context);
+        ChildProcessLauncherHelper.startModerateBindingManagement(context);
     }
 
     @SuppressWarnings("deprecation") // InputMethodSubtype.getLocale() deprecated in API 24
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java
index f851fb1..c8b2e8d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java
@@ -104,6 +104,7 @@
 
     /**
      * Called after data has been retrieved from storage.
+     * @param <T> The type of the data being retrieved.
      */
     public interface FetchCallback<T> {
         public void onDataRetrieved(T readObject);
@@ -484,8 +485,7 @@
      */
     boolean didPreviousUpdateSucceed() {
         long lastUpdateCompletionTime = getLastWebApkUpdateRequestCompletionTime();
-        if (lastUpdateCompletionTime == WebappDataStorage.LAST_USED_INVALID
-                || lastUpdateCompletionTime == WebappDataStorage.LAST_USED_UNSET) {
+        if (lastUpdateCompletionTime == WebappDataStorage.LAST_USED_INVALID) {
             return true;
         }
         return getDidLastWebApkUpdateRequestSucceed();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/BindingManagerIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/BindingManagerIntegrationTest.java
index 536e19a..3a313877 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/BindingManagerIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/BindingManagerIntegrationTest.java
@@ -35,7 +35,7 @@
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.content.browser.BindingManager;
 import org.chromium.content.browser.ChildProcessConnection;
-import org.chromium.content.browser.ChildProcessLauncher;
+import org.chromium.content.browser.ChildProcessLauncherHelper;
 import org.chromium.content.browser.test.ChildProcessAllocatorSettings;
 import org.chromium.content.browser.test.util.Criteria;
 import org.chromium.content.browser.test.util.CriteriaHelper;
@@ -298,7 +298,7 @@
         });
 
         // Kill the renderer and wait for the crash to be noted by the browser process.
-        Assert.assertTrue(ChildProcessLauncher.crashProcessForTesting(
+        Assert.assertTrue(ChildProcessLauncherHelper.crashProcessForTesting(
                 tabs[1].getContentViewCore().getCurrentRenderProcessId()));
 
         CriteriaHelper.pollInstrumentationThread(
@@ -367,7 +367,7 @@
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
         // Kill the renderer and wait for the crash to be noted by the browser process.
-        Assert.assertTrue(ChildProcessLauncher.crashProcessForTesting(
+        Assert.assertTrue(ChildProcessLauncherHelper.crashProcessForTesting(
                 tab.getContentViewCore().getCurrentRenderProcessId()));
 
         CriteriaHelper.pollInstrumentationThread(
@@ -444,7 +444,7 @@
         });
         ChromeTabUtils.waitForTabPageLoaded(tabs[0], "about:blank");
         ChromeTabUtils.waitForTabPageLoaded(tabs[1], "about:blank");
-        // At this point 3 sanboxed services are allocated; the initial one + 2 new tabs.
+        // At this point 3 sandboxed services are allocated; the initial one + 2 new tabs.
         Assert.assertFalse(mBindingManager.isReleaseAllModerateBindingsCalled());
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
@@ -456,7 +456,7 @@
             }
         });
         ChromeTabUtils.waitForTabPageLoaded(tabs[2], "about:blank");
-        // At this point all the sanboxed services are allocated.
+        // At this point all the sandboxed services are allocated.
         mBindingManager.assertIsReleaseAllModerateBindingsCalled();
     }
 
@@ -497,7 +497,7 @@
             }
         });
 
-        Assert.assertTrue(ChildProcessLauncher.crashProcessForTesting(
+        Assert.assertTrue(ChildProcessLauncherHelper.crashProcessForTesting(
                 tabs[1].getContentViewCore().getCurrentRenderProcessId()));
 
         CriteriaHelper.pollInstrumentationThread(
@@ -548,7 +548,7 @@
     public void setUp() throws Exception {
         // Hook in the test binding manager.
         mBindingManager = new MockBindingManager();
-        ChildProcessLauncher.setBindingManagerForTesting(mBindingManager);
+        ChildProcessLauncherHelper.setBindingManagerForTesting(mBindingManager);
 
         mActivityTestRule.startMainActivityOnBlankPage();
 
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 1e03569..a64f30a 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -2250,9 +2250,6 @@
      flag_descriptions::kMaterialDesignIncognitoNTPName,
      flag_descriptions::kMaterialDesignIncognitoNTPDescription, kOsAll,
      FEATURE_VALUE_TYPE(features::kMaterialDesignIncognitoNTP)},
-    {"enable-md-settings", flag_descriptions::kEnableMaterialDesignSettingsName,
-     flag_descriptions::kEnableMaterialDesignSettingsDescription, kOsDesktop,
-     FEATURE_VALUE_TYPE(features::kMaterialDesignSettings)},
     {"safe-search-url-reporting",
      flag_descriptions::kSafeSearchUrlReportingName,
      flag_descriptions::kSafeSearchUrlReportingDescription, kOsAll,
diff --git a/chrome/browser/autocomplete/chrome_autocomplete_provider_client.cc b/chrome/browser/autocomplete/chrome_autocomplete_provider_client.cc
index dd567035..f5f29cc 100644
--- a/chrome/browser/autocomplete/chrome_autocomplete_provider_client.cc
+++ b/chrome/browser/autocomplete/chrome_autocomplete_provider_client.cc
@@ -6,7 +6,6 @@
 
 #include <stddef.h>
 
-#include "base/feature_list.h"
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/strings/utf_string_conversions.h"
@@ -24,7 +23,6 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "chrome/browser/sync/profile_sync_service_factory.h"
-#include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/common/url_constants.h"
 #include "components/browser_sync/profile_sync_service.h"
@@ -187,13 +185,6 @@
     builtins.push_back(settings +
                        base::ASCIIToUTF16(kChromeSettingsSubPages[i]));
   }
-
-  if (!base::FeatureList::IsEnabled(features::kMaterialDesignSettings)) {
-    builtins.push_back(
-        settings +
-        base::ASCIIToUTF16(
-            chrome::kDeprecatedOptionsContentSettingsExceptionsSubPage));
-  }
 #endif
 
   return builtins;
diff --git a/chrome/browser/browser_about_handler.cc b/chrome/browser/browser_about_handler.cc
index 8fc14bc..9a708c27 100644
--- a/chrome/browser/browser_about_handler.cc
+++ b/chrome/browser/browser_about_handler.cc
@@ -87,31 +87,15 @@
     host = chrome::kChromeUIUberHost;
     path = chrome::kChromeUIExtensionsHost;
 #endif  // BUILDFLAG(ENABLE_EXTENSIONS)
-  // Redirect chrome://history.
   } else if (host == chrome::kChromeUIHistoryHost) {
+    // Redirect chrome://history.
     path = url->path();
-  // Redirect chrome://settings, unless MD settings is enabled.
   } else if (host == chrome::kChromeUISettingsHost) {
-    if (base::FeatureList::IsEnabled(features::kMaterialDesignSettings)) {
-      return true;  // Prevent further rewriting - this is a valid URL.
-    } else if (::switches::SettingsWindowEnabled()) {
-      host = chrome::kChromeUISettingsFrameHost;
-    } else {
-      host = chrome::kChromeUIUberHost;
-      path = chrome::kChromeUISettingsHost + url->path();
-    }
-  // Redirect chrome://help, unless MD settings is enabled.
+    // Redirect chrome://settings.
+    return true;  // Prevent further rewriting - this is a valid URL.
   } else if (host == chrome::kChromeUIHelpHost) {
-    if (base::FeatureList::IsEnabled(features::kMaterialDesignSettings)) {
-      return false;  // Handled in the HandleWebUI handler.
-    } else if (::switches::SettingsWindowEnabled()) {
-      host = chrome::kChromeUISettingsFrameHost;
-      if (url->path().empty() || url->path() == "/")
-        path = chrome::kChromeUIHelpHost;
-    } else {
-      host = chrome::kChromeUIUberHost;
-      path = chrome::kChromeUIHelpHost + url->path();
-    }
+    // Redirect chrome://help, unless MD settings is enabled.
+    return false;  // Handled in the HandleWebUI handler.
   }
 
   GURL::Replacements replacements;
diff --git a/chrome/browser/browser_about_handler_unittest.cc b/chrome/browser/browser_about_handler_unittest.cc
index 03e2ebb8..7aa32e78 100644
--- a/chrome/browser/browser_about_handler_unittest.cc
+++ b/chrome/browser/browser_about_handler_unittest.cc
@@ -12,8 +12,6 @@
 
 #include "base/macros.h"
 #include "base/message_loop/message_loop.h"
-#include "base/test/scoped_feature_list.h"
-#include "chrome/common/chrome_features.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/test/base/testing_profile.h"
 #include "content/public/browser/navigation_controller.h"
@@ -78,50 +76,7 @@
   TestWillHandleBrowserAboutURL(test_cases);
 }
 
-#if defined(OS_CHROMEOS)
-// Chrome OS defaults to showing Options in a window and including About in
-// Options.
-TEST_F(BrowserAboutHandlerTest, WillHandleBrowserAboutURLForOptionsChromeOS) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(features::kMaterialDesignSettings);
-
-  std::string chrome_prefix(content::kChromeUIScheme);
-  chrome_prefix.append(url::kStandardSchemeSeparator);
-  std::vector<AboutURLTestCase> test_cases(
-      {{GURL(chrome_prefix + chrome::kChromeUISettingsHost),
-        GURL(chrome_prefix + chrome::kChromeUISettingsFrameHost)},
-       {GURL(chrome_prefix + chrome::kChromeUIHelpHost),
-        GURL(chrome_prefix + chrome::kChromeUISettingsFrameHost + "/" +
-             chrome::kChromeUIHelpHost)}});
-  TestWillHandleBrowserAboutURL(test_cases);
-}
-
-#else
-TEST_F(BrowserAboutHandlerTest, WillHandleBrowserAboutURLForOptions) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(features::kMaterialDesignSettings);
-
-  std::string chrome_prefix(content::kChromeUIScheme);
-  chrome_prefix.append(url::kStandardSchemeSeparator);
-  std::vector<AboutURLTestCase> test_cases(
-      {{
-           GURL(chrome_prefix + chrome::kChromeUISettingsHost),
-           GURL(chrome_prefix + chrome::kChromeUIUberHost + "/" +
-                chrome::kChromeUISettingsHost + "/"),
-       },
-       {
-           GURL(chrome_prefix + chrome::kChromeUIHelpHost),
-           GURL(chrome_prefix + chrome::kChromeUIUberHost + "/" +
-                chrome::kChromeUIHelpHost + "/"),
-       }});
-  TestWillHandleBrowserAboutURL(test_cases);
-}
-#endif
-
 TEST_F(BrowserAboutHandlerTest, WillHandleBrowserAboutURLForMDSettings) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(features::kMaterialDesignSettings);
-
   std::string chrome_prefix(content::kChromeUIScheme);
   chrome_prefix.append(url::kStandardSchemeSeparator);
   std::vector<AboutURLTestCase> test_cases(
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 2798f17..c0d10f9 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -658,15 +658,13 @@
 
 // Handles rewriting Web UI URLs.
 bool HandleWebUI(GURL* url, content::BrowserContext* browser_context) {
-  if (base::FeatureList::IsEnabled(features::kMaterialDesignSettings)) {
-    // Rewrite chrome://help and chrome://chrome to chrome://settings/help.
-    if (url->host() == chrome::kChromeUIHelpHost ||
-        (url->host() == chrome::kChromeUIUberHost &&
-         (url->path().empty() || url->path() == "/"))) {
-      *url = ReplaceURLHostAndPath(*url, chrome::kChromeUISettingsHost,
-                                   chrome::kChromeUIHelpHost);
-      return true;  // Return true to update the displayed URL.
-    }
+  // Rewrite chrome://help and chrome://chrome to chrome://settings/help.
+  if (url->host() == chrome::kChromeUIHelpHost ||
+      (url->host() == chrome::kChromeUIUberHost &&
+       (url->path().empty() || url->path() == "/"))) {
+    *url = ReplaceURLHostAndPath(*url, chrome::kChromeUISettingsHost,
+                                 chrome::kChromeUIHelpHost);
+    return true;  // Return true to update the displayed URL.
   }
 
   // Do not handle special URLs such as "about:foo"
@@ -706,10 +704,8 @@
 bool HandleWebUIReverse(GURL* url, content::BrowserContext* browser_context) {
   // No need to actually reverse-rewrite the URL, but return true to update the
   // displayed URL when rewriting chrome://help to chrome://settings/help.
-  if (base::FeatureList::IsEnabled(features::kMaterialDesignSettings) &&
-      url->host() == chrome::kChromeUISettingsHost) {
+  if (url->host() == chrome::kChromeUISettingsHost)
     return true;
-  }
 
   if (!url->is_valid() || !url->SchemeIs(content::kChromeUIScheme))
     return false;
diff --git a/chrome/browser/chrome_content_browser_client_browsertest.cc b/chrome/browser/chrome_content_browser_client_browsertest.cc
index e0e82fe9..ede5c9c 100644
--- a/chrome/browser/chrome_content_browser_client_browsertest.cc
+++ b/chrome/browser/chrome_content_browser_client_browsertest.cc
@@ -2,20 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "chrome/browser/chrome_content_browser_client.h"
+
 #include "base/command_line.h"
 #include "base/macros.h"
-#include "base/test/scoped_feature_list.h"
-#include "build/build_config.h"
-#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_switches.h"
-#include "chrome/common/pref_names.h"
-#include "chrome/common/url_constants.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
-#include "components/prefs/pref_service.h"
 #include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/web_contents.h"
@@ -25,129 +20,34 @@
 
 namespace content {
 
-class ChromeContentBrowserClientBrowserTest : public InProcessBrowserTest {
- public:
-  // Returns the last committed navigation entry of the first tab. May be NULL
-  // if there is no such entry.
-  NavigationEntry* GetLastCommittedEntry() {
-    return browser()->tab_strip_model()->GetWebContentsAt(0)->
-        GetController().GetLastCommittedEntry();
-  }
-
-  void SetUpInProcessBrowserTestFixture() override {
-    disable_md_settings_.InitAndDisableFeature(
-        features::kMaterialDesignSettings);
-  }
-
-#if defined(OS_CHROMEOS)
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    command_line->AppendSwitch(switches::kDisableSettingsWindow);
-  }
-#endif
-
- private:
-  base::test::ScopedFeatureList disable_md_settings_;
-};
-
-IN_PROC_BROWSER_TEST_F(ChromeContentBrowserClientBrowserTest,
-                       UberURLHandler_SettingsPage) {
-  const GURL url_short("chrome://settings/");
-  const GURL url_long("chrome://chrome/settings/");
-
-  ui_test_utils::NavigateToURL(browser(), url_short);
-  NavigationEntry* entry = GetLastCommittedEntry();
-
-  ASSERT_TRUE(entry != NULL);
-  EXPECT_EQ(url_long, entry->GetURL());
-  EXPECT_EQ(url_short, entry->GetVirtualURL());
-}
-
-IN_PROC_BROWSER_TEST_F(ChromeContentBrowserClientBrowserTest,
-                       UberURLHandler_ContentSettingsPage) {
-  const GURL url_short("chrome://settings/content");
-  const GURL url_long("chrome://chrome/settings/content");
-
-  ui_test_utils::NavigateToURL(browser(), url_short);
-  NavigationEntry* entry = GetLastCommittedEntry();
-
-  ASSERT_TRUE(entry != NULL);
-  EXPECT_EQ(url_long, entry->GetURL());
-  EXPECT_EQ(url_short, entry->GetVirtualURL());
-}
-
-IN_PROC_BROWSER_TEST_F(ChromeContentBrowserClientBrowserTest,
-                       UberURLHandler_AboutPage) {
-  const GURL url("chrome://chrome/");
-
-  ui_test_utils::NavigateToURL(browser(), url);
-  NavigationEntry* entry = GetLastCommittedEntry();
-
-  ASSERT_TRUE(entry != NULL);
-  EXPECT_EQ(url, entry->GetURL());
-  EXPECT_EQ(url, entry->GetVirtualURL());
-}
-
-IN_PROC_BROWSER_TEST_F(ChromeContentBrowserClientBrowserTest,
-                       UberURLHandler_NewTabPageOverride) {
-  PrefService* prefs = browser()->profile()->GetPrefs();
-  static const char kOverrideUrl[] = "http://override.com";
-  prefs->SetString(prefs::kNewTabPageLocationOverride, kOverrideUrl);
-  const GURL ntp_url(chrome::kChromeUINewTabURL);
-
-  ui_test_utils::NavigateToURL(browser(), ntp_url);
-  NavigationEntry* entry = GetLastCommittedEntry();
-
-  ASSERT_TRUE(entry != NULL);
-  EXPECT_TRUE(entry->GetVirtualURL().is_valid());
-  EXPECT_EQ(GURL(kOverrideUrl), entry->GetVirtualURL());
-
-  prefs->SetString(prefs::kNewTabPageLocationOverride, "");
-
-  ui_test_utils::NavigateToURL(browser(), ntp_url);
-  entry = GetLastCommittedEntry();
-
-  ASSERT_TRUE(entry != NULL);
-  EXPECT_TRUE(entry->GetVirtualURL().is_valid());
-  EXPECT_EQ(ntp_url, entry->GetVirtualURL());
-}
-
-IN_PROC_BROWSER_TEST_F(ChromeContentBrowserClientBrowserTest,
-                       UberURLHandler_EmptyHost) {
-  const GURL url("chrome://chrome//foo");
-
-  ui_test_utils::NavigateToURL(browser(), url);
-  NavigationEntry* entry = GetLastCommittedEntry();
-
-  ASSERT_TRUE(entry != NULL);
-  EXPECT_TRUE(entry->GetVirtualURL().is_valid());
-  EXPECT_EQ(url, entry->GetVirtualURL());
-}
-
 // Use a test class with SetUpCommandLine to ensure the flag is sent to the
 // first renderer process.
-class ChromeContentBrowserClientSitePerProcessTest
-    : public ChromeContentBrowserClientBrowserTest {
+class ChromeContentBrowserClientBrowserTest : public InProcessBrowserTest {
  public:
-  ChromeContentBrowserClientSitePerProcessTest() {}
+  ChromeContentBrowserClientBrowserTest() {}
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
-    content::IsolateAllSitesForTesting(command_line);
+    IsolateAllSitesForTesting(command_line);
   }
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(ChromeContentBrowserClientSitePerProcessTest);
+  DISALLOW_COPY_AND_ASSIGN(ChromeContentBrowserClientBrowserTest);
 };
 
 // Test that a basic navigation works in --site-per-process mode.  This prevents
 // regressions when that mode calls out into the ChromeContentBrowserClient,
 // such as http://crbug.com/164223.
-IN_PROC_BROWSER_TEST_F(ChromeContentBrowserClientSitePerProcessTest,
+IN_PROC_BROWSER_TEST_F(ChromeContentBrowserClientBrowserTest,
                        SitePerProcessNavigation) {
   ASSERT_TRUE(embedded_test_server()->Start());
   const GURL url(embedded_test_server()->GetURL("/title1.html"));
 
   ui_test_utils::NavigateToURL(browser(), url);
-  NavigationEntry* entry = GetLastCommittedEntry();
+  NavigationEntry* entry = browser()
+                               ->tab_strip_model()
+                               ->GetWebContentsAt(0)
+                               ->GetController()
+                               .GetLastCommittedEntry();
 
   ASSERT_TRUE(entry != NULL);
   EXPECT_EQ(url, entry->GetURL());
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 54de7a89..db3c5f7 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -24,15 +24,6 @@
     "If enabled, the chrome://md-policy URL loads the Material Design "
     "policy page.";
 
-//  Material Design version of chrome://settings
-
-const char kEnableMaterialDesignSettingsName[] =
-    "Enable Material Design settings";
-
-const char kEnableMaterialDesignSettingsDescription[] =
-    "If enabled, the chrome://settings/ URL loads the Material Design "
-    "settings page.";
-
 //  Material Design version of chrome://extensions
 
 const char kEnableMaterialDesignExtensionsName[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 91d0c3f4..dce25c2 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -227,9 +227,6 @@
 extern const char kEnableMaterialDesignPolicyPageName[];
 extern const char kEnableMaterialDesignPolicyPageDescription[];
 
-extern const char kEnableMaterialDesignSettingsName[];
-extern const char kEnableMaterialDesignSettingsDescription[];
-
 extern const char kEnableMidiManagerDynamicInstantiationName[];
 extern const char kEnableMidiManagerDynamicInstantiationDescription[];
 
diff --git a/chrome/browser/notifications/platform_notification_service_interactive_uitest.cc b/chrome/browser/notifications/platform_notification_service_interactive_uitest.cc
index 5a9a85e4..936d5dd 100644
--- a/chrome/browser/notifications/platform_notification_service_interactive_uitest.cc
+++ b/chrome/browser/notifications/platform_notification_service_interactive_uitest.cc
@@ -413,10 +413,7 @@
   EXPECT_DOUBLE_EQ(5.5, GetEngagementScore(origin));
 
   std::string url = web_contents->GetLastCommittedURL().spec();
-  if (base::FeatureList::IsEnabled(features::kMaterialDesignSettings))
-    ASSERT_EQ("chrome://settings/content/notifications", url);
-  else
-    ASSERT_EQ("chrome://settings/contentExceptions#notifications", url);
+  ASSERT_EQ("chrome://settings/content/notifications", url);
 }
 
 IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
diff --git a/chrome/browser/page_load_metrics/observers/subresource_filter_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/subresource_filter_metrics_observer.cc
index 1ee8fca..fc22351 100644
--- a/chrome/browser/page_load_metrics/observers/subresource_filter_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/subresource_filter_metrics_observer.cc
@@ -135,6 +135,28 @@
 
 }  // namespace
 
+SubresourceFilterMetricsObserver::SubresourceFilterMetricsObserver()
+    : scoped_observer_(this) {}
+SubresourceFilterMetricsObserver::~SubresourceFilterMetricsObserver() = default;
+
+page_load_metrics::PageLoadMetricsObserver::ObservePolicy
+SubresourceFilterMetricsObserver::OnStart(
+    content::NavigationHandle* navigation_handle,
+    const GURL& currently_committed_url,
+    bool started_in_foreground) {
+  navigation_handle_ = navigation_handle;
+  auto* observer_manager =
+      subresource_filter::SubresourceFilterObserverManager::FromWebContents(
+          navigation_handle->GetWebContents());
+  // |observer_manager| isn't constructed if the feature for subresource
+  // filtering isn't enabled.
+  if (observer_manager) {
+    scoped_observer_.Add(observer_manager);
+    return CONTINUE_OBSERVING;
+  }
+  return STOP_OBSERVING;
+}
+
 page_load_metrics::PageLoadMetricsObserver::ObservePolicy
 SubresourceFilterMetricsObserver::FlushMetricsOnAppEnterBackground(
     const page_load_metrics::mojom::PageLoadTiming& timing,
@@ -152,13 +174,17 @@
 SubresourceFilterMetricsObserver::OnCommit(
     content::NavigationHandle* navigation_handle,
     ukm::SourceId source_id) {
-  const auto* subres_filter =
-      ContentSubresourceFilterDriverFactory::FromWebContents(
-          navigation_handle->GetWebContents());
-  if (subres_filter)
-    LogActivationDecisionMetrics(
-        navigation_handle,
-        subres_filter->GetActivationDecisionForLastCommittedPageLoad());
+  // Just in case OnSubresourceFilterGoingAway is called before this point.
+  if (!activation_decision_) {
+    DCHECK(!scoped_observer_.IsObservingSources());
+    return STOP_OBSERVING;
+  }
+
+  did_commit_ = true;
+  navigation_handle_ = nullptr;
+  DCHECK(scoped_observer_.IsObservingSources());
+  LogActivationDecisionMetrics(navigation_handle, *activation_decision_);
+  scoped_observer_.RemoveAll();
   return CONTINUE_OBSERVING;
 }
 
@@ -298,6 +324,23 @@
   played_media_ = true;
 }
 
+void SubresourceFilterMetricsObserver::OnSubresourceFilterGoingAway() {
+  scoped_observer_.RemoveAll();
+}
+
+void SubresourceFilterMetricsObserver::OnPageActivationComputed(
+    content::NavigationHandle* navigation_handle,
+    subresource_filter::ActivationDecision activation_decision,
+    const subresource_filter::ActivationState& activation_state) {
+  // Make sure we don't get notifications from subsequent navigations.
+  if (navigation_handle != navigation_handle_)
+    return;
+  // Ensure this will always be called at most once before commit.
+  DCHECK(!did_commit_);
+  DCHECK(!activation_decision_);
+  activation_decision_ = activation_decision;
+}
+
 void SubresourceFilterMetricsObserver::OnGoingAway(
     const page_load_metrics::mojom::PageLoadTiming& timing,
     const page_load_metrics::PageLoadExtraInfo& info,
diff --git a/chrome/browser/page_load_metrics/observers/subresource_filter_metrics_observer.h b/chrome/browser/page_load_metrics/observers/subresource_filter_metrics_observer.h
index 1ee7c07..c45cf79 100644
--- a/chrome/browser/page_load_metrics/observers/subresource_filter_metrics_observer.h
+++ b/chrome/browser/page_load_metrics/observers/subresource_filter_metrics_observer.h
@@ -6,7 +6,11 @@
 #define CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_SUBRESOURCE_FILTER_METRICS_OBSERVER_H_
 
 #include "base/macros.h"
+#include "base/optional.h"
+#include "base/scoped_observer.h"
 #include "chrome/browser/page_load_metrics/page_load_metrics_observer.h"
+#include "components/subresource_filter/content/browser/subresource_filter_observer.h"
+#include "components/subresource_filter/content/browser/subresource_filter_observer_manager.h"
 #include "components/ukm/ukm_source.h"
 
 namespace internal {
@@ -59,12 +63,16 @@
 }  // namespace internal
 
 class SubresourceFilterMetricsObserver
-    : public page_load_metrics::PageLoadMetricsObserver {
+    : public page_load_metrics::PageLoadMetricsObserver,
+      public subresource_filter::SubresourceFilterObserver {
  public:
-  SubresourceFilterMetricsObserver() = default;
-  ~SubresourceFilterMetricsObserver() override = default;
+  SubresourceFilterMetricsObserver();
+  ~SubresourceFilterMetricsObserver() override;
 
   // page_load_metrics::PageLoadMetricsObserver:
+  ObservePolicy OnStart(content::NavigationHandle* navigation_handle,
+                        const GURL& currently_committed_url,
+                        bool started_in_foreground) override;
   ObservePolicy OnCommit(content::NavigationHandle* navigation_handle,
                          ukm::SourceId source_id) override;
   ObservePolicy FlushMetricsOnAppEnterBackground(
@@ -95,18 +103,35 @@
       bool is_in_main_frame) override;
 
  private:
+  // subresource_filter::SubresourceFilterObserver:
+  void OnSubresourceFilterGoingAway() override;
+  void OnPageActivationComputed(
+      content::NavigationHandle* navigation_handle,
+      subresource_filter::ActivationDecision activation_decision,
+      const subresource_filter::ActivationState& activation_state) override;
+
   void OnGoingAway(const page_load_metrics::mojom::PageLoadTiming& timing,
                    const page_load_metrics::PageLoadExtraInfo& info,
                    base::TimeTicks app_background_time);
 
+  base::Optional<subresource_filter::ActivationDecision> activation_decision_;
+
+  ScopedObserver<subresource_filter::SubresourceFilterObserverManager,
+                 subresource_filter::SubresourceFilterObserver>
+      scoped_observer_;
+
   int64_t network_bytes_ = 0;
   int64_t cache_bytes_ = 0;
 
   int num_network_resources_ = 0;
   int num_cache_resources_ = 0;
 
+  // Used as a unique id for the ongoing navigation. Do not use after OnCommit.
+  content::NavigationHandle* navigation_handle_ = nullptr;
+
   bool subresource_filter_observed_ = false;
   bool played_media_ = false;
+  bool did_commit_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(SubresourceFilterMetricsObserver);
 };
diff --git a/chrome/browser/page_load_metrics/observers/subresource_filter_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/subresource_filter_metrics_observer_unittest.cc
index c9eb705..77292ee 100644
--- a/chrome/browser/page_load_metrics/observers/subresource_filter_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/subresource_filter_metrics_observer_unittest.cc
@@ -4,24 +4,46 @@
 
 #include "chrome/browser/page_load_metrics/observers/subresource_filter_metrics_observer.h"
 
+#include <memory>
+
 #include "base/memory/ptr_util.h"
 #include "chrome/browser/page_load_metrics/observers/page_load_metrics_observer_test_harness.h"
+#include "components/subresource_filter/content/browser/subresource_filter_observer_manager.h"
+#include "components/subresource_filter/core/common/activation_decision.h"
+#include "components/subresource_filter/core/common/activation_level.h"
+#include "components/subresource_filter/core/common/activation_state.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/test/navigation_simulator.h"
+#include "url/gurl.h"
 
 namespace {
 const char kDefaultTestUrl[] = "https://example.com/";
+const char kDefaultTestUrlWithActivation[] = "https://example-activation.com/";
 }  // namespace
 
 class SubresourceFilterMetricsObserverTest
     : public page_load_metrics::PageLoadMetricsObserverTestHarness {
  public:
+  SubresourceFilterMetricsObserverTest() {}
+  ~SubresourceFilterMetricsObserverTest() override {}
+
+  void SetUp() override {
+    page_load_metrics::PageLoadMetricsObserverTestHarness::SetUp();
+    subresource_filter::SubresourceFilterObserverManager::CreateForWebContents(
+        web_contents());
+    observer_manager_ =
+        subresource_filter::SubresourceFilterObserverManager::FromWebContents(
+            web_contents());
+  }
+
   void RegisterObservers(page_load_metrics::PageLoadTracker* tracker) override {
     tracker->AddObserver(base::MakeUnique<SubresourceFilterMetricsObserver>());
   }
 
-  bool AnyMetricsRecorded() {
-    return !histogram_tester()
-                .GetTotalCountsForPrefix("PageLoad.Clients.SubresourceFilter.")
-                .empty();
+  size_t TotalMetricsRecorded() {
+    return histogram_tester()
+        .GetTotalCountsForPrefix("PageLoad.Clients.SubresourceFilter.")
+        .size();
   }
 
   void InitializePageLoadTiming(
@@ -44,11 +66,39 @@
         base::TimeDelta::FromMilliseconds(1500);
     PopulateRequiredTimingFields(timing);
   }
+
+  void SimulateNavigateAndCommit(const GURL& url) {
+    std::unique_ptr<content::NavigationSimulator> simulator =
+        content::NavigationSimulator::CreateRendererInitiated(url, main_rfh());
+    simulator->Start();
+    // Simulate an activation notification.
+    content::NavigationHandle* handle = simulator->GetNavigationHandle();
+    if (handle->GetURL() == kDefaultTestUrlWithActivation) {
+      observer_manager_->NotifyPageActivationComputed(
+          handle, subresource_filter::ActivationDecision::ACTIVATED,
+          subresource_filter::ActivationState(
+              subresource_filter::ActivationLevel::ENABLED));
+    } else {
+      observer_manager_->NotifyPageActivationComputed(
+          handle,
+          subresource_filter::ActivationDecision::ACTIVATION_CONDITIONS_NOT_MET,
+          subresource_filter::ActivationState(
+              subresource_filter::ActivationLevel::DISABLED));
+    }
+    simulator->Commit();
+  }
+
+ private:
+  // Owned by the WebContents.
+  subresource_filter::SubresourceFilterObserverManager* observer_manager_ =
+      nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(SubresourceFilterMetricsObserverTest);
 };
 
 TEST_F(SubresourceFilterMetricsObserverTest,
        NoMetricsForNonSubresourceFilteredNavigation) {
-  NavigateAndCommit(GURL(kDefaultTestUrl));
+  SimulateNavigateAndCommit(GURL(kDefaultTestUrl));
 
   page_load_metrics::mojom::PageLoadTiming timing;
   InitializePageLoadTiming(&timing);
@@ -58,11 +108,16 @@
   // metrics.
   NavigateToUntrackedUrl();
 
-  ASSERT_FALSE(AnyMetricsRecorded());
+  EXPECT_EQ(1u, TotalMetricsRecorded());
+  histogram_tester().ExpectBucketCount(
+      internal::kHistogramSubresourceFilterActivationDecision,
+      static_cast<int>(subresource_filter::ActivationDecision::
+                           ACTIVATION_CONDITIONS_NOT_MET),
+      1);
 }
 
 TEST_F(SubresourceFilterMetricsObserverTest, Basic) {
-  NavigateAndCommit(GURL(kDefaultTestUrl));
+  SimulateNavigateAndCommit(GURL(kDefaultTestUrlWithActivation));
 
   page_load_metrics::mojom::PageLoadTiming timing;
   InitializePageLoadTiming(&timing);
@@ -74,7 +129,7 @@
   // Navigate away from the current page to force logging of metrics.
   NavigateToUntrackedUrl();
 
-  ASSERT_TRUE(AnyMetricsRecorded());
+  EXPECT_GT(TotalMetricsRecorded(), 0u);
 
   histogram_tester().ExpectTotalCount(
       internal::kHistogramSubresourceFilterCount, 1);
@@ -153,7 +208,7 @@
 }
 
 TEST_F(SubresourceFilterMetricsObserverTest, Subresources) {
-  NavigateAndCommit(GURL(kDefaultTestUrl));
+  SimulateNavigateAndCommit(GURL(kDefaultTestUrlWithActivation));
 
   SimulateLoadedResource(
       {GURL(), -1 /* frame_tree_node_id */, false /* was_cached */,
@@ -251,7 +306,7 @@
 }
 
 TEST_F(SubresourceFilterMetricsObserverTest, SubresourcesWithMedia) {
-  NavigateAndCommit(GURL(kDefaultTestUrl));
+  SimulateNavigateAndCommit(GURL(kDefaultTestUrlWithActivation));
 
   SimulateMediaPlayed();
 
diff --git a/chrome/browser/permissions/grouped_permission_infobar_delegate_android.cc b/chrome/browser/permissions/grouped_permission_infobar_delegate_android.cc
index fa74251..c4ecd24 100644
--- a/chrome/browser/permissions/grouped_permission_infobar_delegate_android.cc
+++ b/chrome/browser/permissions/grouped_permission_infobar_delegate_android.cc
@@ -52,13 +52,6 @@
   return permission_prompt_->GetMessageTextFragment(position);
 }
 
-void GroupedPermissionInfoBarDelegate::ToggleAccept(size_t position,
-                                                    bool new_value) {
-  DCHECK_LT(position, PermissionCount());
-  if (permission_prompt_)
-    permission_prompt_->ToggleAccept(position, new_value);
-}
-
 base::string16 GroupedPermissionInfoBarDelegate::GetMessageText() const {
   return l10n_util::GetStringFUTF16(
       IDS_PERMISSIONS_BUBBLE_PROMPT,
@@ -88,6 +81,10 @@
     permission_prompt_->Closing();
 }
 
+base::string16 GroupedPermissionInfoBarDelegate::GetLinkText() const {
+  return permission_prompt_->GetLinkText();
+}
+
 GroupedPermissionInfoBarDelegate::GroupedPermissionInfoBarDelegate(
     PermissionPromptAndroid* permission_prompt,
     const GURL& requesting_origin)
@@ -126,6 +123,10 @@
                                                          : IDS_PERMISSION_DENY);
 }
 
+GURL GroupedPermissionInfoBarDelegate::GetLinkURL() const {
+  return permission_prompt_->GetLinkURL();
+}
+
 bool GroupedPermissionInfoBarDelegate::EqualsDelegate(
     infobars::InfoBarDelegate* delegate) const {
   // The PermissionRequestManager doesn't create duplicate infobars so a pointer
diff --git a/chrome/browser/permissions/grouped_permission_infobar_delegate_android.h b/chrome/browser/permissions/grouped_permission_infobar_delegate_android.h
index 148379c94..4bb31f5 100644
--- a/chrome/browser/permissions/grouped_permission_infobar_delegate_android.h
+++ b/chrome/browser/permissions/grouped_permission_infobar_delegate_android.h
@@ -41,14 +41,12 @@
   // Message text to display for an individual permission at |position|.
   base::string16 GetMessageTextFragment(size_t position) const;
 
-  // Toggle accept value for an individual permission at |position|.
-  void ToggleAccept(size_t position, bool new_value);
-
   // ConfirmInfoBarDelegate:
   base::string16 GetMessageText() const override;
   bool Accept() override;
   bool Cancel() override;
   void InfoBarDismissed() override;
+  base::string16 GetLinkText() const override;
 
  protected:
   bool GetAcceptState(size_t position);
@@ -62,6 +60,7 @@
   Type GetInfoBarType() const override;
   int GetButtons() const override;
   base::string16 GetButtonLabel(InfoBarButton button) const override;
+  GURL GetLinkURL() const override;
 
   // InfoBarDelegate:
   bool EqualsDelegate(infobars::InfoBarDelegate* delegate) const override;
diff --git a/chrome/browser/permissions/permission_dialog_delegate.cc b/chrome/browser/permissions/permission_dialog_delegate.cc
index 5113aba..52eb39d 100644
--- a/chrome/browser/permissions/permission_dialog_delegate.cc
+++ b/chrome/browser/permissions/permission_dialog_delegate.cc
@@ -160,9 +160,9 @@
       ResourceMapper::MapFromChromiumId(
           permission_prompt_->GetIconIdForPermission(0)),
       ConvertUTF16ToJavaString(env, permission_prompt_->GetMessageText(0)),
-      // TODO(timloh): Pass the actual link text for EME.
-      ConvertUTF16ToJavaString(env, base::string16()), primaryButtonText,
-      secondaryButtonText, permission_prompt_->ShouldShowPersistenceToggle()));
+      ConvertUTF16ToJavaString(env, permission_prompt_->GetLinkText()),
+      primaryButtonText, secondaryButtonText,
+      permission_prompt_->ShouldShowPersistenceToggle()));
 }
 
 void PermissionDialogDelegate::Accept(JNIEnv* env,
@@ -211,14 +211,11 @@
   // InfoBarService as an owner() to open the link. That will fail since the
   // wrapped delegate has no owner (it hasn't been added as an infobar).
   if (tab_->web_contents()) {
-    if (infobar_delegate_) {
-      tab_->web_contents()->OpenURL(content::OpenURLParams(
-          infobar_delegate_->GetLinkURL(), content::Referrer(),
-          WindowOpenDisposition::NEW_FOREGROUND_TAB, ui::PAGE_TRANSITION_LINK,
-          false));
-    }
-    // TODO(timloh): Show a 'learn more' link in the PermissionRequestManager
-    // codepath for EME.
+    GURL linkURL = infobar_delegate_ ? infobar_delegate_->GetLinkURL()
+                                     : permission_prompt_->GetLinkURL();
+    tab_->web_contents()->OpenURL(content::OpenURLParams(
+        linkURL, content::Referrer(), WindowOpenDisposition::NEW_FOREGROUND_TAB,
+        ui::PAGE_TRANSITION_LINK, false));
   }
 }
 
diff --git a/chrome/browser/permissions/permission_prompt_android.cc b/chrome/browser/permissions/permission_prompt_android.cc
index 21c8cce0..e860404 100644
--- a/chrome/browser/permissions/permission_prompt_android.cc
+++ b/chrome/browser/permissions/permission_prompt_android.cc
@@ -9,6 +9,9 @@
 #include "chrome/browser/permissions/grouped_permission_infobar_delegate_android.h"
 #include "chrome/browser/permissions/permission_dialog_delegate.h"
 #include "chrome/browser/permissions/permission_request.h"
+#include "chrome/common/url_constants.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
 
 PermissionPromptAndroid::PermissionPromptAndroid(
     content::WebContents* web_contents)
@@ -76,11 +79,6 @@
     delegate_->TogglePersist(value);
 }
 
-void PermissionPromptAndroid::ToggleAccept(int index, bool value) {
-  if (delegate_)
-    delegate_->ToggleAccept(index, value);
-}
-
 void PermissionPromptAndroid::Accept() {
   if (delegate_)
     delegate_->Accept();
@@ -129,6 +127,22 @@
   return requests[position]->GetMessageTextFragment();
 }
 
+base::string16 PermissionPromptAndroid::GetLinkText() const {
+  if (GetContentSettingType(0) ==
+      CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER) {
+    return l10n_util::GetStringUTF16(IDS_LEARN_MORE);
+  }
+  return base::string16();
+}
+
+GURL PermissionPromptAndroid::GetLinkURL() const {
+  if (GetContentSettingType(0) ==
+      CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER) {
+    return GURL(chrome::kEnhancedPlaybackNotificationLearnMoreURL);
+  }
+  return GURL();
+}
+
 // static
 std::unique_ptr<PermissionPrompt> PermissionPrompt::Create(
     content::WebContents* web_contents) {
diff --git a/chrome/browser/permissions/permission_prompt_android.h b/chrome/browser/permissions/permission_prompt_android.h
index 5575726..7a92397 100644
--- a/chrome/browser/permissions/permission_prompt_android.h
+++ b/chrome/browser/permissions/permission_prompt_android.h
@@ -10,6 +10,7 @@
 #include "base/strings/string16.h"
 #include "chrome/browser/ui/permission_bubble/permission_prompt.h"
 #include "components/content_settings/core/common/content_settings_types.h"
+#include "url/gurl.h"
 
 namespace content {
 class WebContents;
@@ -31,7 +32,6 @@
 
   void Closing();
   void TogglePersist(bool value);
-  void ToggleAccept(int index, bool value);
   void Accept();
   void Deny();
 
@@ -42,6 +42,9 @@
   base::string16 GetMessageText(size_t position) const;
   base::string16 GetMessageTextFragment(size_t position) const;
 
+  base::string16 GetLinkText() const;
+  GURL GetLinkURL() const;
+
  private:
   // PermissionPromptAndroid is owned by PermissionRequestManager, so it should
   // be safe to hold a raw WebContents pointer here because this class is
diff --git a/chrome/browser/permissions/permission_request_impl.cc b/chrome/browser/permissions/permission_request_impl.cc
index fbc860d3..01016af 100644
--- a/chrome/browser/permissions/permission_request_impl.cc
+++ b/chrome/browser/permissions/permission_request_impl.cc
@@ -148,7 +148,7 @@
     case CONTENT_SETTINGS_TYPE_MIDI_SYSEX:
       message_id = IDS_MIDI_SYSEX_PERMISSION_FRAGMENT;
       break;
-#if defined(OS_CHROMEOS)
+#if defined(OS_ANDROID) || defined(OS_CHROMEOS)
     case CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER:
       message_id = IDS_PROTECTED_MEDIA_IDENTIFIER_PERMISSION_FRAGMENT;
       break;
diff --git a/chrome/browser/permissions/permission_request_manager.cc b/chrome/browser/permissions/permission_request_manager.cc
index 3d4e4d2..494b880 100644
--- a/chrome/browser/permissions/permission_request_manager.cc
+++ b/chrome/browser/permissions/permission_request_manager.cc
@@ -334,16 +334,6 @@
   return requests_;
 }
 
-const std::vector<bool>& PermissionRequestManager::AcceptStates() {
-  // TODO(crbug.com/728483): Remove this function.
-  CR_DEFINE_STATIC_LOCAL(std::vector<bool>, accept_states, ());
-  return accept_states;
-}
-
-void PermissionRequestManager::ToggleAccept(int request_index, bool new_value) {
-  // TODO(crbug.com/728483): Remove this function.
-}
-
 void PermissionRequestManager::TogglePersist(bool new_value) {
   persist_ = new_value;
 }
diff --git a/chrome/browser/permissions/permission_request_manager.h b/chrome/browser/permissions/permission_request_manager.h
index db8aac9..f768823 100644
--- a/chrome/browser/permissions/permission_request_manager.h
+++ b/chrome/browser/permissions/permission_request_manager.h
@@ -137,8 +137,6 @@
 
   // PermissionPrompt::Delegate:
   const std::vector<PermissionRequest*>& Requests() override;
-  const std::vector<bool>& AcceptStates() override;
-  void ToggleAccept(int request_index, bool new_value) override;
   void TogglePersist(bool new_value) override;
   void Accept() override;
   void Deny() override;
diff --git a/chrome/browser/permissions/permission_request_manager_unittest.cc b/chrome/browser/permissions/permission_request_manager_unittest.cc
index 37564927..559ea5e 100644
--- a/chrome/browser/permissions/permission_request_manager_unittest.cc
+++ b/chrome/browser/permissions/permission_request_manager_unittest.cc
@@ -61,10 +61,6 @@
     ChromeRenderViewHostTestHarness::TearDown();
   }
 
-  void ToggleAccept(int index, bool value) {
-    manager_->ToggleAccept(index, value);
-  }
-
   void Accept() {
     manager_->Accept();
   }
diff --git a/chrome/browser/policy/policy_browsertest.cc b/chrome/browser/policy/policy_browsertest.cc
index 056deee..a9df68e9 100644
--- a/chrome/browser/policy/policy_browsertest.cc
+++ b/chrome/browser/policy/policy_browsertest.cc
@@ -33,7 +33,6 @@
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/test/test_file_util.h"
 #include "base/threading/sequenced_worker_pool.h"
 #include "base/threading/thread_restrictions.h"
@@ -89,7 +88,6 @@
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/location_bar/location_bar.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/extensions/extension_constants.h"
@@ -4366,151 +4364,6 @@
   }
 }
 
-namespace {
-const char kTestUser1[] = "test1@domain.com";
-}  // anonymous namespace
-
-class ChromeOSPolicyTest : public PolicyTest {
- public:
-  ChromeOSPolicyTest() {}
-
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    PolicyTest::SetUpCommandLine(command_line);
-    command_line->AppendSwitchASCII(chromeos::switches::kLoginUser,
-                                    cryptohome_id1_.id());
-    command_line->AppendSwitchASCII(chromeos::switches::kLoginProfile, "hash");
-    command_line->AppendSwitch(
-        chromeos::switches::kAllowFailedPolicyFetchForTest);
-  }
-
- protected:
-  const AccountId test_account_id1_ = AccountId::FromUserEmail(kTestUser1);
-  const cryptohome::Identification cryptohome_id1_ =
-      cryptohome::Identification(test_account_id1_);
-
-  // Logs in |account_id|.
-  void LogIn(const AccountId& account_id, const std::string& user_id_hash) {
-    user_manager::UserManager::Get()->UserLoggedIn(account_id, user_id_hash,
-                                                   false);
-    base::RunLoop().RunUntilIdle();
-  }
-
-  void NavigateToUrl(const GURL& url) {
-    ui_test_utils::NavigateToURL(browser(), url);
-    base::RunLoop().RunUntilIdle();
-  }
-
-  void CheckSystemTimezoneAutomaticDetectionPolicyUnset() {
-    PrefService* local_state = g_browser_process->local_state();
-    EXPECT_FALSE(local_state->IsManagedPreference(
-        prefs::kSystemTimezoneAutomaticDetectionPolicy));
-    EXPECT_EQ(0, local_state->GetInteger(
-                     prefs::kSystemTimezoneAutomaticDetectionPolicy));
-  }
-
-  void SetAndTestSystemTimezoneAutomaticDetectionPolicy(int policy_value) {
-    PolicyMap policies;
-    policies.Set(key::kSystemTimezoneAutomaticDetection, POLICY_LEVEL_MANDATORY,
-                 POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
-                 base::MakeUnique<base::Value>(policy_value), nullptr);
-    UpdateProviderPolicy(policies);
-
-    PrefService* local_state = g_browser_process->local_state();
-
-    EXPECT_TRUE(local_state->IsManagedPreference(
-        prefs::kSystemTimezoneAutomaticDetectionPolicy));
-    EXPECT_EQ(policy_value,
-              local_state->GetInteger(
-                  prefs::kSystemTimezoneAutomaticDetectionPolicy));
-  }
-
-  void SetEmptyPolicy() { UpdateProviderPolicy(PolicyMap()); }
-
-  bool CheckResolveTimezoneByGeolocation(bool checked, bool disabled) {
-    checker.set_web_contents(
-        browser()->tab_strip_model()->GetActiveWebContents());
-    const std::string expression = base::StringPrintf(
-        "(function () {\n"
-        "  var checkbox = "
-        "document.getElementById('resolve-timezone-by-geolocation');\n"
-        "  if (!checkbox) {\n"
-        "    console.log('resolve-timezone-by-geolocation not found.');\n"
-        "    return false;\n"
-        "  }\n"
-        "  var expected_checked = %s;\n"
-        "  var expected_disabled = %s;\n"
-        "  var checked = checkbox.checked;\n"
-        "  var disabled = checkbox.disabled;\n"
-        "  if (checked != expected_checked)\n"
-        "    console.log('ERROR: expected_checked=' + expected_checked + ' != "
-        "checked=' + checked);\n"
-        "\n"
-        "  if (disabled != expected_disabled)\n"
-        "    console.log('ERROR: expected_disabled=' + expected_disabled + ' "
-        "!= disabled=' + disabled);\n"
-        "\n"
-        "  return (checked == expected_checked && disabled == "
-        "expected_disabled);\n"
-        "})()",
-        checked ? "true" : "false", disabled ? "true" : "false");
-    return checker.GetBool(expression);
-  }
-
- private:
-  chromeos::test::JSChecker checker;
-
-  DISALLOW_COPY_AND_ASSIGN(ChromeOSPolicyTest);
-};
-
-IN_PROC_BROWSER_TEST_F(ChromeOSPolicyTest, SystemTimezoneAutomaticDetection) {
-  base::test::ScopedFeatureList disable_md_settings;
-  disable_md_settings.InitAndDisableFeature(features::kMaterialDesignSettings);
-
-  ui_test_utils::NavigateToURL(browser(), GURL("chrome://settings"));
-  chromeos::system::TimeZoneResolverManager* manager =
-      g_browser_process->platform_part()->GetTimezoneResolverManager();
-
-  // Policy not set.
-  CheckSystemTimezoneAutomaticDetectionPolicyUnset();
-  EXPECT_TRUE(CheckResolveTimezoneByGeolocation(true, false));
-  EXPECT_TRUE(manager->TimeZoneResolverShouldBeRunningForTests());
-
-  int policy_value = 0 /* USERS_DECIDE */;
-  SetAndTestSystemTimezoneAutomaticDetectionPolicy(policy_value);
-  EXPECT_TRUE(CheckResolveTimezoneByGeolocation(true, false));
-  EXPECT_TRUE(manager->TimeZoneResolverShouldBeRunningForTests());
-
-  policy_value = 1 /* DISABLED */;
-  SetAndTestSystemTimezoneAutomaticDetectionPolicy(policy_value);
-  EXPECT_TRUE(CheckResolveTimezoneByGeolocation(false, true));
-  EXPECT_FALSE(manager->TimeZoneResolverShouldBeRunningForTests());
-
-  policy_value = 2 /* IP_ONLY */;
-  SetAndTestSystemTimezoneAutomaticDetectionPolicy(policy_value);
-  EXPECT_TRUE(CheckResolveTimezoneByGeolocation(true, true));
-  EXPECT_TRUE(manager->TimeZoneResolverShouldBeRunningForTests());
-
-  policy_value = 3 /* SEND_WIFI_ACCESS_POINTS */;
-  SetAndTestSystemTimezoneAutomaticDetectionPolicy(policy_value);
-  EXPECT_TRUE(CheckResolveTimezoneByGeolocation(true, true));
-  EXPECT_TRUE(manager->TimeZoneResolverShouldBeRunningForTests());
-
-  policy_value = 4 /* SEND_ALL_LOCATION_INFO */;
-  SetAndTestSystemTimezoneAutomaticDetectionPolicy(policy_value);
-  EXPECT_TRUE(CheckResolveTimezoneByGeolocation(true, true));
-  EXPECT_TRUE(manager->TimeZoneResolverShouldBeRunningForTests());
-
-  policy_value = 1 /* DISABLED */;
-  SetAndTestSystemTimezoneAutomaticDetectionPolicy(policy_value);
-  EXPECT_TRUE(CheckResolveTimezoneByGeolocation(false, true));
-  EXPECT_FALSE(manager->TimeZoneResolverShouldBeRunningForTests());
-
-  SetEmptyPolicy();
-  // Policy not set.
-  CheckSystemTimezoneAutomaticDetectionPolicyUnset();
-  EXPECT_TRUE(CheckResolveTimezoneByGeolocation(true, false));
-  EXPECT_TRUE(manager->TimeZoneResolverShouldBeRunningForTests());
-}
 #endif  // defined(OS_CHROMEOS)
 
 class NetworkTimePolicyTest : public PolicyTest {
diff --git a/chrome/browser/resources/settings/site_settings/add_site_dialog.js b/chrome/browser/resources/settings/site_settings/add_site_dialog.js
index 09263f1..91ceb61 100644
--- a/chrome/browser/resources/settings/site_settings/add_site_dialog.js
+++ b/chrome/browser/resources/settings/site_settings/add_site_dialog.js
@@ -46,6 +46,7 @@
     this.addWebUIListener('onIncognitoStatusChanged', function(hasIncognito) {
       this.$.incognito.checked = false;
       this.showIncognitoSessionOnly_ = hasIncognito &&
+          !loadTimeData.getBoolean('isGuest') &&
           this.contentSetting != settings.PermissionValues.SESSION_ONLY;
     }.bind(this));
     this.browserProxy.updateIncognitoStatus();
diff --git a/chrome/browser/resources/vulcanize_gn.py b/chrome/browser/resources/vulcanize_gn.py
index 84954cc..e698301 100755
--- a/chrome/browser/resources/vulcanize_gn.py
+++ b/chrome/browser/resources/vulcanize_gn.py
@@ -188,7 +188,7 @@
       f.write(new_data)
       f.truncate()
 
-    node.RunNode([node_modules.PathToUglifyJs(), crisper_output.name,
+    node.RunNode([node_modules.PathToUglify(), crisper_output.name,
                   '--comments', '"/Copyright|license|LICENSE|\<\/?if/"',
                   '--output', js_out_path])
   finally:
diff --git a/chrome/browser/subresource_filter/chrome_subresource_filter_client.cc b/chrome/browser/subresource_filter/chrome_subresource_filter_client.cc
index 8a27baba..d1cd4aa 100644
--- a/chrome/browser/subresource_filter/chrome_subresource_filter_client.cc
+++ b/chrome/browser/subresource_filter/chrome_subresource_filter_client.cc
@@ -52,12 +52,6 @@
 void ChromeSubresourceFilterClient::MaybeAppendNavigationThrottles(
     content::NavigationHandle* navigation_handle,
     std::vector<std::unique_ptr<content::NavigationThrottle>>* throttles) {
-  // Don't add any throttles if the feature isn't enabled at all.
-  if (!base::FeatureList::IsEnabled(
-          subresource_filter::kSafeBrowsingSubresourceFilter)) {
-    return;
-  }
-
   if (navigation_handle->IsInMainFrame()) {
     safe_browsing::SafeBrowsingService* safe_browsing_service =
         g_browser_process->safe_browsing_service();
diff --git a/chrome/browser/subresource_filter/subresource_filter_browsertest.cc b/chrome/browser/subresource_filter/subresource_filter_browsertest.cc
index 3299a596..d3560fa3 100644
--- a/chrome/browser/subresource_filter/subresource_filter_browsertest.cc
+++ b/chrome/browser/subresource_filter/subresource_filter_browsertest.cc
@@ -1446,7 +1446,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(SubresourceFilterBrowserTest,
-                       WhiteliseSiteOnReload_ActivationDisabledOnReload) {
+                       WhitelistSiteOnReload_ActivationDisabledOnReload) {
   GURL url(GetTestUrl("subresource_filter/frame_with_included_script.html"));
   ConfigureAsPhishingURL(url);
   ASSERT_NO_FATAL_FAILURE(
@@ -1482,7 +1482,7 @@
 
 IN_PROC_BROWSER_TEST_F(
     SubresourceFilterBrowserTest,
-    WhiteliseSiteOnReload_ActivationDisabledOnReloadFromScript) {
+    WhitelistSiteOnReload_ActivationDisabledOnReloadFromScript) {
   GURL url(GetTestUrl("subresource_filter/frame_with_included_script.html"));
   ConfigureAsPhishingURL(url);
   ASSERT_NO_FATAL_FAILURE(
@@ -1520,7 +1520,7 @@
 
 IN_PROC_BROWSER_TEST_F(
     SubresourceFilterBrowserTest,
-    WhiteliseSiteOnReload_ActivationDisabledOnNavigationToSameURL) {
+    WhitelistSiteOnReload_ActivationDisabledOnNavigationToSameURL) {
   GURL url(GetTestUrl("subresource_filter/frame_with_included_script.html"));
   ConfigureAsPhishingURL(url);
   ASSERT_NO_FATAL_FAILURE(
diff --git a/chrome/browser/ui/android/infobars/grouped_permission_infobar.cc b/chrome/browser/ui/android/infobars/grouped_permission_infobar.cc
index 35d3436f..98f63aa 100644
--- a/chrome/browser/ui/android/infobars/grouped_permission_infobar.cc
+++ b/chrome/browser/ui/android/infobars/grouped_permission_infobar.cc
@@ -38,6 +38,8 @@
   base::android::ScopedJavaLocalRef<jstring> message_text =
       base::android::ConvertUTF16ToJavaString(
           env, delegate->GetMessageText());
+  base::android::ScopedJavaLocalRef<jstring> link_text =
+      base::android::ConvertUTF16ToJavaString(env, delegate->GetLinkText());
   base::android::ScopedJavaLocalRef<jstring> ok_button_text =
       base::android::ConvertUTF16ToJavaString(
           env, GetTextFor(ConfirmInfoBarDelegate::BUTTON_OK));
@@ -59,7 +61,7 @@
   return Java_GroupedPermissionInfoBar_create(
       env, GetTab()->GetJavaObject(),
       base::android::ToJavaIntArray(env, content_settings_types), message_text,
-      ok_button_text, cancel_button_text,
+      link_text, ok_button_text, cancel_button_text,
       delegate->ShouldShowPersistenceToggle(),
       base::android::ToJavaArrayOfStrings(env, permission_strings),
       base::android::ToJavaIntArray(env, permission_icons));
diff --git a/chrome/browser/ui/ash/system_tray_client.cc b/chrome/browser/ui/ash/system_tray_client.cc
index c4ff0ae..635d767c 100644
--- a/chrome/browser/ui/ash/system_tray_client.cc
+++ b/chrome/browser/ui/ash/system_tray_client.cc
@@ -8,7 +8,6 @@
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/public/interfaces/constants.mojom.h"
 #include "ash/shell.h"
-#include "base/feature_list.h"
 #include "base/logging.h"
 #include "base/memory/weak_ptr.h"
 #include "base/metrics/user_metrics.h"
@@ -35,7 +34,6 @@
 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
 #include "chrome/browser/ui/singleton_tabs.h"
 #include "chrome/browser/upgrade_detector.h"
-#include "chrome/common/chrome_features.h"
 #include "chrome/common/url_constants.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/session_manager_client.h"
@@ -415,9 +413,9 @@
 
   std::string page = chrome::kInternetSubPage;
   if (!network_id.empty()) {
-    if (base::FeatureList::IsEnabled(features::kMaterialDesignSettings))
-      page = chrome::kNetworkDetailSubPage;
-    page += "?guid=" + net::EscapeUrlEncodedData(network_id, true);
+    page = chrome::kNetworkDetailSubPage;
+    page += "?guid=";
+    page += net::EscapeUrlEncodedData(network_id, true);
     if (show_configure)
       page += "&showConfigure=true";
   }
diff --git a/chrome/browser/ui/chrome_pages.cc b/chrome/browser/ui/chrome_pages.cc
index ce47b609..115eb7cf 100644
--- a/chrome/browser/ui/chrome_pages.cc
+++ b/chrome/browser/ui/chrome_pages.cc
@@ -7,7 +7,6 @@
 #include <stddef.h>
 
 #include "base/command_line.h"
-#include "base/feature_list.h"
 #include "base/logging.h"
 #include "base/macros.h"
 #include "base/metrics/user_metrics.h"
@@ -32,7 +31,6 @@
 #include "chrome/browser/ui/webui/md_bookmarks/md_bookmarks_ui.h"
 #include "chrome/browser/ui/webui/options/content_settings_handler.h"
 #include "chrome/browser/ui/webui/site_settings_helper.h"
-#include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/url_constants.h"
 #include "components/bookmarks/browser/bookmark_model.h"
@@ -51,9 +49,7 @@
 #endif
 
 #if defined(OS_CHROMEOS)
-#include "base/feature_list.h"
 #include "chrome/browser/chromeos/genius_app/app_id.h"
-#include "chrome/common/chrome_features.h"
 #include "chrome/grit/generated_resources.h"
 #include "extensions/browser/extension_registry.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -142,20 +138,15 @@
 }
 
 std::string GenerateContentSettingsExceptionsSubPage(ContentSettingsType type) {
-  if (!base::FeatureList::IsEnabled(features::kMaterialDesignSettings)) {
-    return kDeprecatedOptionsContentSettingsExceptionsSubPage +
-           std::string(kHashMark) +
-           site_settings::ContentSettingsTypeToGroupName(type);
-  }
-
   // In MD Settings, the exceptions no longer have a separate subpage.
   // This list overrides the group names defined in site_settings_helper for the
   // purposes of URL generation for MD Settings only. We need this because some
   // of the old group names are no longer appropriate: i.e. "plugins" =>
   // "flash".
   //
-  // TODO(tommycli): Update the group names defined in site_settings_helper once
-  // Options is removed from Chrome. Then this list will no longer be needed.
+  // TODO(crbug.com/728353): Update the group names defined in
+  // site_settings_helper once Options is removed from Chrome. Then this list
+  // will no longer be needed.
   typedef std::map<ContentSettingsType, std::string> ContentSettingPathMap;
   CR_DEFINE_STATIC_LOCAL(
       ContentSettingPathMap, kSettingsPathOverrides,
@@ -175,13 +166,6 @@
   return std::string(kContentSettingsSubPage) + "/" + content_type_path;
 }
 
-#if defined(OS_CHROMEOS)
-std::string GenerateContentSettingsSearchQueryPath(int query_message_id) {
-  return std::string(chrome::kDeprecatedOptionsSearchSubPage) + kHashMark +
-         l10n_util::GetStringUTF8(query_message_id);
-}
-#endif
-
 }  // namespace
 
 void ShowBookmarkManager(Browser* browser) {
@@ -317,24 +301,6 @@
                                    const std::string& sub_page) {
   std::string sub_page_path = sub_page;
 
-#if defined(OS_CHROMEOS)
-  if (!base::FeatureList::IsEnabled(features::kMaterialDesignSettings)) {
-    if (sub_page == chrome::kAccessibilitySubPage) {
-      sub_page_path = GenerateContentSettingsSearchQueryPath(
-          IDS_OPTIONS_SETTINGS_SECTION_TITLE_ACCESSIBILITY);
-    } else if (sub_page == chrome::kBluetoothSubPage) {
-      sub_page_path = GenerateContentSettingsSearchQueryPath(
-          IDS_OPTIONS_SETTINGS_SECTION_TITLE_BLUETOOTH);
-    } else if (sub_page == chrome::kDateTimeSubPage) {
-      sub_page_path = GenerateContentSettingsSearchQueryPath(
-          IDS_OPTIONS_SETTINGS_SECTION_TITLE_DATETIME);
-    } else if (sub_page == chrome::kStylusSubPage ||
-               sub_page == chrome::kPowerSubPage) {
-      sub_page_path += "-overlay";
-    }
-  }
-#endif
-
   if (::switches::SettingsWindowEnabled()) {
     base::RecordAction(base::UserMetricsAction("ShowOptions"));
     SettingsWindowManager::GetInstance()->ShowChromePageForProfile(
diff --git a/chrome/browser/ui/cocoa/permission_bubble/permission_bubble_controller_unittest.mm b/chrome/browser/ui/cocoa/permission_bubble/permission_bubble_controller_unittest.mm
index 982c8842..0acc057 100644
--- a/chrome/browser/ui/cocoa/permission_bubble/permission_bubble_controller_unittest.mm
+++ b/chrome/browser/ui/cocoa/permission_bubble/permission_bubble_controller_unittest.mm
@@ -69,7 +69,6 @@
                                        public PermissionPrompt::Delegate {
  public:
 
-  MOCK_METHOD2(ToggleAccept, void(int, bool));
   MOCK_METHOD1(TogglePersist, void(bool));
   MOCK_METHOD0(SetCustomizationMode, void());
   MOCK_METHOD0(Accept, void());
@@ -97,12 +96,6 @@
     return requests_;
   }
 
-  const std::vector<bool>& AcceptStates() override {
-    // TODO(crbug.com/728483): Remove this function.
-    CR_DEFINE_STATIC_LOCAL(std::vector<bool>, accept_states, ());
-    return accept_states;
-  }
-
   void AddRequest(const std::string& title) {
     std::unique_ptr<MockPermissionRequest> request =
         base::MakeUnique<MockPermissionRequest>(
@@ -158,7 +151,6 @@
   std::unique_ptr<PermissionBubbleCocoa> bridge_;
   std::vector<PermissionRequest*> requests_;
   std::vector<std::unique_ptr<PermissionRequest>> owned_requests_;
-  std::vector<bool> accept_states_;
 };
 
 TEST_F(PermissionBubbleControllerTest, ShowAndClose) {
diff --git a/chrome/browser/ui/page_info/permission_menu_model.cc b/chrome/browser/ui/page_info/permission_menu_model.cc
index d7bf8ca7..95788a8 100644
--- a/chrome/browser/ui/page_info/permission_menu_model.cc
+++ b/chrome/browser/ui/page_info/permission_menu_model.cc
@@ -118,24 +118,6 @@
   AddCheckItem(CONTENT_SETTING_BLOCK, label);
 }
 
-PermissionMenuModel::PermissionMenuModel(Profile* profile,
-                                         const GURL& url,
-                                         ContentSetting setting,
-                                         const ChangeCallback& callback)
-    : ui::SimpleMenuModel(this),
-      host_content_settings_map_(
-          HostContentSettingsMapFactory::GetForProfile(profile)),
-      callback_(callback) {
-  DCHECK(setting == CONTENT_SETTING_ALLOW || setting == CONTENT_SETTING_BLOCK);
-  permission_.type = CONTENT_SETTINGS_TYPE_DEFAULT;
-  permission_.setting = setting;
-  permission_.default_setting = CONTENT_SETTING_NUM_SETTINGS;
-  AddCheckItem(CONTENT_SETTING_ALLOW,
-               l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW));
-  AddCheckItem(CONTENT_SETTING_BLOCK,
-               l10n_util::GetStringUTF16(IDS_PERMISSION_DENY));
-}
-
 PermissionMenuModel::~PermissionMenuModel() {}
 
 bool PermissionMenuModel::IsCommandIdChecked(int command_id) const {
diff --git a/chrome/browser/ui/page_info/permission_menu_model.h b/chrome/browser/ui/page_info/permission_menu_model.h
index d6d01e54d..d34d9ae 100644
--- a/chrome/browser/ui/page_info/permission_menu_model.h
+++ b/chrome/browser/ui/page_info/permission_menu_model.h
@@ -26,14 +26,6 @@
                       const GURL& url,
                       const PageInfoUI::PermissionInfo& info,
                       const ChangeCallback& callback);
-  // Creates a special-case menu model that only has the allow and block
-  // options.  It does not track a permission type.  |setting| is the
-  // initial selected option.  It must be either CONTENT_SETTING_ALLOW or
-  // CONTENT_SETTING_BLOCK.
-  PermissionMenuModel(Profile* profile,
-                      const GURL& url,
-                      ContentSetting setting,
-                      const ChangeCallback& callback);
   ~PermissionMenuModel() override;
 
   // Overridden from ui::SimpleMenuModel::Delegate:
diff --git a/chrome/browser/ui/page_info/permission_menu_model_unittest.cc b/chrome/browser/ui/page_info/permission_menu_model_unittest.cc
index 33c8e62..6dd1044 100644
--- a/chrome/browser/ui/page_info/permission_menu_model_unittest.cc
+++ b/chrome/browser/ui/page_info/permission_menu_model_unittest.cc
@@ -63,13 +63,6 @@
   }
 }
 
-TEST_F(PermissionMenuModelTest, TestAllowBlock) {
-  TestCallback callback;
-  PermissionMenuModel model(profile(), GURL("http://www.google.com"),
-                            CONTENT_SETTING_ALLOW, callback.callback());
-  EXPECT_EQ(2, model.GetItemCount());
-}
-
 TEST_F(PermissionMenuModelTest, TestIncognitoNotifications) {
   TestCallback callback;
   PageInfoUI::PermissionInfo permission;
diff --git a/chrome/browser/ui/permission_bubble/mock_permission_prompt.cc b/chrome/browser/ui/permission_bubble/mock_permission_prompt.cc
index 2618143e..87f3801 100644
--- a/chrome/browser/ui/permission_bubble/mock_permission_prompt.cc
+++ b/chrome/browser/ui/permission_bubble/mock_permission_prompt.cc
@@ -8,6 +8,11 @@
 #include "base/run_loop.h"
 #include "chrome/browser/permissions/permission_request_manager.h"
 #include "chrome/browser/ui/permission_bubble/mock_permission_prompt_factory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if !defined(OS_ANDROID)
+#include "ui/gfx/vector_icon_types.h"
+#endif
 
 MockPermissionPrompt::~MockPermissionPrompt() {
   Hide();
@@ -20,6 +25,14 @@
   for (const PermissionRequest* request : manager_->requests_) {
     factory_->request_types_seen_.push_back(
         request->GetPermissionRequestType());
+    // The actual prompt will call these, so test they're sane.
+    EXPECT_FALSE(request->GetMessageTextFragment().empty());
+#if defined(OS_ANDROID)
+    EXPECT_FALSE(request->GetMessageText().empty());
+    EXPECT_NE(0, request->GetIconId());
+#else
+    EXPECT_FALSE(request->GetIconId().is_empty());
+#endif
   }
   factory_->UpdateResponseType();
   is_visible_ = true;
diff --git a/chrome/browser/ui/permission_bubble/permission_bubble_browser_test_util.cc b/chrome/browser/ui/permission_bubble/permission_bubble_browser_test_util.cc
index 19a2548..a1158095 100644
--- a/chrome/browser/ui/permission_bubble/permission_bubble_browser_test_util.cc
+++ b/chrome/browser/ui/permission_bubble/permission_bubble_browser_test_util.cc
@@ -29,10 +29,6 @@
   return requests_;
 }
 
-const std::vector<bool>& TestPermissionBubbleViewDelegate::AcceptStates() {
-  return accept_states_;
-}
-
 PermissionBubbleBrowserTest::PermissionBubbleBrowserTest() {
 }
 
diff --git a/chrome/browser/ui/permission_bubble/permission_bubble_browser_test_util.h b/chrome/browser/ui/permission_bubble/permission_bubble_browser_test_util.h
index 76a0a9b..fca91ba 100644
--- a/chrome/browser/ui/permission_bubble/permission_bubble_browser_test_util.h
+++ b/chrome/browser/ui/permission_bubble/permission_bubble_browser_test_util.h
@@ -24,9 +24,7 @@
   ~TestPermissionBubbleViewDelegate() override;
 
   const std::vector<PermissionRequest*>& Requests() override;
-  const std::vector<bool>& AcceptStates() override;
 
-  void ToggleAccept(int, bool) override {}
   void TogglePersist(bool) override {}
   void Accept() override {}
   void Deny() override {}
@@ -38,7 +36,6 @@
 
  private:
   std::vector<PermissionRequest*> requests_;
-  std::vector<bool> accept_states_;
 
   DISALLOW_COPY_AND_ASSIGN(TestPermissionBubbleViewDelegate);
 };
@@ -61,7 +58,6 @@
  private:
   TestPermissionBubbleViewDelegate test_delegate_;
   std::vector<std::unique_ptr<PermissionRequest>> requests_;
-  std::vector<bool> accept_states_;
 
   DISALLOW_COPY_AND_ASSIGN(PermissionBubbleBrowserTest);
 };
diff --git a/chrome/browser/ui/permission_bubble/permission_prompt.h b/chrome/browser/ui/permission_bubble/permission_prompt.h
index 6e172b4..e81cf8e 100644
--- a/chrome/browser/ui/permission_bubble/permission_prompt.h
+++ b/chrome/browser/ui/permission_bubble/permission_prompt.h
@@ -32,9 +32,7 @@
     // These pointers should not be stored as the actual request objects may be
     // deleted upon navigation and so on.
     virtual const std::vector<PermissionRequest*>& Requests() = 0;
-    virtual const std::vector<bool>& AcceptStates() = 0;
 
-    virtual void ToggleAccept(int index, bool new_value) = 0;
     virtual void TogglePersist(bool new_value) = 0;
     virtual void Accept() = 0;
     virtual void Deny() = 0;
diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc
index b565c76..4becadd 100644
--- a/chrome/browser/ui/tab_helpers.cc
+++ b/chrome/browser/ui/tab_helpers.cc
@@ -66,6 +66,7 @@
 #include "components/history/core/browser/top_sites.h"
 #include "components/password_manager/core/browser/password_manager.h"
 #include "components/subresource_filter/content/browser/content_subresource_filter_driver_factory.h"
+#include "components/subresource_filter/core/browser/subresource_filter_features.h"
 #include "components/tracing/common/tracing_switches.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/browser_side_navigation_policy.h"
@@ -184,7 +185,10 @@
   ChromePasswordManagerClient::CreateForWebContentsWithAutofillClient(
       web_contents,
       autofill::ChromeAutofillClient::FromWebContents(web_contents));
-  ChromeSubresourceFilterClient::CreateForWebContents(web_contents);
+  if (base::FeatureList::IsEnabled(
+          subresource_filter::kSafeBrowsingSubresourceFilter)) {
+    ChromeSubresourceFilterClient::CreateForWebContents(web_contents);
+  }
   ChromeTranslateClient::CreateForWebContents(web_contents);
   CoreTabHelper::CreateForWebContents(web_contents);
   data_use_measurement::DataUseWebContentsObserver::CreateForWebContents(
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc
index c348f621..5d68c50 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc
@@ -13,7 +13,6 @@
 #include "ash/frame/header_painter_util.h"
 #include "ash/shell.h"
 #include "ash/wm/window_util.h"
-#include "base/feature_list.h"
 #include "build/build_config.h"
 #include "chrome/browser/profiles/profiles_state.h"
 #include "chrome/browser/themes/theme_properties.h"
@@ -29,7 +28,6 @@
 #include "chrome/browser/ui/views/tabs/tab_strip.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "chrome/browser/web_applications/web_app.h"
-#include "chrome/common/chrome_features.h"
 #include "chrome/grit/theme_resources.h"
 #include "content/public/browser/web_contents.h"
 #include "third_party/skia/include/core/SkColor.h"
@@ -102,11 +100,9 @@
     header_painter->Init(frame(), this, caption_button_container_);
     if (window_icon_)
       header_painter->UpdateLeftHeaderView(window_icon_);
-    if (base::FeatureList::IsEnabled(features::kMaterialDesignSettings)) {
-      // For non app (i.e. WebUI) windows (e.g. Settings) use MD frame color.
-      if (!browser_view()->browser()->is_app())
-        header_painter->SetFrameColors(kMdWebUIFrameColor, kMdWebUIFrameColor);
-    }
+    // For non app (i.e. WebUI) windows (e.g. Settings) use MD frame color.
+    if (!browser_view()->browser()->is_app())
+      header_painter->SetFrameColors(kMdWebUIFrameColor, kMdWebUIFrameColor);
   } else {
     BrowserHeaderPainterAsh* header_painter = new BrowserHeaderPainterAsh;
     header_painter_.reset(header_painter);
diff --git a/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc b/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc
index 42e5bcf..624d469 100644
--- a/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc
+++ b/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc
@@ -327,11 +327,6 @@
     delegate_->Closing();
 }
 
-void PermissionPromptImpl::ToggleAccept(int index, bool value) {
-  if (delegate_)
-    delegate_->ToggleAccept(index, value);
-}
-
 void PermissionPromptImpl::TogglePersist(bool value) {
   if (delegate_)
     delegate_->TogglePersist(value);
diff --git a/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.h b/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.h
index 3c1140c7..cc6173f 100644
--- a/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.h
+++ b/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.h
@@ -36,7 +36,6 @@
   gfx::NativeWindow GetNativeWindow() override;
 
   void Closing();
-  void ToggleAccept(int index, bool value);
   void TogglePersist(bool value);
   void Accept();
   void Deny();
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index 24a950d..d0520cce 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -422,8 +422,13 @@
     return &NewWebUI<IdentityInternalsUI>;
   if (url.host_piece() == chrome::kChromeUINewTabHost)
     return &NewWebUI<NewTabUI>;
-  if (url.host_piece() == chrome::kChromeUIMdSettingsHost)
+  // Settings are implemented with native UI elements on Android.
+  if (url.host_piece() == chrome::kChromeUISettingsHost ||
+      url.host_piece() == chrome::kChromeUIMdSettingsHost) {
     return &NewWebUI<settings::MdSettingsUI>;
+  }
+  if (url.host_piece() == chrome::kChromeUISettingsFrameHost)
+    return &NewWebUI<options::OptionsUI>;
   // If the material design extensions page is enabled, it gets its own host.
   // Otherwise, it's handled by the uber settings page.
   if (url.host_piece() == chrome::kChromeUIExtensionsHost &&
@@ -432,18 +437,6 @@
   }
   if (url.host_piece() == chrome::kChromeUIHistoryHost)
     return &NewWebUI<MdHistoryUI>;
-  // Material Design Settings gets its own host, if enabled.
-  if (base::FeatureList::IsEnabled(features::kMaterialDesignSettings) &&
-      url.host_piece() == chrome::kChromeUISettingsHost) {
-    return &NewWebUI<settings::MdSettingsUI>;
-  }
-  // Settings are implemented with native UI elements on Android.
-  // Handle chrome://settings if settings in a window is enabled.
-  if (url.host_piece() == chrome::kChromeUISettingsFrameHost ||
-      (url.host_piece() == chrome::kChromeUISettingsHost &&
-       ::switches::SettingsWindowEnabled())) {
-    return &NewWebUI<options::OptionsUI>;
-  }
   if (url.host_piece() == chrome::kChromeUISyncFileSystemInternalsHost)
     return &NewWebUI<SyncFileSystemInternalsUI>;
   if (url.host_piece() == chrome::kChromeUISystemInfoHost)
diff --git a/chrome/browser/ui/webui/log_web_ui_url_browsertest.cc b/chrome/browser/ui/webui/log_web_ui_url_browsertest.cc
index 24acac8..4bbc452 100644
--- a/chrome/browser/ui/webui/log_web_ui_url_browsertest.cc
+++ b/chrome/browser/ui/webui/log_web_ui_url_browsertest.cc
@@ -11,11 +11,9 @@
 #include "base/hash.h"
 #include "base/macros.h"
 #include "base/test/histogram_tester.h"
-#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/common/chrome_features.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -41,32 +39,12 @@
     return histogram_tester_.GetAllSamples(webui::kWebUICreatedForUrl);
   }
 
-  void SetUpOnMainThread() override {
-    // Disable MD Settings to test non-MD settings page.
-    scoped_feature_list_.InitAndDisableFeature(
-        features::kMaterialDesignSettings);
-  }
-
  private:
-  base::test::ScopedFeatureList scoped_feature_list_;
   base::HistogramTester histogram_tester_;
 
   DISALLOW_COPY_AND_ASSIGN(LogWebUIUrlTest);
 };
 
-IN_PROC_BROWSER_TEST_F(LogWebUIUrlTest, TestSettingsFrame) {
-  GURL settings_frame_url(chrome::kChromeUISettingsFrameURL);
-
-  ui_test_utils::NavigateToURL(browser(), settings_frame_url);
-
-  uint32_t settings_frame_url_hash = base::Hash(settings_frame_url.spec());
-  EXPECT_THAT(GetSamples(), ElementsAre(Bucket(settings_frame_url_hash, 1)));
-
-  chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB);
-
-  EXPECT_THAT(GetSamples(), ElementsAre(Bucket(settings_frame_url_hash, 2)));
-}
-
 #if BUILDFLAG(ENABLE_EXTENSIONS)
 IN_PROC_BROWSER_TEST_F(LogWebUIUrlTest, TestExtensionsPage) {
   content::WebContents* tab =
@@ -95,25 +73,6 @@
   EXPECT_THAT(GetSamples(), ElementsAre(Bucket(extensions_frame_url_hash, 1),
                                         Bucket(uber_frame_url_hash, 1),
                                         Bucket(uber_url_hash, 1)));
-
-  {
-    // Pretend a user clicked on "Settings".
-    base::string16 settings_title =
-        l10n_util::GetStringUTF16(IDS_SETTINGS_SETTINGS);
-    content::TitleWatcher title_watcher(tab, settings_title);
-    std::string javascript =
-        "uber.invokeMethodOnWindow(window, 'showPage', {pageId: 'settings'})";
-    ASSERT_TRUE(content::ExecuteScript(tab, javascript));
-    ASSERT_EQ(settings_title, title_watcher.WaitAndGetTitle());
-  }
-
-  GURL settings_frame_url(chrome::kChromeUISettingsFrameURL);
-  uint32_t settings_frame_url_hash = base::Hash(settings_frame_url.spec());
-
-  EXPECT_THAT(GetSamples(), ElementsAre(Bucket(extensions_frame_url_hash, 1),
-                                        Bucket(settings_frame_url_hash, 1),
-                                        Bucket(uber_frame_url_hash, 1),
-                                        Bucket(uber_url_hash, 1)));
 }
 #endif
 
diff --git a/chrome/browser/ui/webui/options/certificate_manager_browsertest.cc b/chrome/browser/ui/webui/options/certificate_manager_browsertest.cc
deleted file mode 100644
index ed0ae53..0000000
--- a/chrome/browser/ui/webui/options/certificate_manager_browsertest.cc
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/memory/ptr_util.h"
-#include "base/values.h"
-#include "build/build_config.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/browser/ui/webui/options/options_ui_browsertest.h"
-#include "components/policy/core/browser/browser_policy_connector.h"
-#include "components/policy/core/common/external_data_fetcher.h"
-#include "components/policy/core/common/mock_configuration_policy_provider.h"
-#include "components/policy/core/common/policy_map.h"
-#include "components/policy/core/common/policy_types.h"
-#include "components/policy/policy_constants.h"
-#include "content/public/browser/render_frame_host.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/test/browser_test_utils.h"
-#include "content/public/test/test_utils.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-#if defined(OS_CHROMEOS)
-#include "chrome/browser/chromeos/policy/device_policy_cros_browser_test.h"
-#include "chromeos/network/onc/onc_test_utils.h"
-#endif
-
-using testing::Return;
-using testing::_;
-
-class CertificateManagerBrowserTest : public options::OptionsUIBrowserTest {
- public:
-  CertificateManagerBrowserTest() {}
-  ~CertificateManagerBrowserTest() override {}
-
- protected:
-  void SetUpInProcessBrowserTestFixture() override {
-    options::OptionsUIBrowserTest::SetUpInProcessBrowserTestFixture();
-#if defined(OS_CHROMEOS)
-    device_policy_test_helper_.MarkAsEnterpriseOwned();
-#endif
-    // Setup the policy provider for injecting certs through ONC policy.
-    EXPECT_CALL(provider_, IsInitializationComplete(_))
-        .WillRepeatedly(Return(true));
-    policy::BrowserPolicyConnector::SetPolicyProviderForTesting(&provider_);
-  }
-
-#if defined(OS_CHROMEOS)
-  void LoadONCPolicy(const std::string& filename) {
-    const std::string& user_policy_blob =
-        chromeos::onc::test_utils::ReadTestData(filename);
-    policy::PolicyMap policy;
-    policy.Set(policy::key::kOpenNetworkConfiguration,
-               policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-               policy::POLICY_SOURCE_CLOUD,
-               base::MakeUnique<base::Value>(user_policy_blob), nullptr);
-    provider_.UpdateChromePolicy(policy);
-    content::RunAllPendingInMessageLoop();
-  }
-#endif
-
-  void ClickElement(const std::string& selector) {
-    EXPECT_TRUE(content::ExecuteScript(
-        GetSettingsFrame(),
-        "document.querySelector(\"" + selector + "\").click()"));
-  }
-
-  bool HasElement(const std::string& selector) {
-    bool result;
-    EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
-        GetSettingsFrame(),
-        "window.domAutomationController.send("
-        "    !!document.querySelector('" + selector + "'));",
-        &result));
-    return result;
-  }
-
-  policy::MockConfigurationPolicyProvider provider_;
-#if defined(OS_CHROMEOS)
-  policy::DevicePolicyCrosTestHelper device_policy_test_helper_;
-#endif
-};
-
-#if defined(OS_CHROMEOS)
-// Ensure policy-installed certificates without web trust do not display
-// the managed setting indicator (only on Chrome OS).
-IN_PROC_BROWSER_TEST_F(CertificateManagerBrowserTest,
-                       PolicyCertificateWithoutWebTrustHasNoIndicator) {
-  LoadONCPolicy("certificate-authority.onc");
-  NavigateToSettings();
-  ClickElement("#certificatesManageButton");
-  ClickElement("#ca-certs-nav-tab");
-  EXPECT_FALSE(HasElement(".cert-policy"));
-}
-#endif
-
-#if defined(OS_CHROMEOS)
-// Ensure policy-installed certificates with web trust display the
-// managed setting indicator (only on Chrome OS).
-IN_PROC_BROWSER_TEST_F(CertificateManagerBrowserTest,
-                       PolicyCertificateWithWebTrustHasIndicator) {
-  LoadONCPolicy("certificate-web-authority.onc");
-  NavigateToSettings();
-  ClickElement("#certificatesManageButton");
-  ClickElement("#ca-certs-nav-tab");
-  EXPECT_TRUE(HasElement(".cert-policy"));
-}
-#endif
diff --git a/chrome/browser/ui/webui/options/chromeos/guest_mode_options_browsertest.cc b/chrome/browser/ui/webui/options/chromeos/guest_mode_options_browsertest.cc
deleted file mode 100644
index d2bdaa8..0000000
--- a/chrome/browser/ui/webui/options/chromeos/guest_mode_options_browsertest.cc
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/webui/options/chromeos/guest_mode_options_browsertest.h"
-
-#include "base/command_line.h"
-#include "chrome/browser/ui/webui/options/options_ui_browsertest.h"
-#include "chrome/common/chrome_switches.h"
-#include "chrome/test/base/testing_profile.h"
-#include "chromeos/chromeos_switches.h"
-#include "components/signin/core/account_id/account_id.h"
-#include "components/user_manager/user_names.h"
-
-GuestModeOptionsUIBrowserTest::GuestModeOptionsUIBrowserTest() {}
-
-GuestModeOptionsUIBrowserTest::~GuestModeOptionsUIBrowserTest() {}
-
-void GuestModeOptionsUIBrowserTest::SetUpCommandLine(
-    base::CommandLine* command_line) {
-  command_line->AppendSwitch(chromeos::switches::kGuestSession);
-  command_line->AppendSwitchASCII(
-      chromeos::switches::kLoginUser,
-      user_manager::GuestAccountId().GetUserEmail());
-  command_line->AppendSwitchASCII(chromeos::switches::kLoginProfile,
-                                  TestingProfile::kTestUserProfileDir);
-  command_line->AppendSwitch(switches::kIncognito);
-}
diff --git a/chrome/browser/ui/webui/options/chromeos/guest_mode_options_browsertest.h b/chrome/browser/ui/webui/options/chromeos/guest_mode_options_browsertest.h
deleted file mode 100644
index 33ccde9..0000000
--- a/chrome/browser/ui/webui/options/chromeos/guest_mode_options_browsertest.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS_CHROMEOS_GUEST_MODE_OPTIONS_BROWSERTEST_H_
-#define CHROME_BROWSER_UI_WEBUI_OPTIONS_CHROMEOS_GUEST_MODE_OPTIONS_BROWSERTEST_H_
-
-#include "base/command_line.h"
-#include "base/compiler_specific.h"
-#include "base/macros.h"
-#include "chrome/test/base/web_ui_browser_test.h"
-#include "content/public/browser/web_ui_message_handler.h"
-
-// This is a helper class used by guest_mode_options_browsertest.js to enable
-// guest mode.
-class GuestModeOptionsUIBrowserTest : public WebUIBrowserTest {
- public:
-  GuestModeOptionsUIBrowserTest();
-  ~GuestModeOptionsUIBrowserTest() override;
-
- private:
-  void SetUpCommandLine(base::CommandLine* command_line) override;
-
-  DISALLOW_COPY_AND_ASSIGN(GuestModeOptionsUIBrowserTest);
-};
-
-#endif  // CHROME_BROWSER_UI_WEBUI_OPTIONS_CHROMEOS_GUEST_MODE_OPTIONS_BROWSERTEST_H_
diff --git a/chrome/browser/ui/webui/options/chromeos/guest_mode_options_browsertest.js b/chrome/browser/ui/webui/options/chromeos/guest_mode_options_browsertest.js
deleted file mode 100644
index f4a0d0e..0000000
--- a/chrome/browser/ui/webui/options/chromeos/guest_mode_options_browsertest.js
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-GEN_INCLUDE(['../options_browsertest_base.js']);
-GEN('#include "chrome/browser/ui/webui/options/chromeos/' +
-    'guest_mode_options_browsertest.h"');
-
-/**
- * TestFixture for guest mode options WebUI testing.
- * @extends {OptionsBrowsertestBase}
- * @constructor
- */
-function GuestModeOptionsUIBrowserTest() {}
-
-GuestModeOptionsUIBrowserTest.prototype = {
-  __proto__: OptionsBrowsertestBase.prototype,
-
-  /** @override */
-  browsePreload: 'chrome://settings-frame',
-
-  /** @override */
-  typedefCppFixture: 'GuestModeOptionsUIBrowserTest',
-
-  /**
-   * Returns the element that sets a given preference, failing if no such
-   * element is found.
-   * @param {string} pref Name of the preference.
-   * @return {!HTMLElement} The element controlling the preference.
-   */
-  getControlForPref: function(pref) {
-    var control = document.querySelector('[pref="' + pref + '"]');
-    assertTrue(!!control);
-    return control;
-  },
-
-  /** @param {!HTMLElement} el */
-  expectHidden: function(el) {
-    expectFalse(el.offsetHeight > 0 && el.offsetWidth > 0);
-  },
-};
-
-/** Test sections that should be hidden in guest mode. */
-TEST_F('GuestModeOptionsUIBrowserTest', 'testSections', function() {
-  this.expectHidden($('startup-section'));
-  this.expectHidden($('appearance-section'));
-  this.expectHidden($('android-apps-section'));
-  this.expectHidden($('sync-users-section'));
-  this.expectHidden($('easy-unlock-section'));
-  this.expectHidden($('reset-profile-settings-section'));
-});
-
-/** Test controls that should be disabled in guest mode. */
-TEST_F('GuestModeOptionsUIBrowserTest', 'testControls', function() {
-  // Appearance section.
-  var setWallpaper = $('set-wallpaper');
-  expectTrue(setWallpaper.disabled);
-
-  // Passwords and autofill section.
-  expectTrue($('autofill-enabled').disabled);
-
-  // Date and time section.
-  expectTrue($('timezone-value-select').disabled);
-  expectFalse($('resolve-timezone-by-geolocation').disabled);
-  expectFalse($('use-24hour-clock').disabled);
-
-  // Privacy section.
-  expectTrue(this.getControlForPref('search.suggest_enabled').disabled);
-  expectTrue($('networkPredictionOptions').disabled);
-
-  // Web content section.
-  expectTrue($('defaultZoomFactor').disabled);
-
-  // Downloads section.
-  expectTrue(this.getControlForPref('gdata.disabled').disabled);
-
-  // Content settings overlay.
-  expectTrue(this.getControlForPref('settings.privacy.drm_enabled').disabled);
-  expectTrue($('protected-content-exceptions').disabled);
-});
diff --git a/chrome/browser/ui/webui/options/chromeos/guest_mode_options_ui_browsertest.cc b/chrome/browser/ui/webui/options/chromeos/guest_mode_options_ui_browsertest.cc
deleted file mode 100644
index 3d7a39c..0000000
--- a/chrome/browser/ui/webui/options/chromeos/guest_mode_options_ui_browsertest.cc
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/command_line.h"
-#include "chrome/browser/ui/webui/options/options_ui_browsertest.h"
-#include "chrome/common/chrome_switches.h"
-#include "chrome/test/base/testing_profile.h"
-#include "chromeos/chromeos_switches.h"
-#include "components/signin/core/account_id/account_id.h"
-#include "components/user_manager/user_names.h"
-
-namespace {
-
-// Same as OptionsUIBrowserTest but launches with Guest mode command line
-// switches.
-class GuestModeOptionsBrowserTest : public options::OptionsUIBrowserTest {
- public:
-  GuestModeOptionsBrowserTest() : OptionsUIBrowserTest() {}
-
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    command_line->AppendSwitch(chromeos::switches::kGuestSession);
-    command_line->AppendSwitchASCII(
-        chromeos::switches::kLoginUser,
-        user_manager::GuestAccountId().GetUserEmail());
-    command_line->AppendSwitchASCII(chromeos::switches::kLoginProfile,
-                                    TestingProfile::kTestUserProfileDir);
-    command_line->AppendSwitch(switches::kIncognito);
-  }
-};
-
-IN_PROC_BROWSER_TEST_F(GuestModeOptionsBrowserTest, LoadOptionsByURL) {
-  NavigateToSettings();
-  VerifyTitle();
-  VerifyNavbar();
-}
-
-}  // namespace
diff --git a/chrome/browser/ui/webui/options/clear_browser_data_browsertest.cc b/chrome/browser/ui/webui/options/clear_browser_data_browsertest.cc
deleted file mode 100644
index 7806323..0000000
--- a/chrome/browser/ui/webui/options/clear_browser_data_browsertest.cc
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <stddef.h>
-
-#include "base/macros.h"
-#include "build/build_config.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/webui/options/options_ui_browsertest.h"
-#include "chrome/common/url_constants.h"
-#include "components/prefs/pref_service.h"
-#include "content/public/browser/browsing_data_remover.h"
-#include "content/public/test/browser_test_utils.h"
-#include "content/public/test/browsing_data_remover_test_util.h"
-
-namespace options {
-
-class ClearBrowserDataBrowserTest : public OptionsUIBrowserTest {
- protected:
-  void ClickElement(const std::string& selector) {
-    bool element_enabled = false;
-    ASSERT_NO_FATAL_FAILURE(GetElementEnabledState(selector, &element_enabled));
-    ASSERT_TRUE(element_enabled);
-    ASSERT_TRUE(content::ExecuteScript(
-        GetSettingsFrame(),
-        "document.querySelector('" + selector + "').click();"));
-  }
-
-  bool IsElementEnabled(const std::string& selector) {
-    bool element_enabled = false;
-    GetElementEnabledState(selector, &element_enabled);
-    return element_enabled;
-  }
-
- private:
-  void GetElementEnabledState(
-      const std::string& selector,
-      bool* enabled) {
-    ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
-        GetSettingsFrame(),
-        "window.domAutomationController.send(!document.querySelector('" +
-            selector + "').disabled);",
-        enabled));
-  }
-};
-
-// http://crbug.com/458684
-#if defined(OS_WIN) || defined(OS_MACOSX)
-#define MAYBE_CommitButtonDisabledWhileDeletionInProgress \
-    DISABLED_CommitButtonDisabledWhileDeletionInProgress
-#else
-#define MAYBE_CommitButtonDisabledWhileDeletionInProgress \
-    CommitButtonDisabledWhileDeletionInProgress
-#endif
-
-IN_PROC_BROWSER_TEST_F(ClearBrowserDataBrowserTest,
-                       MAYBE_CommitButtonDisabledWhileDeletionInProgress) {
-  const char kCommitButtonId[] = "#clear-browser-data-commit";
-  content::BrowsingDataRemoverCompletionInhibitor completion_inhibitor(
-      content::BrowserContext::GetBrowsingDataRemover(browser()->profile()));
-
-  // Navigate to the Clear Browsing Data dialog to ensure that the commit button
-  // is initially enabled, usable, and gets disabled after having been pressed.
-  NavigateToSettingsSubpage(chrome::kClearBrowserDataSubPage);
-  ASSERT_NO_FATAL_FAILURE(ClickElement(kCommitButtonId));
-  EXPECT_FALSE(IsElementEnabled(kCommitButtonId));
-
-  completion_inhibitor.BlockUntilNearCompletion();
-
-  // Simulate a reload while the previous removal is still running, and verify
-  // that the button is still disabled.
-  NavigateToSettingsSubpage(chrome::kClearBrowserDataSubPage);
-  EXPECT_FALSE(IsElementEnabled(kCommitButtonId));
-
-  completion_inhibitor.ContinueToCompletion();
-
-  // However, the button should be enabled again once the process has finished.
-  NavigateToSettingsSubpage(chrome::kClearBrowserDataSubPage);
-  EXPECT_TRUE(IsElementEnabled(kCommitButtonId));
-}
-
-IN_PROC_BROWSER_TEST_F(ClearBrowserDataBrowserTest,
-                       CommitButtonDisabledWhenNoDataTypesSelected) {
-  const char kCommitButtonId[] = "#clear-browser-data-commit";
-  const char* kDataTypes[] = {"browser.clear_data.browsing_history",
-                              "browser.clear_data.download_history",
-                              "browser.clear_data.cache",
-                              "browser.clear_data.cookies",
-                              "browser.clear_data.passwords",
-                              "browser.clear_data.form_data",
-                              "browser.clear_data.hosted_apps_data",
-                              "browser.clear_data.media_licenses"};
-
-  PrefService* prefs = browser()->profile()->GetPrefs();
-  for (size_t i = 0; i < arraysize(kDataTypes); ++i) {
-    prefs->SetBoolean(kDataTypes[i], false);
-  }
-
-  // Navigate to the Clear Browsing Data dialog to ensure that the commit button
-  // is disabled if clearing is not requested for any of the data types.
-  NavigateToSettingsSubpage(chrome::kClearBrowserDataSubPage);
-  EXPECT_FALSE(IsElementEnabled(kCommitButtonId));
-
-  // However, expect the commit button to be re-enabled if any of the data types
-  // gets selected to be cleared.
-  for (size_t i = 0; i < arraysize(kDataTypes); ++i) {
-    prefs->SetBoolean(kDataTypes[i], true);
-    EXPECT_TRUE(IsElementEnabled(kCommitButtonId));
-    prefs->SetBoolean(kDataTypes[i], false);
-  }
-}
-
-}  // namespace options
diff --git a/chrome/browser/ui/webui/options/content_settings_exception_area_browsertest.cc b/chrome/browser/ui/webui/options/content_settings_exception_area_browsertest.cc
deleted file mode 100644
index e149f7e..0000000
--- a/chrome/browser/ui/webui/options/content_settings_exception_area_browsertest.cc
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/browser_commands.h"
-#include "chrome/browser/ui/webui/options/options_ui_browsertest.h"
-
-typedef options::OptionsUIBrowserTest ContentSettingsExceptionsAreaBrowserTest;
-
-// Test that an incognito window can be opened while the exceptions page is
-// open. If this test fails it could indicate that a new content setting has
-// been added but is not being dealt with correctly by the content settings
-// handling WebUI code.
-#if defined(OS_MACOSX) && defined(ADDRESS_SANITIZER)
-// Flaky on ASAN on Mac. See https://crbug.com/674497.
-#define MAYBE_OpenIncognitoWindow DISABLED_OpenIncognitoWindow
-#else
-#define MAYBE_OpenIncognitoWindow OpenIncognitoWindow
-#endif
-IN_PROC_BROWSER_TEST_F(ContentSettingsExceptionsAreaBrowserTest,
-                       MAYBE_OpenIncognitoWindow) {
-  NavigateToSettingsSubpage("contentExceptions");
-  chrome::NewIncognitoWindow(browser());
-}
diff --git a/chrome/browser/ui/webui/options/language_dictionary_interactive_uitest.cc b/chrome/browser/ui/webui/options/language_dictionary_interactive_uitest.cc
deleted file mode 100644
index dbdad4f2..0000000
--- a/chrome/browser/ui/webui/options/language_dictionary_interactive_uitest.cc
+++ /dev/null
@@ -1,255 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/macros.h"
-#include "base/strings/stringprintf.h"
-#include "base/test/scoped_feature_list.h"
-#include "build/build_config.h"
-#include "chrome/browser/chrome_notification_types.h"
-#include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/chrome_pages.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/common/chrome_features.h"
-#include "chrome/common/url_constants.h"
-#include "chrome/test/base/in_process_browser_test.h"
-#include "chrome/test/base/interactive_test_utils.h"
-#include "content/public/test/browser_test_utils.h"
-
-namespace {
-
-// This class tests the language dictionary settings.
-// This test is part of the interactive_ui_tests instead of browser_tests
-// because it is necessary to emulate pushing the tab key.
-class LanguageDictionaryWebUITest : public InProcessBrowserTest {
- public:
-  LanguageDictionaryWebUITest() {}
-
-  // Navigate to the editDictionary page.
-  void SetUpOnMainThread() override {
-    disable_md_settings_.InitAndDisableFeature(
-        features::kMaterialDesignSettings);
-    const GURL url = chrome::GetSettingsUrl("editDictionary");
-    ui_test_utils::NavigateToURL(browser(), url);
-  }
-
- protected:
-  const std::string kDictionaryListSelector =
-      "#language-dictionary-overlay-word-list";
-
-  content::RenderFrameHost* GetActiveFrame() {
-    return GetActiveWebContents()->GetFocusedFrame();
-  }
-
-  content::RenderViewHost* GetRenderViewHost() {
-    return GetActiveWebContents()->GetRenderViewHost();
-  }
-
-  content::WebContents* GetActiveWebContents() {
-    return browser()->tab_strip_model()->GetActiveWebContents();
-  }
-
-  // Add a few test words to the dictionary.
-  void SetTestWords(const std::string& list_selector) {
-    const std::string script = base::StringPrintf(
-        "document.querySelector('%s').setWordList(['cat', 'dog', 'bird']);",
-        list_selector.c_str());
-    EXPECT_TRUE(content::ExecuteScript(GetActiveFrame(), script));
-    // Expected list size is 4: 3 word items + 1 placeholder.
-    EXPECT_EQ(4, GetListSize(list_selector));
-  }
-
-  // Returns the number of items in the list.
-  int GetListSize(const std::string& list_selector) {
-    const std::string script = base::StringPrintf(
-        "domAutomationController.send("
-        "document.querySelector('%s').items.length);",
-        list_selector.c_str());
-    int length = -1;
-    EXPECT_TRUE(content::ExecuteScriptAndExtractInt(
-        GetActiveFrame(),
-        script,
-        &length));
-    return length;
-  }
-
-  // Returns true if element contains document.activeElement.
-  bool ContainsActiveElement(const std::string& element_selector) {
-    const std::string script = base::StringPrintf(
-        "domAutomationController.send("
-        "document.querySelector('%s').contains(document.activeElement));",
-        element_selector.c_str());
-    bool result;
-    EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
-        GetActiveFrame(),
-        script,
-        &result));
-    return result;
-  }
-
-  // Returns true if list item[|index|] contains document.activeElement.
-  bool ListItemContainsActiveElement(const std::string& list_selector,
-                                     int index) {
-    EXPECT_GE(index, 0);
-    // EXPECT_TRUE will fail if index is out of bounds.
-    const std::string script = base::StringPrintf(
-        "domAutomationController.send("
-        "document.querySelector('%s').items[%d].contains("
-        "document.activeElement));",
-        list_selector.c_str(),
-        index);
-    bool result;
-    EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
-        GetActiveFrame(),
-        script,
-        &result));
-    return result;
-  }
-
-  // Returns true if list item[|index|] has 'selected' attribute.
-  bool ListItemSelected(const std::string& list_selector, int index) {
-    EXPECT_GE(index, 0);
-    // EXPECT_TRUE will fail if index is out of bounds.
-    const std::string script = base::StringPrintf(
-        "domAutomationController.send("
-        "document.querySelector('%s').items[%d].hasAttribute('selected'));",
-        list_selector.c_str(),
-        index);
-    bool result = false;
-    EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
-        GetActiveFrame(),
-        script,
-        &result));
-    return result;
-  }
-
-  // Returns true if list item[|index|] has 'selected' attribute and contains
-  // document.activeElement.
-  bool ListItemSelectedAndFocused(const std::string& list_selector,
-                                  int index) {
-    EXPECT_GE(index, 0);
-    return ListItemSelected(list_selector, index) &&
-           ListItemContainsActiveElement(list_selector, index);
-  }
-
-  // Press and release a key in the browser. This will wait for the element on
-  // the page to change.
-  bool PressKey(ui::KeyboardCode key_code, bool shift) {
-    return ui_test_utils::SendKeyPressAndWait(
-        browser(),
-        key_code,
-        false,
-        shift,
-        false,
-        false,
-        content::NOTIFICATION_FOCUS_CHANGED_IN_PAGE,
-        content::Source<content::RenderViewHost>(GetRenderViewHost()));
-  }
-
-  void InitializeDomMessageQueue() {
-    dom_message_queue_.reset(new content::DOMMessageQueue);
-  }
-
-  // Wait for a message from the DOM automation controller.
-  void WaitForDomMessage(const std::string& message) {
-    const std::string expected = "\"" + message + "\"";
-    std::string received;
-    do {
-      ASSERT_TRUE(dom_message_queue_->WaitForMessage(&received));
-    } while (received != expected);
-  }
-
-  // Add a JavaScript event listener to send a DOM automation controller message
-  // whenever the |selected| property of the list item changes.
-  void ListenForItemSelectedChange(const std::string& list_selector,
-                                   int index) {
-    EXPECT_GE(index, 0);
-    // EXPECT_TRUE will fail if index is out of bounds.
-    const std::string script = base::StringPrintf(
-        "document.querySelector('%s').items[%d].addEventListener("
-            "'selectedChange', function() {"
-              "domAutomationController.setAutomationId(0);"
-              "domAutomationController.send('selected=' + this.selected);"
-            "});",
-        list_selector.c_str(),
-        index);
-
-    EXPECT_TRUE(content::ExecuteScript(
-        GetActiveFrame(),
-        script));
-  }
-
- private:
-  std::unique_ptr<content::DOMMessageQueue> dom_message_queue_;
-  base::test::ScopedFeatureList disable_md_settings_;
-
-  DISALLOW_COPY_AND_ASSIGN(LanguageDictionaryWebUITest);
-};
-
-}  // namespace
-
-// Test InlineEditableItemList keyboard focus behavior in editDictionary
-// overlay.
-// editDictionary overlay doesn't exist on OSX so disable it there.
-#if !defined(OS_MACOSX)
-
-// Crashes on Win 7. http://crbug.com/500609
-#if defined(OS_WIN)
-#define MAYBE_TestListKeyboardFocus DISABLED_TestListKeyboardFocus
-#else
-#define MAYBE_TestListKeyboardFocus TestListKeyboardFocus
-#endif
-
-IN_PROC_BROWSER_TEST_F(LanguageDictionaryWebUITest,
-                       MAYBE_TestListKeyboardFocus) {
-  const std::string list_selector = kDictionaryListSelector;
-
-  // Populate the list with some test words.
-  SetTestWords(list_selector);
-  int placeholder_index = GetListSize(list_selector) - 1;
-
-  // Listen for changes of the placeholder item's |selected| property so that
-  // test can wait until change has taken place after key press before
-  // continuing.
-  InitializeDomMessageQueue();
-  ListenForItemSelectedChange(list_selector, placeholder_index);
-
-  // Press tab to focus the placeholder.
-  PressKey(ui::VKEY_TAB, false);
-
-  // Wait for placeholder item to become selected.
-  WaitForDomMessage("selected=true");
-
-  // Verify that the placeholder is selected and has focus.
-  EXPECT_TRUE(ListItemSelectedAndFocused(list_selector, placeholder_index));
-
-  // Press up arrow to select item above the placeholder.
-  PressKey(ui::VKEY_UP, false);
-
-  // Wait for placeholder to become unselected.
-  WaitForDomMessage("selected=false");
-
-  // Verify that the placeholder is no longer selected.
-  EXPECT_FALSE(ListItemSelected(list_selector, placeholder_index));
-
-  // Verify that the item above the placeholder is selected and has focus.
-  EXPECT_TRUE(ListItemSelectedAndFocused(list_selector,
-                                         placeholder_index - 1));
-
-  // Press tab to leave the list.
-  PressKey(ui::VKEY_TAB, false);
-
-  // Verify that focus has left the list.
-  EXPECT_FALSE(ContainsActiveElement(list_selector));
-
-  // Verify that the item above the placeholder is still selected.
-  EXPECT_TRUE(ListItemSelected(list_selector, placeholder_index - 1));
-
-  // Press shift+tab to go back to the list.
-  PressKey(ui::VKEY_TAB, true);
-
-  // Verify that the item above the placeholder is selected and has focus.
-  EXPECT_TRUE(ListItemSelectedAndFocused(list_selector,
-                                         placeholder_index - 1));
-}
-#endif  // !defined(OS_MACOSX)
diff --git a/chrome/browser/ui/webui/options/language_options_interactive_uitest.cc b/chrome/browser/ui/webui/options/language_options_interactive_uitest.cc
deleted file mode 100644
index e9fa0e27..0000000
--- a/chrome/browser/ui/webui/options/language_options_interactive_uitest.cc
+++ /dev/null
@@ -1,166 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/macros.h"
-#include "base/test/scoped_feature_list.h"
-#include "build/build_config.h"
-#include "chrome/browser/chrome_notification_types.h"
-#include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/chrome_pages.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/common/chrome_features.h"
-#include "chrome/common/pref_names.h"
-#include "chrome/common/url_constants.h"
-#include "chrome/test/base/in_process_browser_test.h"
-#include "chrome/test/base/interactive_test_utils.h"
-#include "chrome/test/base/ui_test_utils.h"
-#include "components/prefs/pref_service.h"
-#include "content/public/browser/render_frame_host.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/test/browser_test_utils.h"
-
-namespace language_options_ui_test {
-
-namespace {
-
-// This class will test the language options settings.
-// This test is part of the interactive_ui_tests isntead of browser_tests
-// because it is necessary to emulate pushing a button in order to properly
-// test accessibility.
-class LanguageOptionsWebUITest : public InProcessBrowserTest {
- public:
-  LanguageOptionsWebUITest() {}
-
-  void SetUpInProcessBrowserTestFixture() override {
-    disable_md_settings_.InitAndDisableFeature(
-        features::kMaterialDesignSettings);
-  }
-
-  // This method will navigate to the language settings page and show
-  // a subset of languages from the list of available languages.
-  void SetUpOnMainThread() override {
-#if defined(OS_CHROMEOS)
-    auto* setting_name = prefs::kLanguagePreferredLanguages;
-#else
-    auto* setting_name = prefs::kAcceptLanguages;
-#endif
-
-    const GURL url = chrome::GetSettingsUrl(chrome::kLanguageOptionsSubPage);
-    ui_test_utils::NavigateToURL(browser(), url);
-    browser()->profile()->GetPrefs()->SetString(setting_name, "en-US,es,fr");
-  }
-
- protected:
-  // Will get the id of the element in the UI that has focus.
-  std::string GetActiveElementId() {
-    std::string get_element_id_script =
-        "domAutomationController.send(document.activeElement.id);";
-    std::string element_id;
-    EXPECT_TRUE(content::ExecuteScriptAndExtractString(
-          GetActiveFrame(),
-          get_element_id_script,
-          &element_id));
-    return element_id;
-  }
-
-  content::RenderFrameHost* GetActiveFrame() {
-    return GetActiveWebContents()->GetFocusedFrame();
-  }
-
-  content::RenderViewHost* GetRenderViewHost() {
-    return GetActiveWebContents()->GetRenderViewHost();
-  }
-
-  content::WebContents* GetActiveWebContents() {
-    return browser()->tab_strip_model()->GetActiveWebContents();
-  }
-
-  // Press and release a key in the browser. This will wait for the element on
-  // the page to change.
-  bool PressKey(ui::KeyboardCode key_code) {
-    return ui_test_utils::SendKeyPressAndWait(
-        browser(),
-        key_code,
-        false,
-        false,
-        false,
-        false,
-        content::NOTIFICATION_FOCUS_CHANGED_IN_PAGE,
-        content::Source<content::RenderViewHost>(GetRenderViewHost()));
-  }
-
- private:
-  base::test::ScopedFeatureList disable_md_settings_;
-
-  DISALLOW_COPY_AND_ASSIGN(LanguageOptionsWebUITest);
-};
-
-}  // namespace
-
-// This test will verify that the appropriate languages are available.
-// This test will also fail if the language page is not loaded because a random
-// page will not have the language list.
-// Test assumes that the default active element is the list of languages.
-IN_PROC_BROWSER_TEST_F(LanguageOptionsWebUITest, TestAvailableLanguages) {
-  // Verify that the language list is focused by default.
-  std::string original_id = GetActiveElementId();
-  EXPECT_EQ("language-options-list", original_id);
-
-  content::RenderFrameHost* active_frame = GetActiveFrame();
-
-  std::string count_deletable_items_script =
-    "domAutomationController.send("
-    "    document.activeElement.querySelectorAll('.deletable-item').length);";
-
-  // Count the number of languages in the list.
-  int language_count = 0;
-  ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
-      active_frame,
-      count_deletable_items_script,
-      &language_count));
-  EXPECT_EQ(3, language_count);
-
-  std::string get_children_of_current_element_script =
-      "domAutomationController.send(document.activeElement.textContent);";
-
-  // Verify that the correct languages are added to the list.
-  std::string languages;
-  ASSERT_TRUE(content::ExecuteScriptAndExtractString(
-      active_frame,
-      get_children_of_current_element_script,
-      &languages));
-  EXPECT_EQ("English (United States)SpanishFrench", languages);
-}
-
-// This test will validate that the language webui is accessible through
-// the keyboard.
-// This test must be updated if the tab order of the elements on this page
-// is changed.
-
-// Crashes on Win 7. http://crbug.com/500609
-#if defined(OS_WIN)
-#define MAYBE_TestListTabAccessibility DISABLED_TestListTabAccessibility
-#else
-#define MAYBE_TestListTabAccessibility TestListTabAccessibility
-#endif
-
-IN_PROC_BROWSER_TEST_F(LanguageOptionsWebUITest,
-    MAYBE_TestListTabAccessibility) {
-  // Verify that the language list is focused by default.
-  std::string original_id = GetActiveElementId();
-  EXPECT_EQ("language-options-list", original_id);
-
-  // Press tab to select the next element.
-  ASSERT_TRUE(PressKey(ui::VKEY_TAB));
-
-  // Make sure that the element is now the button that is next in the tab order.
-  // Checking that the list is no longer selected is not sufficient to validate
-  // this use case because this test should fail if an item inside the list is
-  // selected.
-  std::string new_id = GetActiveElementId();
-  EXPECT_EQ("language-options-add-button", new_id);
-}
-
-}  // namespace language_options_ui_test
-
diff --git a/chrome/browser/ui/webui/options/options_ui_browsertest.cc b/chrome/browser/ui/webui/options/options_ui_browsertest.cc
deleted file mode 100644
index b375226..0000000
--- a/chrome/browser/ui/webui/options/options_ui_browsertest.cc
+++ /dev/null
@@ -1,344 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/webui/options/options_ui_browsertest.h"
-
-#include "base/scoped_observer.h"
-#include "base/strings/string16.h"
-#include "base/strings/utf_string_conversions.h"
-#include "build/build_config.h"
-#include "chrome/browser/chrome_notification_types.h"
-#include "chrome/browser/signin/account_tracker_service_factory.h"
-#include "chrome/browser/signin/signin_manager_factory.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/chrome_pages.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/browser/ui/webui/options/options_ui.h"
-#include "chrome/browser/ui/webui/uber/uber_ui.h"
-#include "chrome/common/chrome_features.h"
-#include "chrome/common/url_constants.h"
-#include "chrome/grit/generated_resources.h"
-#include "chrome/test/base/ui_test_utils.h"
-#include "components/prefs/pref_service.h"
-#include "components/signin/core/browser/account_tracker_service.h"
-#include "components/signin/core/browser/signin_manager.h"
-#include "components/strings/grit/components_strings.h"
-#include "content/public/browser/notification_service.h"
-#include "content/public/browser/render_frame_host.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/test/browser_test_utils.h"
-#include "content/public/test/test_utils.h"
-#include "ui/base/l10n/l10n_util.h"
-
-#if !defined(OS_CHROMEOS)
-#include <string>
-
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/files/file_path.h"
-#include "base/run_loop.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_attributes_storage.h"
-#include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/ui/browser_commands.h"
-#include "content/public/test/test_navigation_observer.h"
-#include "ui/base/window_open_disposition.h"
-#include "url/gurl.h"
-#endif
-
-using content::MessageLoopRunner;
-
-namespace options {
-
-namespace {
-
-class SignOutWaiter : public SigninManagerBase::Observer {
- public:
-  explicit SignOutWaiter(SigninManagerBase* signin_manager)
-      : seen_(false), running_(false), scoped_observer_(this) {
-    scoped_observer_.Add(signin_manager);
-  }
-  ~SignOutWaiter() override {}
-
-  void Wait() {
-    if (seen_)
-      return;
-
-    running_ = true;
-    message_loop_runner_ = new MessageLoopRunner;
-    message_loop_runner_->Run();
-    EXPECT_TRUE(seen_);
-  }
-
-  void GoogleSignedOut(const std::string& account_id,
-                       const std::string& username) override {
-    seen_ = true;
-    if (!running_)
-      return;
-
-    message_loop_runner_->Quit();
-    running_ = false;
-  }
-
- private:
-  bool seen_;
-  bool running_;
-  ScopedObserver<SigninManagerBase, SignOutWaiter> scoped_observer_;
-  scoped_refptr<MessageLoopRunner> message_loop_runner_;
-};
-
-#if !defined(OS_CHROMEOS)
-void RunClosureWhenProfileInitialized(const base::Closure& closure,
-                                      Profile* profile,
-                                      Profile::CreateStatus status) {
-  if (status == Profile::CREATE_STATUS_INITIALIZED)
-    closure.Run();
-}
-#endif
-
-bool FrameHasSettingsSourceHost(content::RenderFrameHost* frame) {
-  return frame->GetLastCommittedURL().DomainIs(
-      chrome::kChromeUISettingsFrameHost);
-}
-
-}  // namespace
-
-OptionsUIBrowserTest::OptionsUIBrowserTest() {
-}
-
-void OptionsUIBrowserTest::SetUpInProcessBrowserTestFixture() {
-  InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
-  disable_md_settings_.InitAndDisableFeature(features::kMaterialDesignSettings);
-}
-
-void OptionsUIBrowserTest::NavigateToSettings() {
-  NavigateToSettingsSubpage("");
-}
-
-void OptionsUIBrowserTest::NavigateToSettingsSubpage(
-    const std::string& sub_page) {
-  const GURL& url = chrome::GetSettingsUrl(sub_page);
-  ui_test_utils::NavigateToURLWithDisposition(
-      browser(), url, WindowOpenDisposition::CURRENT_TAB, 0);
-
-  content::WebContents* web_contents =
-      browser()->tab_strip_model()->GetActiveWebContents();
-  ASSERT_TRUE(web_contents);
-  ASSERT_TRUE(web_contents->GetWebUI());
-
-  content::WebUIController* controller =
-      web_contents->GetWebUI()->GetController();
-#if !defined(OS_CHROMEOS)
-  controller = static_cast<UberUI*>(controller)->
-      GetSubpage(chrome::kChromeUISettingsFrameURL)->GetController();
-#endif
-  OptionsUI* options_ui = static_cast<OptionsUI*>(controller);
-
-  // It is not possible to subscribe to the OnFinishedLoading event before the
-  // call to NavigateToURL(), because the WebUI does not yet exist at that time.
-  // However, it is safe to subscribe afterwards, because the event will always
-  // be posted asynchronously to the message loop.
-  scoped_refptr<MessageLoopRunner> message_loop_runner(new MessageLoopRunner);
-  std::unique_ptr<OptionsUI::OnFinishedLoadingCallbackList::Subscription>
-      subscription = options_ui->RegisterOnFinishedLoadingCallback(
-          message_loop_runner->QuitClosure());
-  message_loop_runner->Run();
-
-  // The OnFinishedLoading event, which indicates that all WebUI initialization
-  // methods have been called on the JS side, is temporally unrelated to whether
-  // or not the WebContents considers itself to have finished loading. We want
-  // to wait for this too, however, because, e.g. this is a sufficient condition
-  // to get the focus properly placed on a form element.
-  content::WaitForLoadStop(web_contents);
-}
-
-void OptionsUIBrowserTest::NavigateToSettingsFrame() {
-  const GURL& url = GURL(chrome::kChromeUISettingsFrameURL);
-  ui_test_utils::NavigateToURL(browser(), url);
-}
-
-void OptionsUIBrowserTest::VerifyNavbar() {
-  bool navbar_exist = false;
-#if defined(OS_CHROMEOS)
-  bool should_navbar_exist = false;
-#else
-  bool should_navbar_exist = true;
-#endif
-  EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
-      browser()->tab_strip_model()->GetActiveWebContents(),
-      "domAutomationController.send("
-      "    !!document.getElementById('navigation'))",
-      &navbar_exist));
-  EXPECT_EQ(should_navbar_exist, navbar_exist);
-}
-
-void OptionsUIBrowserTest::VerifyTitle() {
-  base::string16 title =
-      browser()->tab_strip_model()->GetActiveWebContents()->GetTitle();
-  base::string16 expected_title = l10n_util::GetStringUTF16(IDS_SETTINGS_TITLE);
-  EXPECT_NE(title.find(expected_title), base::string16::npos);
-}
-
-content::RenderFrameHost* OptionsUIBrowserTest::GetSettingsFrame() {
-  // NB: The utility function content::FrameHasSourceUrl can't be used because
-  // the settings frame navigates itself to chrome://settings-frame/settings
-  // to indicate that it's showing the top-level settings. Therefore, just
-  // match the host.
-  return content::FrameMatchingPredicate(
-      browser()->tab_strip_model()->GetActiveWebContents(),
-      base::Bind(&FrameHasSettingsSourceHost));
-}
-
-IN_PROC_BROWSER_TEST_F(OptionsUIBrowserTest, LoadOptionsByURL) {
-  NavigateToSettings();
-  VerifyTitle();
-  VerifyNavbar();
-}
-
-// Flaky on Linux, Mac and Win: http://crbug.com/469113
-#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX)
-#define MAYBE_VerifyManagedSignout DISABLED_VerifyManagedSignout
-#else
-#define MAYBE_VerifyManagedSignout VerifyManagedSignout
-#endif
-
-#if !defined(OS_CHROMEOS)
-IN_PROC_BROWSER_TEST_F(OptionsUIBrowserTest, MAYBE_VerifyManagedSignout) {
-  SigninManager* signin =
-      SigninManagerFactory::GetForProfile(browser()->profile());
-  signin->OnExternalSigninCompleted("test@example.com");
-  signin->ProhibitSignout(true);
-
-  NavigateToSettingsFrame();
-
-  // This script simulates a click on the "Disconnect your Google Account"
-  // button and returns true if the hidden flag of the appropriate dialog gets
-  // flipped.
-  bool result = false;
-  ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
-      browser()->tab_strip_model()->GetActiveWebContents(),
-      "var dialog = $('manage-profile-overlay-disconnect-managed');"
-      "var original_status = dialog.hidden;"
-      "var original = ManageProfileOverlay.showDisconnectManagedProfileDialog;"
-      "var teststub = function(event) {"
-      "  original(event);"
-      "  domAutomationController.send(original_status && !dialog.hidden);"
-      "};"
-      "ManageProfileOverlay.showDisconnectManagedProfileDialog = teststub;"
-      "$('start-stop-sync').click();",
-      &result));
-
-  EXPECT_TRUE(result);
-
-  base::FilePath profile_dir = browser()->profile()->GetPath();
-  ProfileAttributesStorage& storage =
-      g_browser_process->profile_manager()->GetProfileAttributesStorage();
-  ProfileAttributesEntry* entry;
-
-  EXPECT_TRUE(DirectoryExists(profile_dir));
-  EXPECT_TRUE(storage.GetProfileAttributesWithPath(profile_dir, &entry));
-
-  // TODO(kaliamoorthi): Get the macos problem fixed and remove this code.
-  // Deleting the Profile also destroys all browser windows of that Profile.
-  // Wait for the current browser to close before resuming, otherwise
-  // the browser_tests shutdown code will be confused on the Mac.
-  content::WindowedNotificationObserver wait_for_browser_closed(
-      chrome::NOTIFICATION_BROWSER_CLOSED,
-      content::NotificationService::AllSources());
-
-  ASSERT_TRUE(content::ExecuteScript(
-      browser()->tab_strip_model()->GetActiveWebContents(),
-      "$('disconnect-managed-profile-ok').click();"));
-
-  EXPECT_TRUE(storage.GetProfileAttributesWithPath(profile_dir, &entry));
-
-  wait_for_browser_closed.Wait();
-}
-
-IN_PROC_BROWSER_TEST_F(OptionsUIBrowserTest, VerifyUnmanagedSignout) {
-  const std::string user = "test@example.com";
-  AccountTrackerServiceFactory::GetForProfile(browser()->profile())
-      ->SeedAccountInfo("12345", user);
-  SigninManager* signin =
-      SigninManagerFactory::GetForProfile(browser()->profile());
-  signin->OnExternalSigninCompleted(user);
-
-  NavigateToSettingsFrame();
-
-  // This script simulates a click on the "Disconnect your Google Account"
-  // button and returns true if the hidden flag of the appropriate dialog gets
-  // flipped.
-  bool result = false;
-  ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
-      browser()->tab_strip_model()->GetActiveWebContents(),
-      "var dialog = $('sync-setup-stop-syncing');"
-      "var original_status = dialog.hidden;"
-      "$('start-stop-sync').click();"
-      "domAutomationController.send(original_status && !dialog.hidden);",
-      &result));
-
-  EXPECT_TRUE(result);
-
-  SignOutWaiter sign_out_waiter(signin);
-
-  ASSERT_TRUE(content::ExecuteScript(
-      browser()->tab_strip_model()->GetActiveWebContents(),
-      "$('stop-syncing-ok').click();"));
-
-  sign_out_waiter.Wait();
-
-  EXPECT_TRUE(browser()->profile()->GetProfileUserName() != user);
-  EXPECT_FALSE(signin->IsAuthenticated());
-}
-
-// Regression test for http://crbug.com/301436, excluded on Chrome OS because
-// profile management in the settings UI exists on desktop platforms only.
-IN_PROC_BROWSER_TEST_F(OptionsUIBrowserTest, NavigateBackFromOverlayDialog) {
-  NavigateToSettingsFrame();
-
-  // Click a button that opens an overlay dialog.
-  content::WebContents* contents =
-      browser()->tab_strip_model()->GetActiveWebContents();
-  ASSERT_TRUE(content::ExecuteScript(
-      contents, "$('manage-default-search-engines').click();"));
-
-  // Go back to the settings page.
-  content::TestNavigationObserver observer(contents);
-  chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
-  observer.Wait();
-
-  // Verify that the settings page lists one profile.
-  const char javascript[] =
-      "domAutomationController.send("
-      "    document.querySelectorAll('list#profiles-list > div[role=listitem]')"
-      "        .length);";
-  int profiles;
-  ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
-      contents, javascript, &profiles));
-  EXPECT_EQ(1, profiles);
-
-  // Create a second profile.
-  ProfileManager* profile_manager = g_browser_process->profile_manager();
-  const base::FilePath profile_path =
-      profile_manager->GenerateNextProfileDirectoryPath();
-
-  base::RunLoop run_loop;
-  profile_manager->CreateProfileAsync(
-      profile_manager->GenerateNextProfileDirectoryPath(),
-      base::Bind(&RunClosureWhenProfileInitialized,
-                 run_loop.QuitClosure()),
-                 base::string16(),
-                 std::string(),
-                 std::string());
-  run_loop.Run();
-
-  // Verify that the settings page has updated and lists two profiles.
-  ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
-      contents, javascript, &profiles));
-  EXPECT_EQ(2, profiles);
-}
-#endif
-
-}  // namespace options
diff --git a/chrome/browser/ui/webui/options/options_ui_browsertest.h b/chrome/browser/ui/webui/options/options_ui_browsertest.h
deleted file mode 100644
index 35b49b2..0000000
--- a/chrome/browser/ui/webui/options/options_ui_browsertest.h
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS_OPTIONS_UI_BROWSERTEST_H_
-#define CHROME_BROWSER_UI_WEBUI_OPTIONS_OPTIONS_UI_BROWSERTEST_H_
-
-#include <string>
-
-#include "base/macros.h"
-#include "base/test/scoped_feature_list.h"
-#include "chrome/test/base/in_process_browser_test.h"
-
-namespace content {
-class RenderFrameHost;
-}
-
-namespace options {
-
-class OptionsUIBrowserTest : public InProcessBrowserTest {
- public:
-  OptionsUIBrowserTest();
-
-  void SetUpInProcessBrowserTestFixture() override;
-
-  // Navigate to the Uber/Settings page and block until it has loaded.
-  void NavigateToSettings();
-
-  // Navigate to a certain subpage in the Uber/Settings page and block until it
-  // has loaded.
-  void NavigateToSettingsSubpage(const std::string& sub_page);
-
-  // Navigate to the Settings frame and block until complete.
-  void NavigateToSettingsFrame();
-
-  // Check navbar's existence.
-  void VerifyNavbar();
-
-  // Verify that the page title is correct.
-  // The only guarantee we can make about the title of a settings tab is that
-  // it should contain IDS_SETTINGS_TITLE somewhere.
-  void VerifyTitle();
-
- protected:
-  // Returns the RenderFrameHost associated with the Settings frame inside the
-  // Uber UI page (which must be already loaded).
-  content::RenderFrameHost* GetSettingsFrame();
-
- private:
-  base::test::ScopedFeatureList disable_md_settings_;
-
-  DISALLOW_COPY_AND_ASSIGN(OptionsUIBrowserTest);
-};
-
-}  // namespace options
-
-#endif  // CHROME_BROWSER_UI_WEBUI_OPTIONS_OPTIONS_UI_BROWSERTEST_H_
diff --git a/chrome/browser/ui/webui/settings/site_settings_handler.cc b/chrome/browser/ui/webui/settings/site_settings_handler.cc
index 59a527e..8d1981ca 100644
--- a/chrome/browser/ui/webui/settings/site_settings_handler.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_handler.cc
@@ -440,8 +440,12 @@
       map, content_type, extension_registry, web_ui(), /*incognito=*/false,
       /*filter=*/nullptr, exceptions.get());
 
-  if (profile_->HasOffTheRecordProfile()) {
-    Profile* incognito = profile_->GetOffTheRecordProfile();
+  Profile* incognito = profile_->HasOffTheRecordProfile()
+                           ? profile_->GetOffTheRecordProfile()
+                           : nullptr;
+  // On Chrome OS in Guest mode the incognito profile is the primary profile,
+  // so do not fetch an extra copy of the same exceptions.
+  if (incognito && incognito != profile_) {
     map = HostContentSettingsMapFactory::GetForProfile(incognito);
     extension_registry = extensions::ExtensionRegistry::Get(incognito);
     site_settings::GetExceptionsFromHostContentSettingsMap(
diff --git a/chrome/browser/ui/webui/signin/user_manager_screen_handler.cc b/chrome/browser/ui/webui/signin/user_manager_screen_handler.cc
index d608631..c0e1b8b 100644
--- a/chrome/browser/ui/webui/signin/user_manager_screen_handler.cc
+++ b/chrome/browser/ui/webui/signin/user_manager_screen_handler.cc
@@ -10,7 +10,6 @@
 #include <vector>
 
 #include "base/bind.h"
-#include "base/feature_list.h"
 #include "base/location.h"
 #include "base/macros.h"
 #include "base/metrics/histogram_macros.h"
@@ -48,7 +47,6 @@
 #include "chrome/browser/ui/webui/profile_helper.h"
 #include "chrome/browser/ui/webui/signin/login_ui_service.h"
 #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
-#include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/grit/chromium_strings.h"
@@ -307,9 +305,8 @@
   const PrefService::Preference* add_person_enabled_pref =
       service->FindPreference(prefs::kBrowserAddPersonEnabled);
 
-  if (base::FeatureList::IsEnabled(features::kMaterialDesignSettings) &&
-      (guest_mode_enabled_pref->HasUserSetting() ||
-       add_person_enabled_pref->HasUserSetting())) {
+  if (guest_mode_enabled_pref->HasUserSetting() ||
+      add_person_enabled_pref->HasUserSetting()) {
     service->ClearPref(guest_mode_enabled_pref->name());
     service->ClearPref(add_person_enabled_pref->name());
     base::RecordAction(
diff --git a/chrome/browser/ui/webui/uber/uber_ui.cc b/chrome/browser/ui/webui/uber/uber_ui.cc
index 5a8dfc15..aa7a4f95 100644
--- a/chrome/browser/ui/webui/uber/uber_ui.cc
+++ b/chrome/browser/ui/webui/uber/uber_ui.cc
@@ -79,9 +79,8 @@
 
   source->AddBoolean("hideExtensions",
       base::FeatureList::IsEnabled(features::kMaterialDesignExtensions));
-  source->AddBoolean("hideSettingsAndHelp",
-      ::switches::SettingsWindowEnabled() ||
-      base::FeatureList::IsEnabled(features::kMaterialDesignSettings));
+  // TODO(dbeam): remove hideSettingsAndHelp soon.
+  source->AddBoolean("hideSettingsAndHelp", true);
   source->AddString("extensionsHost", chrome::kChromeUIExtensionsHost);
   source->AddLocalizedString("extensionsDisplayName",
                              IDS_MANAGE_EXTENSIONS_SETTING_WINDOWS_TITLE);
diff --git a/chrome/browser/ui/webui/uber/uber_ui_browsertest.cc b/chrome/browser/ui/webui/uber/uber_ui_browsertest.cc
deleted file mode 100644
index 01eca7b..0000000
--- a/chrome/browser/ui/webui/uber/uber_ui_browsertest.cc
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (c) 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <string>
-#include <utility>
-
-#include "base/command_line.h"
-#include "base/macros.h"
-#include "base/test/scoped_feature_list.h"
-#include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/extensions/test_extension_system.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/common/chrome_features.h"
-#include "chrome/common/chrome_switches.h"
-#include "chrome/common/url_constants.h"
-#include "chrome/test/base/ui_test_utils.h"
-#include "chrome/test/base/web_ui_browser_test.h"
-#include "content/public/common/content_switches.h"
-#include "content/public/test/browser_test_utils.h"
-#include "extensions/browser/extension_registry.h"
-#include "extensions/common/extension.h"
-#include "extensions/common/extension_builder.h"
-
-class UberUIBrowserTest : public WebUIBrowserTest {
- public:
-  UberUIBrowserTest() {}
-  ~UberUIBrowserTest() override {}
-
-  bool GetJsBool(const char* js) {
-    bool result = false;
-    EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
-        GetWebContents(),
-        std::string("domAutomationController.send(") + js + ");",
-        &result));
-    return result;
-  }
-
-  void SelectTab(const std::string& name) {
-    ASSERT_TRUE(content::ExecuteScript(
-        GetWebContents(),
-        std::string("var data = {pageId: '") + name + "'};" +
-            "uber.invokeMethodOnWindow(this, 'changeSelection', data);"));
-  }
-
- private:
-  content::WebContents* GetWebContents() {
-    return browser()->tab_strip_model()->GetActiveWebContents();
-  }
-
-  DISALLOW_COPY_AND_ASSIGN(UberUIBrowserTest);
-};
-
-IN_PROC_BROWSER_TEST_F(UberUIBrowserTest, EnableMdExtensionsHidesExtensions) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatures({features::kMaterialDesignExtensions},
-                                       {features::kMaterialDesignSettings});
-
-  ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUIUberFrameURL));
-  SelectTab("settings");
-  EXPECT_TRUE(GetJsBool("$('extensions').hidden"));
-}
-
-IN_PROC_BROWSER_TEST_F(UberUIBrowserTest, EnableMdSettingsHidesSettings) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatures({features::kMaterialDesignSettings},
-                                       {features::kMaterialDesignExtensions});
-
-  ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUIUberFrameURL));
-  SelectTab("extensions");
-  EXPECT_TRUE(GetJsBool("$('settings').hidden && $('help').hidden"));
-}
-
-IN_PROC_BROWSER_TEST_F(UberUIBrowserTest,
-                       EnableSettingsWindowHidesSettingsAndHelp) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(features::kMaterialDesignSettings);
-
-  base::CommandLine::ForCurrentProcess()->AppendSwitch(
-      ::switches::kEnableSettingsWindow);
-  ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUIUberFrameURL));
-  SelectTab("extensions");
-  EXPECT_TRUE(GetJsBool("$('settings').hidden && $('help').hidden"));
-}
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index b70aa576..d8aa673 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -203,11 +203,6 @@
 #endif
 };
 
-// Enables or disables the Material Design version of chrome://settings.
-// Also affects chrome://help.
-const base::Feature kMaterialDesignSettings{"MaterialDesignSettings",
-                                            base::FEATURE_ENABLED_BY_DEFAULT};
-
 #if !defined(OS_ANDROID) && !defined(OS_IOS)
 // Enables media content bitstream remoting, an optimization that can activate
 // during Cast Tab Mirroring.
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 471c1b5..a5f748d8 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -109,8 +109,6 @@
 
 extern const base::Feature kMaterialDesignIncognitoNTP;
 
-extern const base::Feature kMaterialDesignSettings;
-
 #if !defined(OS_ANDROID) && !defined(OS_IOS)
 extern const base::Feature kMediaRemoting;
 extern const base::Feature kMediaRouterUIRouteController;
diff --git a/chrome/common/url_constants.cc b/chrome/common/url_constants.cc
index 8fee2ee..28fe0e1 100644
--- a/chrome/common/url_constants.cc
+++ b/chrome/common/url_constants.cc
@@ -373,7 +373,6 @@
 const char kAccessibilitySubPage[] = "accessibility";
 const char kBluetoothSubPage[] = "bluetoothDevices";
 const char kDateTimeSubPage[] = "dateTime";
-const char kDeprecatedOptionsSearchSubPage[] = "search";
 const char kDisplaySubPage[] = "display";
 const char kHelpSubPage[] = "help";
 const char kInternetSubPage[] = "internet";
diff --git a/chrome/common/url_constants.h b/chrome/common/url_constants.h
index 0e9d600..a7e788c2 100644
--- a/chrome/common/url_constants.h
+++ b/chrome/common/url_constants.h
@@ -348,7 +348,6 @@
 extern const char kBluetoothSubPage[];
 extern const char kDateTimeSubPage[];
 extern const char kDisplaySubPage[];
-extern const char kDeprecatedOptionsSearchSubPage[];
 extern const char kHelpSubPage[];
 extern const char kInternetSubPage[];
 extern const char kNetworkDetailSubPage[];
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 02b76e5..07f4346 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -466,8 +466,6 @@
       "../browser/ui/startup/startup_browser_creator_interactive_uitest.cc",
       "../browser/ui/translate/translate_bubble_test_utils.h",
       "../browser/ui/views/accessibility/navigation_accessibility_uitest_win.cc",
-      "../browser/ui/webui/options/language_dictionary_interactive_uitest.cc",
-      "../browser/ui/webui/options/language_options_interactive_uitest.cc",
       "//ui/base/clipboard/clipboard_unittest.cc",
       "base/always_on_top_window_killer_win.cc",
       "base/always_on_top_window_killer_win.h",
@@ -1716,15 +1714,10 @@
       "../browser/ui/webui/net_internals/net_internals_ui_browsertest.cc",
       "../browser/ui/webui/net_internals/net_internals_ui_browsertest.h",
       "../browser/ui/webui/ntp/new_tab_ui_browsertest.cc",
-      "../browser/ui/webui/options/certificate_manager_browsertest.cc",
-      "../browser/ui/webui/options/clear_browser_data_browsertest.cc",
-      "../browser/ui/webui/options/content_settings_exception_area_browsertest.cc",
       "../browser/ui/webui/options/multilanguage_options_browsertest.cc",
       "../browser/ui/webui/options/multilanguage_options_browsertest.h",
       "../browser/ui/webui/options/options_browsertest.cc",
       "../browser/ui/webui/options/options_browsertest.h",
-      "../browser/ui/webui/options/options_ui_browsertest.cc",
-      "../browser/ui/webui/options/options_ui_browsertest.h",
       "../browser/ui/webui/options/preferences_browsertest.cc",
       "../browser/ui/webui/options/preferences_browsertest.h",
       "../browser/ui/webui/password_manager_internals/password_manager_internals_ui_browsertest.cc",
@@ -1737,7 +1730,6 @@
       "../browser/ui/webui/signin/inline_login_ui_browsertest.cc",
       "../browser/ui/webui/signin/user_manager_ui_browsertest.cc",
       "../browser/ui/webui/task_scheduler_internals/task_scheduler_internals_ui_browsertest.cc",
-      "../browser/ui/webui/uber/uber_ui_browsertest.cc",
       "../browser/ui/webui/webui_browsertest.cc",
       "../browser/ui/webui/webui_webview_browsertest.cc",
       "../browser/ui/zoom/zoom_controller_browsertest.cc",
@@ -2284,9 +2276,6 @@
         "../browser/ui/views/frame/browser_frame_ash_browsertest.cc",
         "../browser/ui/webui/chromeos/keyboard_overlay_ui_browsertest.cc",
         "../browser/ui/webui/options/chromeos/accounts_options_browsertest.cc",
-        "../browser/ui/webui/options/chromeos/guest_mode_options_browsertest.cc",
-        "../browser/ui/webui/options/chromeos/guest_mode_options_browsertest.h",
-        "../browser/ui/webui/options/chromeos/guest_mode_options_ui_browsertest.cc",
         "../browser/ui/webui/options/chromeos/shared_options_browsertest.cc",
       ]
       sources -= [
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn
index 46dcdd48..7e1fb87 100644
--- a/chrome/test/data/webui/BUILD.gn
+++ b/chrome/test/data/webui/BUILD.gn
@@ -48,7 +48,6 @@
     "../../../browser/ui/webui/options/chromeos/accounts_options_browsertest.js",
     "../../../browser/ui/webui/options/chromeos/bluetooth_options_browsertest.js",
     "../../../browser/ui/webui/options/chromeos/date_time_options_browsertest.js",
-    "../../../browser/ui/webui/options/chromeos/guest_mode_options_browsertest.js",
     "../../../browser/ui/webui/options/chromeos/power_overlay_browsertest.js",
     "../../../browser/ui/webui/options/content_options_browsertest.js",
     "../../../browser/ui/webui/options/content_settings_exception_area_browsertest.js",
diff --git a/chrome/test/data/webui/settings/help_page_browsertest.js b/chrome/test/data/webui/settings/help_page_browsertest.js
index 6e041090..ed1bcf7 100644
--- a/chrome/test/data/webui/settings/help_page_browsertest.js
+++ b/chrome/test/data/webui/settings/help_page_browsertest.js
@@ -5,7 +5,6 @@
 /** @fileoverview Material Help page tests. */
 
 GEN_INCLUDE(['settings_page_browsertest.js']);
-GEN('#include "base/command_line.h"');
 
 /**
  * @constructor
@@ -19,9 +18,6 @@
   /** @override */
   browsePreload: 'chrome://help/',
 
-  commandLineSwitches: [{switchName: 'enable-features',
-                         switchValue: 'MaterialDesignSettings'}],
-
   /** @override */
   extraLibraries: PolymerTest.getLibraries(ROOT_PATH),
 
diff --git a/components/dom_distiller/core/javascript/domdistiller.js b/components/dom_distiller/core/javascript/domdistiller.js
index 6e9a417a..f1e5c3b 100644
--- a/components/dom_distiller/core/javascript/domdistiller.js
+++ b/components/dom_distiller/core/javascript/domdistiller.js
@@ -33,5 +33,4 @@
     if (e.stack != undefined) window.console.error(e.stack);
   }
   return undefined;
-})(options = $$OPTIONS,
-   stringify_output = $$STRINGIFY)
+})($$OPTIONS, $$STRINGIFY)
diff --git a/components/subresource_filter/content/browser/content_subresource_filter_driver_factory.cc b/components/subresource_filter/content/browser/content_subresource_filter_driver_factory.cc
index cff5203..663aa6e 100644
--- a/components/subresource_filter/content/browser/content_subresource_filter_driver_factory.cc
+++ b/components/subresource_filter/content/browser/content_subresource_filter_driver_factory.cc
@@ -73,12 +73,10 @@
   activation_decision_ = activation_decision;
   activation_options_ = matched_options;
   DCHECK_NE(activation_decision_, ActivationDecision::UNKNOWN);
-  if (activation_decision_ != ActivationDecision::ACTIVATED) {
-    DCHECK_EQ(activation_options_.activation_level, ActivationLevel::DISABLED);
-    return;
-  }
 
-  DCHECK_NE(activation_options_.activation_level, ActivationLevel::DISABLED);
+  // ACTIVATION_DISABLED implies DISABLED activation level.
+  DCHECK(activation_decision_ != ActivationDecision::ACTIVATION_DISABLED ||
+         activation_options_.activation_level == ActivationLevel::DISABLED);
   ActivationState state = ActivationState(activation_options_.activation_level);
   state.measure_performance = ShouldMeasurePerformanceForPageLoad(
       activation_options_.performance_measurement_rate);
diff --git a/components/subresource_filter/content/browser/subresource_filter_observer.h b/components/subresource_filter/content/browser/subresource_filter_observer.h
index 8946095..0f73479 100644
--- a/components/subresource_filter/content/browser/subresource_filter_observer.h
+++ b/components/subresource_filter/content/browser/subresource_filter_observer.h
@@ -22,8 +22,12 @@
  public:
   virtual ~SubresourceFilterObserver() = default;
 
+  // Called before the observer manager is destroyed. Observers must unregister
+  // themselves by this point.
   virtual void OnSubresourceFilterGoingAway() {}
 
+  // Called at most once per navigation when page activation is computed. This
+  // will be called before ReadyToCommitNavigation.
   virtual void OnPageActivationComputed(
       content::NavigationHandle* navigation_handle,
       ActivationDecision activation_decision,
diff --git a/content/browser/bluetooth/bluetooth_device_chooser_controller.cc b/content/browser/bluetooth/bluetooth_device_chooser_controller.cc
index 5c35cc36..ed7de74 100644
--- a/content/browser/bluetooth/bluetooth_device_chooser_controller.cc
+++ b/content/browser/bluetooth/bluetooth_device_chooser_controller.cc
@@ -491,6 +491,7 @@
 }
 
 void BluetoothDeviceChooserController::PopulateConnectedDevices() {
+  // TODO(crbug.com/728897): Use RetrieveGattConnectedDevices once implemented.
   for (const device::BluetoothDevice* device : adapter_->GetDevices()) {
     if (device->IsGattConnected()) {
       AddFilteredDevice(*device);
diff --git a/content/browser/devtools/protocol/storage_handler.cc b/content/browser/devtools/protocol/storage_handler.cc
index 5e7f9a2..1d2ecba 100644
--- a/content/browser/devtools/protocol/storage_handler.cc
+++ b/content/browser/devtools/protocol/storage_handler.cc
@@ -12,8 +12,6 @@
 #include "content/browser/frame_host/render_frame_host_impl.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/storage_partition.h"
-#include "storage/browser/quota/quota_manager.h"
-#include "storage/common/quota/quota_status_code.h"
 
 namespace content {
 namespace protocol {
@@ -29,20 +27,6 @@
 static const char kServiceWorkers[] = "service_workers";
 static const char kCacheStorage[] = "cache_storage";
 static const char kAll[] = "all";
-
-void QuotaAndUsageDataCallback(
-    std::unique_ptr<StorageHandler::GetUsageAndQuotaCallback> callback,
-    storage::QuotaStatusCode code,
-    int64_t usage,
-    int64_t quota) {
-  if (code != storage::kQuotaStatusOk) {
-    callback->sendFailure(
-        Response::Error("Quota information is not available"));
-    return;
-  }
-  callback->sendSuccess(
-      Storage::QuotaAndUsage::Create().SetQuota(quota).SetUsage(usage).Build());
-}
 }
 
 StorageHandler::StorageHandler()
@@ -104,20 +88,5 @@
   return Response::OK();
 }
 
-void StorageHandler::GetUsageAndQuota(
-    const String& origin,
-    std::unique_ptr<GetUsageAndQuotaCallback> callback) {
-  if (!host_)
-    return callback->sendFailure(Response::InternalError());
-
-  storage::QuotaManager* manager =
-      host_->GetProcess()->GetStoragePartition()->GetQuotaManager();
-  GURL origin_url(origin);
-  manager->GetUsageAndQuotaForWebApps(
-      origin_url, storage::kStorageTypeTemporary,
-      base::Bind(&QuotaAndUsageDataCallback,
-                 base::Passed(std::move(callback))));
-}
-
 }  // namespace protocol
 }  // namespace content
diff --git a/content/browser/devtools/protocol/storage_handler.h b/content/browser/devtools/protocol/storage_handler.h
index b83337c..b5c9db6 100644
--- a/content/browser/devtools/protocol/storage_handler.h
+++ b/content/browser/devtools/protocol/storage_handler.h
@@ -27,9 +27,6 @@
   Response ClearDataForOrigin(
       const std::string& origin,
       const std::string& storage_types) override;
-  void GetUsageAndQuota(
-      const String& origin,
-      std::unique_ptr<GetUsageAndQuotaCallback> callback) override;
 
  private:
   RenderFrameHostImpl* host_;
diff --git a/content/browser/devtools/protocol_config.json b/content/browser/devtools/protocol_config.json
index 753a79d14..302f33b6 100644
--- a/content/browser/devtools/protocol_config.json
+++ b/content/browser/devtools/protocol_config.json
@@ -67,8 +67,7 @@
                 "domain": "ServiceWorker"
             },
             {
-                "domain": "Storage",
-                "async": ["getUsageAndQuota"]
+                "domain": "Storage"
             },
             {
                 "domain": "SystemInfo",
diff --git a/content/browser/indexed_db/cursor_impl.cc b/content/browser/indexed_db/cursor_impl.cc
index 15382eb..60c171a 100644
--- a/content/browser/indexed_db/cursor_impl.cc
+++ b/content/browser/indexed_db/cursor_impl.cc
@@ -96,9 +96,7 @@
     std::unique_ptr<IndexedDBCursor> cursor)
     : cursor_(std::move(cursor)) {}
 
-CursorImpl::IDBThreadHelper::~IDBThreadHelper() {
-  cursor_->RemoveCursorFromTransaction();
-}
+CursorImpl::IDBThreadHelper::~IDBThreadHelper() {}
 
 void CursorImpl::IDBThreadHelper::Advance(
     uint32_t count,
diff --git a/content/browser/indexed_db/indexed_db_cursor.cc b/content/browser/indexed_db/indexed_db_cursor.cc
index f9a130cc..c84547fb 100644
--- a/content/browser/indexed_db/indexed_db_cursor.cc
+++ b/content/browser/indexed_db/indexed_db_cursor.cc
@@ -70,6 +70,8 @@
 }
 
 IndexedDBCursor::~IndexedDBCursor() {
+  if (transaction_)
+    transaction_->UnregisterOpenCursor(this);
   // Call to make sure we complete our lifetime trace.
   Close();
 }
@@ -106,11 +108,6 @@
                         ptr_factory_.GetWeakPtr(), count, callbacks));
 }
 
-void IndexedDBCursor::RemoveCursorFromTransaction() {
-  if (transaction_)
-    transaction_->UnregisterOpenCursor(this);
-}
-
 leveldb::Status IndexedDBCursor::CursorAdvanceOperation(
     uint32_t count,
     scoped_refptr<IndexedDBCallbacks> callbacks,
diff --git a/content/browser/indexed_db/indexed_db_cursor.h b/content/browser/indexed_db/indexed_db_cursor.h
index 591f9f89..1d10e90 100644
--- a/content/browser/indexed_db/indexed_db_cursor.h
+++ b/content/browser/indexed_db/indexed_db_cursor.h
@@ -44,7 +44,6 @@
                                                          : cursor_->value();
   }
 
-  void RemoveCursorFromTransaction();
   void Close();
 
   leveldb::Status CursorIterationOperation(
diff --git a/content/browser/media/media_internals.cc b/content/browser/media/media_internals.cc
index a455f44..def2b61 100644
--- a/content/browser/media/media_internals.cc
+++ b/content/browser/media/media_internals.cc
@@ -13,6 +13,7 @@
 #include "base/containers/flat_map.h"
 #include "base/containers/flat_set.h"
 #include "base/macros.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/string16.h"
 #include "base/strings/string_number_conversions.h"
@@ -351,15 +352,13 @@
   void RecordWatchTime(base::StringPiece key,
                        base::TimeDelta value,
                        bool is_mtbr = false) {
-    base::Histogram::FactoryTimeGet(
-        key.as_string(),
+    base::UmaHistogramCustomTimes(
+        key.as_string(), value,
         // There are a maximum of 5 underflow events possible in a given 7s
         // watch time period, so the minimum value is 1.4s.
         is_mtbr ? base::TimeDelta::FromSecondsD(1.4)
                 : base::TimeDelta::FromSeconds(7),
-        base::TimeDelta::FromHours(10), 50,
-        base::HistogramBase::kUmaTargetedHistogramFlag)
-        ->AddTime(value);
+        base::TimeDelta::FromHours(10), 50);
   }
 
   void RecordMeanTimeBetweenRebuffers(base::StringPiece key,
@@ -367,6 +366,10 @@
     RecordWatchTime(key, value, true);
   }
 
+  void RecordRebuffersCount(base::StringPiece key, int underflow_count) {
+    base::UmaHistogramCounts100(key.as_string(), underflow_count);
+  }
+
   void RecordWatchTimeWithFilter(
       const GURL& url,
       WatchTimeInfo* watch_time_info,
@@ -413,19 +416,19 @@
 
     switch (finalize_type) {
       case FinalizeType::EVERYTHING:
-        if (*underflow_count) {
-          // Check for watch times entries that have corresponding MTBR entries
-          // and report the MTBR value using watch_time / |underflow_count|
-          for (auto& kv : mtbr_keys_) {
-            auto it = watch_time_info->find(kv.first);
-            if (it == watch_time_info->end())
-              continue;
-            RecordMeanTimeBetweenRebuffers(kv.second,
+        // Check for watch times entries that have corresponding MTBR entries
+        // and report the MTBR value using watch_time / |underflow_count|
+        for (auto& mapping : rebuffer_keys_) {
+          auto it = watch_time_info->find(mapping.watch_time_key);
+          if (it == watch_time_info->end())
+            continue;
+          if (*underflow_count) {
+            RecordMeanTimeBetweenRebuffers(mapping.mtbr_key,
                                            it->second / *underflow_count);
           }
-
-          *underflow_count = 0;
+          RecordRebuffersCount(mapping.smooth_rate_key, *underflow_count);
         }
+        *underflow_count = 0;
 
         RecordWatchTimeWithFilter(url, watch_time_info,
                                   base::flat_set<base::StringPiece>());
@@ -458,8 +461,14 @@
   // Set of only the controls related watch time keys.
   const base::flat_set<base::StringPiece> watch_time_controls_keys_;
 
-  // Mapping of WatchTime metric keys to MeanTimeBetweenRebuffers (MTBR) keys.
-  const base::flat_map<base::StringPiece, base::StringPiece> mtbr_keys_;
+  // Mapping of WatchTime metric keys to MeanTimeBetweenRebuffers (MTBR) and
+  // smooth rate (had zero rebuffers) keys.
+  struct RebufferMapping {
+    base::StringPiece watch_time_key;
+    base::StringPiece mtbr_key;
+    base::StringPiece smooth_rate_key;
+  };
+  const std::vector<RebufferMapping> rebuffer_keys_;
 
   content::MediaInternals* const media_internals_;
 
@@ -471,19 +480,22 @@
     : watch_time_keys_(media::GetWatchTimeKeys()),
       watch_time_power_keys_(media::GetWatchTimePowerKeys()),
       watch_time_controls_keys_(media::GetWatchTimeControlsKeys()),
-      mtbr_keys_({{media::kWatchTimeAudioSrc,
-                   media::kMeanTimeBetweenRebuffersAudioSrc},
-                  {media::kWatchTimeAudioMse,
-                   media::kMeanTimeBetweenRebuffersAudioMse},
-                  {media::kWatchTimeAudioEme,
-                   media::kMeanTimeBetweenRebuffersAudioEme},
-                  {media::kWatchTimeAudioVideoSrc,
-                   media::kMeanTimeBetweenRebuffersAudioVideoSrc},
-                  {media::kWatchTimeAudioVideoMse,
-                   media::kMeanTimeBetweenRebuffersAudioVideoMse},
-                  {media::kWatchTimeAudioVideoEme,
-                   media::kMeanTimeBetweenRebuffersAudioVideoEme}},
-                 base::KEEP_FIRST_OF_DUPES),
+      rebuffer_keys_(
+          {{media::kWatchTimeAudioSrc, media::kMeanTimeBetweenRebuffersAudioSrc,
+            media::kRebuffersCountAudioSrc},
+           {media::kWatchTimeAudioMse, media::kMeanTimeBetweenRebuffersAudioMse,
+            media::kRebuffersCountAudioMse},
+           {media::kWatchTimeAudioEme, media::kMeanTimeBetweenRebuffersAudioEme,
+            media::kRebuffersCountAudioEme},
+           {media::kWatchTimeAudioVideoSrc,
+            media::kMeanTimeBetweenRebuffersAudioVideoSrc,
+            media::kRebuffersCountAudioVideoSrc},
+           {media::kWatchTimeAudioVideoMse,
+            media::kMeanTimeBetweenRebuffersAudioVideoMse,
+            media::kRebuffersCountAudioVideoMse},
+           {media::kWatchTimeAudioVideoEme,
+            media::kMeanTimeBetweenRebuffersAudioVideoEme,
+            media::kRebuffersCountAudioVideoEme}}),
       media_internals_(media_internals) {}
 
 void MediaInternals::MediaInternalsUMAHandler::SavePlayerState(
@@ -685,11 +697,9 @@
     return;
 
   if (player_info.has_video && player_info.has_audio) {
-    base::LinearHistogram::FactoryGet(
-        GetUMANameForAVStream(player_info), 1, media::PIPELINE_STATUS_MAX,
-        media::PIPELINE_STATUS_MAX + 1,
-        base::HistogramBase::kUmaTargetedHistogramFlag)
-        ->Add(player_info.last_pipeline_status);
+    base::UmaHistogramExactLinear(GetUMANameForAVStream(player_info),
+                                  player_info.last_pipeline_status,
+                                  media::PIPELINE_STATUS_MAX);
   } else if (player_info.has_audio) {
     UMA_HISTOGRAM_ENUMERATION("Media.PipelineStatus.AudioOnly",
                               player_info.last_pipeline_status,
diff --git a/content/browser/media/media_internals_unittest.cc b/content/browser/media/media_internals_unittest.cc
index 3c66598..940da64 100644
--- a/content/browser/media/media_internals_unittest.cc
+++ b/content/browser/media/media_internals_unittest.cc
@@ -346,7 +346,19 @@
         histogram_tester_(new base::HistogramTester()),
         test_recorder_(new ukm::TestUkmRecorder()),
         watch_time_keys_(media::GetWatchTimeKeys()),
-        watch_time_power_keys_(media::GetWatchTimePowerKeys()) {
+        watch_time_power_keys_(media::GetWatchTimePowerKeys()),
+        mtbr_keys_({media::kMeanTimeBetweenRebuffersAudioSrc,
+                    media::kMeanTimeBetweenRebuffersAudioMse,
+                    media::kMeanTimeBetweenRebuffersAudioEme,
+                    media::kMeanTimeBetweenRebuffersAudioVideoSrc,
+                    media::kMeanTimeBetweenRebuffersAudioVideoMse,
+                    media::kMeanTimeBetweenRebuffersAudioVideoEme}),
+        smooth_keys_({media::kRebuffersCountAudioSrc,
+                      media::kRebuffersCountAudioMse,
+                      media::kRebuffersCountAudioEme,
+                      media::kRebuffersCountAudioVideoSrc,
+                      media::kRebuffersCountAudioVideoMse,
+                      media::kRebuffersCountAudioVideoEme}) {
     media_log_->AddEvent(media_log_->CreateCreatedEvent(kTestOrigin));
   }
 
@@ -382,12 +394,29 @@
     }
   }
 
+  void ExpectHelper(const std::vector<base::StringPiece>& full_key_list,
+                    const std::vector<base::StringPiece>& keys,
+                    int64_t value) {
+    for (auto key : full_key_list) {
+      auto it = std::find(keys.begin(), keys.end(), key);
+      if (it == keys.end())
+        histogram_tester_->ExpectTotalCount(key.as_string(), 0);
+      else
+        histogram_tester_->ExpectUniqueSample(key.as_string(), value, 1);
+    }
+  }
+
   void ExpectMtbrTime(const std::vector<base::StringPiece>& keys,
                       base::TimeDelta value) {
-    for (auto key : keys) {
-      histogram_tester_->ExpectUniqueSample(key.as_string(),
-                                            value.InMilliseconds(), 1);
-    }
+    ExpectHelper(mtbr_keys_, keys, value.InMilliseconds());
+  }
+
+  void ExpectZeroRebuffers(const std::vector<base::StringPiece>& keys) {
+    ExpectHelper(smooth_keys_, keys, 0);
+  }
+
+  void ExpectRebuffers(const std::vector<base::StringPiece>& keys, int count) {
+    ExpectHelper(smooth_keys_, keys, count);
   }
 
   void ExpectUkmWatchTime(size_t entry, size_t size, base::TimeDelta value) {
@@ -415,6 +444,8 @@
   std::unique_ptr<media::WatchTimeReporter> wtr_;
   const base::flat_set<base::StringPiece> watch_time_keys_;
   const base::flat_set<base::StringPiece> watch_time_power_keys_;
+  const std::vector<base::StringPiece> mtbr_keys_;
+  const std::vector<base::StringPiece> smooth_keys_;
 
   DISALLOW_COPY_AND_ASSIGN(MediaInternalsWatchTimeTest);
 };
@@ -447,6 +478,8 @@
   ExpectMtbrTime({media::kMeanTimeBetweenRebuffersAudioMse,
                   media::kMeanTimeBetweenRebuffersAudioEme},
                  kWatchTimeLate / 2);
+  ExpectRebuffers(
+      {media::kRebuffersCountAudioMse, media::kRebuffersCountAudioEme}, 2);
 
   ASSERT_EQ(1U, test_recorder_->sources_count());
   ExpectUkmWatchTime(0, 5, kWatchTimeLate);
@@ -482,6 +515,9 @@
   ExpectMtbrTime({media::kMeanTimeBetweenRebuffersAudioVideoSrc,
                   media::kMeanTimeBetweenRebuffersAudioVideoEme},
                  kWatchTimeLate / 2);
+  ExpectRebuffers({media::kRebuffersCountAudioVideoSrc,
+                   media::kRebuffersCountAudioVideoEme},
+                  2);
 
   ASSERT_EQ(1U, test_recorder_->sources_count());
   ExpectUkmWatchTime(0, 5, kWatchTimeLate);
@@ -546,6 +582,8 @@
   ASSERT_EQ(2U, test_recorder_->sources_count());
   ASSERT_EQ(2U, test_recorder_->entries_count());
   ExpectUkmWatchTime(0, 1, kWatchTime2);
+  ExpectZeroRebuffers({media::kRebuffersCountAudioVideoSrc,
+                       media::kRebuffersCountAudioVideoEme});
 
   // Verify Media.WatchTime keys are properly stripped for UKM reporting.
   EXPECT_TRUE(test_recorder_->FindMetric(test_recorder_->GetEntry(0),
@@ -693,6 +731,21 @@
   EXPECT_TRUE(test_recorder_->GetSourceForUrl(kTestOrigin));
 }
 
+TEST_F(MediaInternalsWatchTimeTest, FinalizeWithoutWatchTime) {
+  EXPECT_CALL(*this, GetCurrentMediaTime())
+      .WillRepeatedly(testing::Return(base::TimeDelta()));
+  Initialize(true, true, false, true);
+  wtr_->OnPlaying();
+  wtr_.reset();
+
+  // No watch time should have been recorded even though a finalize event will
+  // be sent.
+  ExpectWatchTime(std::vector<base::StringPiece>(), base::TimeDelta());
+  ExpectMtbrTime(std::vector<base::StringPiece>(), base::TimeDelta());
+  ExpectZeroRebuffers(std::vector<base::StringPiece>());
+  ASSERT_EQ(0U, test_recorder_->sources_count());
+}
+
 TEST_F(MediaInternalsWatchTimeTest, PlayerDestructionFinalizes) {
   constexpr base::TimeDelta kWatchTimeEarly = base::TimeDelta::FromSeconds(5);
   constexpr base::TimeDelta kWatchTimeLate = base::TimeDelta::FromSeconds(10);
@@ -700,7 +753,7 @@
       .WillOnce(testing::Return(base::TimeDelta()))
       .WillOnce(testing::Return(kWatchTimeEarly))
       .WillRepeatedly(testing::Return(kWatchTimeLate));
-  Initialize(true, true, false, true);
+  Initialize(true, true, true, true);
   wtr_->OnPlaying();
 
   // No log should have been generated yet since the message loop has not had
@@ -714,11 +767,13 @@
       media_log_->CreateEvent(media::MediaLogEvent::WEBMEDIAPLAYER_DESTROYED));
 
   ExpectWatchTime(
-      {media::kWatchTimeAudioVideoAll, media::kWatchTimeAudioVideoSrc,
+      {media::kWatchTimeAudioVideoAll, media::kWatchTimeAudioVideoMse,
        media::kWatchTimeAudioVideoEme, media::kWatchTimeAudioVideoAc,
        media::kWatchTimeAudioVideoNativeControlsOff,
        media::kWatchTimeAudioVideoEmbeddedExperience},
       kWatchTimeLate);
+  ExpectZeroRebuffers({media::kRebuffersCountAudioVideoMse,
+                       media::kRebuffersCountAudioVideoEme});
 
   ASSERT_EQ(1U, test_recorder_->sources_count());
   ExpectUkmWatchTime(0, 5, kWatchTimeLate);
@@ -751,6 +806,8 @@
        media::kWatchTimeAudioVideoNativeControlsOff,
        media::kWatchTimeAudioVideoEmbeddedExperience},
       kWatchTimeLate);
+  ExpectZeroRebuffers({media::kRebuffersCountAudioVideoSrc,
+                       media::kRebuffersCountAudioVideoEme});
 }
 
 }  // namespace content
diff --git a/content/browser/service_worker/link_header_support_unittest.cc b/content/browser/service_worker/link_header_support_unittest.cc
index d589fea..e77c68d9 100644
--- a/content/browser/service_worker/link_header_support_unittest.cc
+++ b/content/browser/service_worker/link_header_support_unittest.cc
@@ -128,7 +128,8 @@
   }
 
   std::unique_ptr<net::URLRequest> CreateRequest(const GURL& request_url,
-                                                 ResourceType resource_type) {
+                                                 ResourceType resource_type,
+                                                 int provider_id) {
     std::unique_ptr<net::URLRequest> request = request_context_.CreateRequest(
         request_url, net::DEFAULT_PRIORITY, &request_delegate_,
         TRAFFIC_ANNOTATION_FOR_TESTS);
@@ -143,7 +144,7 @@
 
     ServiceWorkerRequestHandler::InitializeHandler(
         request.get(), context_wrapper(), &blob_storage_context_,
-        render_process_id(), kMockProviderId, false /* skip_service_worker */,
+        render_process_id(), provider_id, false /* skip_service_worker */,
         FETCH_REQUEST_MODE_NO_CORS, FETCH_CREDENTIALS_MODE_OMIT,
         FetchRedirectMode::FOLLOW_MODE, resource_type,
         REQUEST_CONTEXT_TYPE_HYPERLINK, REQUEST_CONTEXT_FRAME_TYPE_TOP_LEVEL,
@@ -153,8 +154,9 @@
   }
 
   std::unique_ptr<net::URLRequest> CreateSubresourceRequest(
-      const GURL& request_url) {
-    return CreateRequest(request_url, RESOURCE_TYPE_SCRIPT);
+      const GURL& request_url,
+      int provider_id) {
+    return CreateRequest(request_url, RESOURCE_TYPE_SCRIPT, provider_id);
   }
 
   std::vector<ServiceWorkerRegistrationInfo> GetRegistrations() {
@@ -181,7 +183,9 @@
 TEST_F(LinkHeaderServiceWorkerTest, InstallServiceWorker_Basic) {
   CreateDocumentProviderHost();
   ProcessLinkHeaderForRequest(
-      CreateSubresourceRequest(GURL("https://example.com/foo/bar/")).get(),
+      CreateSubresourceRequest(GURL("https://example.com/foo/bar/"),
+                               provider_host()->provider_id())
+          .get(),
       "<../foo.js>; rel=serviceworker", context_wrapper());
   base::RunLoop().RunUntilIdle();
 
@@ -195,7 +199,9 @@
 TEST_F(LinkHeaderServiceWorkerTest, InstallServiceWorker_ScopeWithFragment) {
   CreateDocumentProviderHost();
   ProcessLinkHeaderForRequest(
-      CreateSubresourceRequest(GURL("https://example.com/foo/bar/")).get(),
+      CreateSubresourceRequest(GURL("https://example.com/foo/bar/"),
+                               provider_host()->provider_id())
+          .get(),
       "<../bar.js>; rel=serviceworker; scope=\"scope#ref\"", context_wrapper());
   base::RunLoop().RunUntilIdle();
 
@@ -210,7 +216,9 @@
 TEST_F(LinkHeaderServiceWorkerTest, InstallServiceWorker_ScopeAbsoluteUrl) {
   CreateDocumentProviderHost();
   ProcessLinkHeaderForRequest(
-      CreateSubresourceRequest(GURL("https://example.com/foo/bar/")).get(),
+      CreateSubresourceRequest(GURL("https://example.com/foo/bar/"),
+                               provider_host()->provider_id())
+          .get(),
       "<bar.js>; rel=serviceworker; "
       "scope=\"https://example.com:443/foo/bar/scope\"",
       context_wrapper());
@@ -227,7 +235,9 @@
 TEST_F(LinkHeaderServiceWorkerTest, InstallServiceWorker_ScopeDifferentOrigin) {
   CreateDocumentProviderHost();
   ProcessLinkHeaderForRequest(
-      CreateSubresourceRequest(GURL("https://example.com/foobar/")).get(),
+      CreateSubresourceRequest(GURL("https://example.com/foobar/"),
+                               provider_host()->provider_id())
+          .get(),
       "<bar.js>; rel=serviceworker; scope=\"https://google.com/scope\"",
       context_wrapper());
   base::RunLoop().RunUntilIdle();
@@ -239,7 +249,9 @@
 TEST_F(LinkHeaderServiceWorkerTest, InstallServiceWorker_ScopeUrlEncodedSlash) {
   CreateDocumentProviderHost();
   ProcessLinkHeaderForRequest(
-      CreateSubresourceRequest(GURL("https://example.com/foobar/")).get(),
+      CreateSubresourceRequest(GURL("https://example.com/foobar/"),
+                               provider_host()->provider_id())
+          .get(),
       "<bar.js>; rel=serviceworker; scope=\"./foo%2Fbar\"", context_wrapper());
   base::RunLoop().RunUntilIdle();
 
@@ -251,7 +263,9 @@
        InstallServiceWorker_ScriptUrlEncodedSlash) {
   CreateDocumentProviderHost();
   ProcessLinkHeaderForRequest(
-      CreateSubresourceRequest(GURL("https://example.com/foobar/")).get(),
+      CreateSubresourceRequest(GURL("https://example.com/foobar/"),
+                               provider_host()->provider_id())
+          .get(),
       "<foo%2Fbar.js>; rel=serviceworker", context_wrapper());
   base::RunLoop().RunUntilIdle();
 
@@ -262,7 +276,9 @@
 TEST_F(LinkHeaderServiceWorkerTest, InstallServiceWorker_ScriptAbsoluteUrl) {
   CreateDocumentProviderHost();
   ProcessLinkHeaderForRequest(
-      CreateSubresourceRequest(GURL("https://example.com/foobar/")).get(),
+      CreateSubresourceRequest(GURL("https://example.com/foobar/"),
+                               provider_host()->provider_id())
+          .get(),
       "<https://example.com/bar.js>; rel=serviceworker; scope=foo",
       context_wrapper());
   base::RunLoop().RunUntilIdle();
@@ -278,7 +294,9 @@
        InstallServiceWorker_ScriptDifferentOrigin) {
   CreateDocumentProviderHost();
   ProcessLinkHeaderForRequest(
-      CreateSubresourceRequest(GURL("https://example.com/foobar/")).get(),
+      CreateSubresourceRequest(GURL("https://example.com/foobar/"),
+                               provider_host()->provider_id())
+          .get(),
       "<https://google.com/bar.js>; rel=serviceworker; scope=foo",
       context_wrapper());
   base::RunLoop().RunUntilIdle();
@@ -290,7 +308,9 @@
 TEST_F(LinkHeaderServiceWorkerTest, InstallServiceWorker_MultipleWorkers) {
   CreateDocumentProviderHost();
   ProcessLinkHeaderForRequest(
-      CreateSubresourceRequest(GURL("https://example.com/foobar/")).get(),
+      CreateSubresourceRequest(GURL("https://example.com/foobar/"),
+                               provider_host()->provider_id())
+          .get(),
       "<bar.js>; rel=serviceworker; scope=foo, <baz.js>; "
       "rel=serviceworker; scope=scope",
       context_wrapper());
@@ -310,7 +330,9 @@
        InstallServiceWorker_ValidAndInvalidValues) {
   CreateDocumentProviderHost();
   ProcessLinkHeaderForRequest(
-      CreateSubresourceRequest(GURL("https://example.com/foobar/")).get(),
+      CreateSubresourceRequest(GURL("https://example.com/foobar/"),
+                               provider_host()->provider_id())
+          .get(),
       "<https://google.com/bar.js>; rel=serviceworker; scope=foo, <baz.js>; "
       "rel=serviceworker; scope=scope",
       context_wrapper());
@@ -325,8 +347,8 @@
 
 TEST_F(LinkHeaderServiceWorkerTest, InstallServiceWorker_InsecureContext) {
   CreateDocumentProviderHost();
-  std::unique_ptr<net::URLRequest> request =
-      CreateSubresourceRequest(GURL("https://example.com/foo/bar/"));
+  std::unique_ptr<net::URLRequest> request = CreateSubresourceRequest(
+      GURL("https://example.com/foo/bar/"), provider_host()->provider_id());
   ResourceRequestInfoImpl::ForRequest(request.get())
       ->set_initiated_in_secure_context_for_testing(false);
   ProcessLinkHeaderForRequest(request.get(), "<../foo.js>; rel=serviceworker",
@@ -340,8 +362,9 @@
 TEST_F(LinkHeaderServiceWorkerTest,
        InstallServiceWorker_NavigationFromInsecureContextToSecureContext) {
   CreateDocumentProviderHost();
-  std::unique_ptr<net::URLRequest> request = CreateRequest(
-      GURL("https://example.com/foo/bar/"), RESOURCE_TYPE_MAIN_FRAME);
+  std::unique_ptr<net::URLRequest> request =
+      CreateRequest(GURL("https://example.com/foo/bar/"),
+                    RESOURCE_TYPE_MAIN_FRAME, provider_host()->provider_id());
   ResourceRequestInfoImpl::ForRequest(request.get())
       ->set_initiated_in_secure_context_for_testing(false);
 
@@ -362,11 +385,11 @@
        InstallServiceWorker_NavigationToInsecureContext) {
   CreateDocumentProviderHost();
   provider_host()->SetDocumentUrl(GURL("http://example.com/foo/bar/"));
-  ProcessLinkHeaderForRequest(CreateRequest(GURL("http://example.com/foo/bar/"),
-                                            RESOURCE_TYPE_MAIN_FRAME)
-                                  .get(),
-                              "<../foo.js>; rel=serviceworker",
-                              context_wrapper());
+  ProcessLinkHeaderForRequest(
+      CreateRequest(GURL("http://example.com/foo/bar/"),
+                    RESOURCE_TYPE_MAIN_FRAME, provider_host()->provider_id())
+          .get(),
+      "<../foo.js>; rel=serviceworker", context_wrapper());
   base::RunLoop().RunUntilIdle();
 
   std::vector<ServiceWorkerRegistrationInfo> registrations = GetRegistrations();
@@ -379,7 +402,7 @@
   provider_host()->SetDocumentUrl(GURL("https://example.com/foo/bar/"));
   ProcessLinkHeaderForRequest(
       CreateRequest(GURL("https://example.com/foo/bar/"),
-                    RESOURCE_TYPE_MAIN_FRAME)
+                    RESOURCE_TYPE_MAIN_FRAME, provider_host()->provider_id())
           .get(),
       "<../foo.js>; rel=serviceworker", context_wrapper());
   base::RunLoop().RunUntilIdle();
@@ -392,7 +415,9 @@
        InstallServiceWorker_FromWorkerWithoutControllees) {
   CreateServiceWorkerProviderHost();
   ProcessLinkHeaderForRequest(
-      CreateSubresourceRequest(GURL("https://example.com/foo/bar/")).get(),
+      CreateSubresourceRequest(GURL("https://example.com/foo/bar/"),
+                               provider_host()->provider_id())
+          .get(),
       "<../foo.js>; rel=serviceworker", context_wrapper());
   base::RunLoop().RunUntilIdle();
 
@@ -413,7 +438,9 @@
   provider_host()->running_hosted_version()->AddControllee(controllee.get());
 
   ProcessLinkHeaderForRequest(
-      CreateSubresourceRequest(GURL("https://example.com/foo/bar/")).get(),
+      CreateSubresourceRequest(GURL("https://example.com/foo/bar/"),
+                               provider_host()->provider_id())
+          .get(),
       "<../foo.js>; rel=serviceworker", context_wrapper());
   base::RunLoop().RunUntilIdle();
 
diff --git a/content/browser/service_worker/service_worker_process_manager.cc b/content/browser/service_worker/service_worker_process_manager.cc
index b7fbe590..cedb5d9 100644
--- a/content/browser/service_worker/service_worker_process_manager.cc
+++ b/content/browser/service_worker/service_worker_process_manager.cc
@@ -216,14 +216,18 @@
     }
   }
 
-  // No existing processes available; start a new one.
+  // ServiceWorkerProcessManager does not know of any renderer processes that
+  // are available for |pattern|. Create a SiteInstance and ask for a renderer
+  // process. Attempt to reuse an existing process if possible.
   // TODO(clamy): Update the process reuse mechanism above following the
   // implementation of
   // SiteInstanceImpl::ProcessReusePolicy::REUSE_PENDING_OR_COMMITTED_SITE.
   scoped_refptr<SiteInstanceImpl> site_instance =
       SiteInstanceImpl::CreateForURL(browser_context_, script_url);
-  site_instance->set_process_reuse_policy(
-      SiteInstanceImpl::ProcessReusePolicy::REUSE_PENDING_OR_COMMITTED_SITE);
+  if (can_use_existing_process) {
+    site_instance->set_process_reuse_policy(
+        SiteInstanceImpl::ProcessReusePolicy::REUSE_PENDING_OR_COMMITTED_SITE);
+  }
   RenderProcessHost* rph = site_instance->GetProcess();
 
   // This Init() call posts a task to the IO thread that adds the RPH's
diff --git a/content/browser/service_worker/service_worker_process_manager.h b/content/browser/service_worker/service_worker_process_manager.h
index da8443f..f9dc727 100644
--- a/content/browser/service_worker/service_worker_process_manager.h
+++ b/content/browser/service_worker/service_worker_process_manager.h
@@ -92,6 +92,10 @@
   FRIEND_TEST_ALL_PREFIXES(ServiceWorkerProcessManagerTest,
                            AllocateWorkerProcess_FindAvailableProcess);
   FRIEND_TEST_ALL_PREFIXES(ServiceWorkerProcessManagerTest,
+                           AllocateWorkerProcess_WithProcessReuse);
+  FRIEND_TEST_ALL_PREFIXES(ServiceWorkerProcessManagerTest,
+                           AllocateWorkerProcess_WithoutProcessReuse);
+  FRIEND_TEST_ALL_PREFIXES(ServiceWorkerProcessManagerTest,
                            AllocateWorkerProcess_InShutdown);
 
   // Information about the process for an EmbeddedWorkerInstance.
diff --git a/content/browser/service_worker/service_worker_process_manager_unittest.cc b/content/browser/service_worker/service_worker_process_manager_unittest.cc
index ee6ea10..8e276f62 100644
--- a/content/browser/service_worker/service_worker_process_manager_unittest.cc
+++ b/content/browser/service_worker/service_worker_process_manager_unittest.cc
@@ -8,6 +8,7 @@
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/run_loop.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
 #include "content/browser/service_worker/service_worker_test_utils.h"
 #include "content/common/service_worker/embedded_worker_settings.h"
 #include "content/public/common/child_process_host.h"
@@ -48,11 +49,16 @@
         new ServiceWorkerProcessManager(browser_context_.get()));
     pattern_ = GURL("http://www.example.com/");
     script_url_ = GURL("http://www.example.com/sw.js");
+    render_process_host_factory_.reset(new MockRenderProcessHostFactory());
+    RenderProcessHostImpl::set_render_process_host_factory(
+        render_process_host_factory_.get());
   }
 
   void TearDown() override {
     process_manager_->Shutdown();
     process_manager_.reset();
+    RenderProcessHostImpl::set_render_process_host_factory(nullptr);
+    render_process_host_factory_.reset();
   }
 
   std::unique_ptr<MockRenderProcessHost> CreateRenderProcessHost() {
@@ -66,6 +72,7 @@
   GURL script_url_;
 
  private:
+  std::unique_ptr<MockRenderProcessHostFactory> render_process_host_factory_;
   content::TestBrowserThreadBundle thread_bundle_;
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerProcessManagerTest);
 };
@@ -233,6 +240,97 @@
   EXPECT_TRUE(instance_info.empty());
 }
 
+TEST_F(ServiceWorkerProcessManagerTest,
+       AllocateWorkerProcess_WithProcessReuse) {
+  const int kEmbeddedWorkerId = 100;
+  const GURL kSiteUrl = GURL("http://example.com");
+
+  // Create a process that is hosting a frame with URL |patter_|.
+  std::unique_ptr<MockRenderProcessHost> host(CreateRenderProcessHost());
+  RenderProcessHostImpl::AddFrameWithSite(browser_context_.get(), host.get(),
+                                          kSiteUrl);
+
+  std::map<int, ServiceWorkerProcessManager::ProcessInfo>& instance_info =
+      process_manager_->instance_info_;
+  EXPECT_TRUE(instance_info.empty());
+
+  // Allocate a process to a worker, when process reuse is authorized.
+  base::RunLoop run_loop;
+  ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_MAX_VALUE;
+  int process_id = -10;
+  bool is_new_process = false;
+  process_manager_->AllocateWorkerProcess(
+      kEmbeddedWorkerId, pattern_, script_url_,
+      true /* can_use_existing_process */,
+      base::Bind(&DidAllocateWorkerProcess, run_loop.QuitClosure(), &status,
+                 &process_id, &is_new_process));
+  run_loop.Run();
+
+  // An existing process should be allocated to the worker.
+  EXPECT_EQ(SERVICE_WORKER_OK, status);
+  EXPECT_EQ(host->GetID(), process_id);
+  EXPECT_TRUE(is_new_process);
+  EXPECT_EQ(1u, host->GetWorkerRefCount());
+  EXPECT_EQ(1u, instance_info.size());
+  std::map<int, ServiceWorkerProcessManager::ProcessInfo>::iterator found =
+      instance_info.find(kEmbeddedWorkerId);
+  ASSERT_TRUE(found != instance_info.end());
+  EXPECT_EQ(host->GetID(), found->second.process_id);
+
+  // Release the process.
+  process_manager_->ReleaseWorkerProcess(kEmbeddedWorkerId);
+  EXPECT_EQ(0u, host->GetWorkerRefCount());
+  EXPECT_TRUE(instance_info.empty());
+
+  RenderProcessHostImpl::RemoveFrameWithSite(browser_context_.get(), host.get(),
+                                             kSiteUrl);
+}
+
+TEST_F(ServiceWorkerProcessManagerTest,
+       AllocateWorkerProcess_WithoutProcessReuse) {
+  const int kEmbeddedWorkerId = 100;
+  const GURL kSiteUrl = GURL("http://example.com");
+
+  // Create a process that is hosting a frame with URL |patter_|.
+  std::unique_ptr<MockRenderProcessHost> host(CreateRenderProcessHost());
+  RenderProcessHostImpl::AddFrameWithSite(browser_context_.get(), host.get(),
+                                          kSiteUrl);
+
+  std::map<int, ServiceWorkerProcessManager::ProcessInfo>& instance_info =
+      process_manager_->instance_info_;
+  EXPECT_TRUE(instance_info.empty());
+
+  // Allocate a process to a worker, when process reuse is authorized.
+  base::RunLoop run_loop;
+  ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_MAX_VALUE;
+  int process_id = -10;
+  bool is_new_process = false;
+  process_manager_->AllocateWorkerProcess(
+      kEmbeddedWorkerId, pattern_, script_url_,
+      false /* can_use_existing_process */,
+      base::Bind(&DidAllocateWorkerProcess, run_loop.QuitClosure(), &status,
+                 &process_id, &is_new_process));
+  run_loop.Run();
+
+  // An new process should be allocated to the worker.
+  EXPECT_EQ(SERVICE_WORKER_OK, status);
+  EXPECT_NE(host->GetID(), process_id);
+  EXPECT_TRUE(is_new_process);
+  EXPECT_EQ(0u, host->GetWorkerRefCount());
+  EXPECT_EQ(1u, instance_info.size());
+  std::map<int, ServiceWorkerProcessManager::ProcessInfo>::iterator found =
+      instance_info.find(kEmbeddedWorkerId);
+  ASSERT_TRUE(found != instance_info.end());
+  EXPECT_NE(host->GetID(), found->second.process_id);
+
+  // Release the process.
+  process_manager_->ReleaseWorkerProcess(kEmbeddedWorkerId);
+  EXPECT_TRUE(instance_info.empty());
+
+  RenderProcessHostImpl::RemoveFrameWithSite(browser_context_.get(), host.get(),
+                                             kSiteUrl);
+}
+
 TEST_F(ServiceWorkerProcessManagerTest, AllocateWorkerProcess_InShutdown) {
   process_manager_->Shutdown();
   ASSERT_TRUE(process_manager_->IsShutdown());
diff --git a/content/public/android/BUILD.gn b/content/public/android/BUILD.gn
index 8c73adf..4920ccd 100644
--- a/content/public/android/BUILD.gn
+++ b/content/public/android/BUILD.gn
@@ -116,7 +116,6 @@
     "java/src/org/chromium/content/browser/ChildConnectionAllocator.java",
     "java/src/org/chromium/content/browser/ChildProcessConnection.java",
     "java/src/org/chromium/content/browser/ChildProcessConstants.java",
-    "java/src/org/chromium/content/browser/ChildProcessLauncher.java",
     "java/src/org/chromium/content/browser/ChildProcessLauncherHelper.java",
     "java/src/org/chromium/content/browser/ContentClassFactory.java",
     "java/src/org/chromium/content/browser/ContentFeatureList.java",
diff --git a/content/public/android/java/src/org/chromium/content/app/ChildProcessService.java b/content/public/android/java/src/org/chromium/content/app/ChildProcessService.java
index 9a9f212..4bce955 100644
--- a/content/public/android/java/src/org/chromium/content/app/ChildProcessService.java
+++ b/content/public/android/java/src/org/chromium/content/app/ChildProcessService.java
@@ -9,17 +9,20 @@
 import android.os.IBinder;
 
 import org.chromium.base.annotations.JNINamespace;
-import org.chromium.content.browser.ChildProcessLauncher;
 
 /**
- * This is the base class for child services; the [Non]SandboxedProcessService0, 1.. etc
+ * This is the base class for child services; the [Sandboxed|Privileged]ProcessService0, 1.. etc
  * subclasses provide the concrete service entry points, to enable the browser to connect
  * to more than one distinct process (i.e. one process per service number, up to limit of N).
  * The embedding application must declare these service instances in the application section
- * of its AndroidManifest.xml, for example with N entries of the form:-
- *     <service android:name="org.chromium.content.app.[Non]SandboxedProcessServiceX"
- *              android:process=":[non]sandboxed_processX" />
- * for X in 0...N-1 (where N is {@link ChildProcessLauncher#MAX_REGISTERED_SERVICES})
+ * of its AndroidManifest.xml, first with some meta-data describing the services:
+ *     <meta-data android:name="org.chromium.content.browser.NUM_[SANDBOXED|PRIVILEGED]_SERVICES"
+ *           android:value="N"/>
+ *     <meta-data android:name="org.chromium.content.browser.[SANDBOXED|PRIVILEGED]_SERVICES_NAME"
+ *           android:value="org.chromium.content.app.[Sandboxed|Privileged]ProcessService"/>
+ * and then N entries of the form:
+ *     <service android:name="org.chromium.content.app.[Sandboxed|Privileged]ProcessServiceX"
+ *              android:process=":[sandboxed|privileged]_processX" />
  */
 @JNINamespace("content")
 public class ChildProcessService extends Service {
diff --git a/content/public/android/java/src/org/chromium/content/browser/ChildConnectionAllocator.java b/content/public/android/java/src/org/chromium/content/browser/ChildConnectionAllocator.java
index 9354a91..472fe05 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ChildConnectionAllocator.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ChildConnectionAllocator.java
@@ -234,7 +234,7 @@
     }
 
     @VisibleForTesting
-    ChildProcessConnection[] connectionArrayForTesting() {
-        return mChildProcessConnections;
+    ChildProcessConnection getChildProcessConnectionAtSlotForTesting(int slotNumber) {
+        return mChildProcessConnections[slotNumber];
     }
 }
diff --git a/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncher.java b/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncher.java
deleted file mode 100644
index 9e5db35..0000000
--- a/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncher.java
+++ /dev/null
@@ -1,519 +0,0 @@
-// Copyright 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.content.browser;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.text.TextUtils;
-
-import org.chromium.base.Log;
-import org.chromium.base.ThreadUtils;
-import org.chromium.base.TraceEvent;
-import org.chromium.base.VisibleForTesting;
-import org.chromium.base.annotations.SuppressFBWarnings;
-import org.chromium.base.process_launcher.ChildProcessCreationParams;
-import org.chromium.content.app.SandboxedProcessService;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * This class provides the method to start/stop ChildProcess called by native.
- *
- * Note about threading. The threading here is complicated and not well documented.
- * Code can run on these threads: UI, Launcher, async thread pool, binder, and one-off
- * background threads.
- */
-public class ChildProcessLauncher {
-    private static final String TAG = "ChildProcLauncher";
-
-    private static final String NUM_SANDBOXED_SERVICES_KEY =
-            "org.chromium.content.browser.NUM_SANDBOXED_SERVICES";
-    private static final String SANDBOXED_SERVICES_NAME_KEY =
-            "org.chromium.content.browser.SANDBOXED_SERVICES_NAME";
-    private static final String NUM_PRIVILEGED_SERVICES_KEY =
-            "org.chromium.content.browser.NUM_PRIVILEGED_SERVICES";
-    private static final String PRIVILEGED_SERVICES_NAME_KEY =
-            "org.chromium.content.browser.PRIVILEGED_SERVICES_NAME";
-
-    /**
-     * Implemented by ChildProcessLauncherHelper.
-     */
-    public interface LaunchCallback {
-        void onChildProcessStarted(ChildProcessConnection connection);
-    }
-
-    // Factory used by the SpareConnection to create the actual ChildProcessConnection.
-    private static final SpareChildConnection.ConnectionFactory SANDBOXED_SPARE_CONNECTION_FATORY =
-            new SpareChildConnection.ConnectionFactory() {
-                @Override
-                public ChildProcessConnection allocateBoundConnection(Context context,
-                        ChildProcessCreationParams creationParams,
-                        ChildProcessConnection.StartCallback startCallback,
-                        ChildProcessConnection.DeathCallback deathCallback) {
-                    boolean bindToCallerCheck =
-                            creationParams == null ? false : creationParams.getBindToCallerCheck();
-                    ChildConnectionAllocator allocator =
-                            getConnectionAllocator(context, creationParams, true /* sandboxed */);
-                    return ChildProcessLauncher.allocateBoundConnection(context, allocator,
-                            true /* useBindingManager */, false /* useStrongBinding */,
-                            ChildProcessLauncherHelper.createServiceBundle(bindToCallerCheck),
-                            startCallback, deathCallback);
-                }
-            };
-
-    // Map from package name to ChildConnectionAllocator.
-    private static final Map<String, ChildConnectionAllocator>
-            sSandboxedChildConnectionAllocatorMap = new HashMap<>();
-
-    // Map from a connection to its ChildConnectionAllocator.
-    private static final Map<ChildProcessConnection, ChildConnectionAllocator>
-            sConnectionsToAllocatorMap = new HashMap<>();
-
-    // Allocator used for non-sandboxed services.
-    private static ChildConnectionAllocator sPrivilegedChildConnectionAllocator;
-
-    // Used by tests to override the default sandboxed service allocator settings.
-    private static ChildConnectionAllocator.ConnectionFactory sSandboxedServiceFactoryForTesting;
-    private static int sSandboxedServicesCountForTesting = -1;
-    private static String sSandboxedServicesNameForTesting;
-
-    // A warmed-up connection to a sandboxed service.
-    private static SpareChildConnection sSpareSandboxedConnection;
-
-    public static String getPackageNameFromCreationParams(
-            Context context, ChildProcessCreationParams params, boolean sandboxed) {
-        return (sandboxed && params != null) ? params.getPackageNameForSandboxedService()
-                                             : context.getPackageName();
-    }
-
-    public static boolean isServiceExternalFromCreationParams(
-            ChildProcessCreationParams params, boolean sandboxed) {
-        return sandboxed && params != null && params.getIsSandboxedServiceExternal();
-    }
-
-    @SuppressFBWarnings("LI_LAZY_INIT_STATIC") // Method is single thread.
-    @VisibleForTesting
-    static ChildConnectionAllocator getConnectionAllocator(
-            Context context, ChildProcessCreationParams creationParams, boolean sandboxed) {
-        assert LauncherThread.runningOnLauncherThread();
-        String packageName = getPackageNameFromCreationParams(context, creationParams, sandboxed);
-        boolean bindAsExternalService =
-                isServiceExternalFromCreationParams(creationParams, sandboxed);
-        if (!sandboxed) {
-            if (sPrivilegedChildConnectionAllocator == null) {
-                sPrivilegedChildConnectionAllocator = ChildConnectionAllocator.create(context,
-                        creationParams, packageName, PRIVILEGED_SERVICES_NAME_KEY,
-                        NUM_PRIVILEGED_SERVICES_KEY, bindAsExternalService);
-            }
-            return sPrivilegedChildConnectionAllocator;
-        }
-
-        if (!sSandboxedChildConnectionAllocatorMap.containsKey(packageName)) {
-            Log.w(TAG,
-                    "Create a new ChildConnectionAllocator with package name = %s,"
-                            + " sandboxed = true",
-                    packageName);
-            ChildConnectionAllocator connectionAllocator = null;
-            if (sSandboxedServicesCountForTesting != -1) {
-                // Testing case where allocator settings are overriden.
-                String serviceName = !TextUtils.isEmpty(sSandboxedServicesNameForTesting)
-                        ? sSandboxedServicesNameForTesting
-                        : SandboxedProcessService.class.getName();
-                connectionAllocator = ChildConnectionAllocator.createForTest(creationParams,
-                        packageName, serviceName, sSandboxedServicesCountForTesting,
-                        bindAsExternalService);
-            } else {
-                connectionAllocator = ChildConnectionAllocator.create(context, creationParams,
-                        packageName, SANDBOXED_SERVICES_NAME_KEY, NUM_SANDBOXED_SERVICES_KEY,
-                        bindAsExternalService);
-            }
-            if (sSandboxedServiceFactoryForTesting != null) {
-                connectionAllocator.setConnectionFactoryForTesting(
-                        sSandboxedServiceFactoryForTesting);
-            }
-            sSandboxedChildConnectionAllocatorMap.put(packageName, connectionAllocator);
-        }
-        return sSandboxedChildConnectionAllocatorMap.get(packageName);
-    }
-
-    private static ChildProcessConnection allocateBoundConnection(Context context,
-            ChildConnectionAllocator connectionAllocator, boolean useBindingManager,
-            boolean useStrongBinding, Bundle serviceBundle,
-            ChildProcessConnection.StartCallback startCallback,
-            final ChildProcessConnection.DeathCallback deathCallback) {
-        assert LauncherThread.runningOnLauncherThread();
-
-        ChildProcessConnection.DeathCallback deathCallbackWrapper =
-                new ChildProcessConnection.DeathCallback() {
-                    @Override
-                    public void onChildProcessDied(ChildProcessConnection connection) {
-                        assert LauncherThread.runningOnLauncherThread();
-                        if (connection.getPid() != 0) {
-                            stop(connection.getPid());
-                        } else {
-                            freeConnection(connection);
-                        }
-                        // Forward the call to the provided callback if any. The spare connection
-                        // uses that for clean-up.
-                        if (deathCallback != null) {
-                            deathCallback.onChildProcessDied(connection);
-                        }
-                    }
-                };
-
-        ChildProcessConnection connection =
-                connectionAllocator.allocate(context, serviceBundle, deathCallbackWrapper);
-        if (connection != null) {
-            sConnectionsToAllocatorMap.put(connection, connectionAllocator);
-            connection.start(useStrongBinding, startCallback);
-            if (useBindingManager && !connectionAllocator.isFreeConnectionAvailable()) {
-                // Proactively releases all the moderate bindings once all the sandboxed services
-                // are allocated, which will be very likely to have some of them killed by OOM
-                // killer.
-                getBindingManager().releaseAllModerateBindings();
-            }
-        }
-        return connection;
-    }
-
-    private static final long FREE_CONNECTION_DELAY_MILLIS = 1;
-
-    private static void freeConnection(ChildProcessConnection connection) {
-        assert LauncherThread.runningOnLauncherThread();
-
-        // Freeing a service should be delayed. This is so that we avoid immediately reusing the
-        // freed service (see http://crbug.com/164069): the framework might keep a service process
-        // alive when it's been unbound for a short time. If a new connection to the same service
-        // is bound at that point, the process is reused and bad things happen (mostly static
-        // variables are set when we don't expect them to).
-        final ChildProcessConnection conn = connection;
-        LauncherThread.postDelayed(new Runnable() {
-            @Override
-            public void run() {
-                ChildConnectionAllocator allocator = sConnectionsToAllocatorMap.remove(conn);
-                assert allocator != null;
-                allocator.free(conn);
-                // If there are no more connections for this allocator, clear it. (note that freeing
-                // the connection above may have triggered a new connection allocation from the
-                // allocator listener).
-                String packageName = allocator.getPackageName();
-                if (!allocator.anyConnectionAllocated()
-                        && sSandboxedChildConnectionAllocatorMap.get(packageName) == allocator) {
-                    sSandboxedChildConnectionAllocatorMap.remove(packageName);
-                }
-            }
-        }, FREE_CONNECTION_DELAY_MILLIS);
-    }
-
-    // Map from pid to ChildService connection.
-    private static Map<Integer, ChildProcessConnection> sServiceMap = new ConcurrentHashMap<>();
-
-    // Manages oom bindings used to bind chind services. Lazily initialized by getBindingManager()
-    private static BindingManager sBindingManager;
-
-    // Whether the main application is currently brought to the foreground.
-    private static boolean sApplicationInForeground = true;
-
-    // Lazy initialize sBindingManager
-    // TODO(boliu): This should be internal to content.
-    @SuppressFBWarnings("LI_LAZY_INIT_STATIC") // Method is single thread.
-    public static BindingManager getBindingManager() {
-        assert LauncherThread.runningOnLauncherThread();
-        if (sBindingManager == null) {
-            sBindingManager = BindingManagerImpl.createBindingManager();
-        }
-        return sBindingManager;
-    }
-
-    @VisibleForTesting
-    public static void setBindingManagerForTesting(BindingManager manager) {
-        sBindingManager = manager;
-    }
-
-    /**
-     * Called when the embedding application is sent to background.
-     */
-    public static void onSentToBackground() {
-        assert ThreadUtils.runningOnUiThread();
-        sApplicationInForeground = false;
-        LauncherThread.post(new Runnable() {
-            @Override
-            public void run() {
-                getBindingManager().onSentToBackground();
-            }
-        });
-    }
-
-    /**
-     * Called when the embedding application is brought to foreground.
-     */
-    public static void onBroughtToForeground() {
-        assert ThreadUtils.runningOnUiThread();
-        sApplicationInForeground = true;
-        LauncherThread.post(new Runnable() {
-            @Override
-            public void run() {
-                getBindingManager().onBroughtToForeground();
-            }
-        });
-    }
-
-    /**
-     * Returns whether the application is currently in the foreground.
-     */
-    static boolean isApplicationInForeground() {
-        return sApplicationInForeground;
-    }
-
-    /**
-     * Starts moderate binding management.
-     * Note: WebAPKs and non WebAPKs share the same moderate binding pool, so the size of the
-     * shared moderate binding pool is always set based on the number of sandboxes processes
-     * used by Chrome.
-     * @param context Android's context.
-     */
-    public static void startModerateBindingManagement(final Context context) {
-        assert ThreadUtils.runningOnUiThread();
-        LauncherThread.post(new Runnable() {
-            @Override
-            public void run() {
-                ChildConnectionAllocator allocator = getConnectionAllocator(
-                        context, ChildProcessCreationParams.getDefault(), true /* sandboxed */);
-                getBindingManager().startModerateBindingManagement(
-                        context, allocator.getNumberOfServices());
-            }
-        });
-    }
-
-    /**
-     * Should be called early in startup so the work needed to spawn the child process can be done
-     * in parallel to other startup work. Spare connection is created in sandboxed child process.
-     * @param context the application context used for the connection.
-     */
-    public static void warmUp(final Context context) {
-        assert ThreadUtils.runningOnUiThread();
-        LauncherThread.post(new Runnable() {
-            @Override
-            public void run() {
-                if (sSpareSandboxedConnection != null && !sSpareSandboxedConnection.isEmpty()) {
-                    return;
-                }
-                sSpareSandboxedConnection =
-                        new SpareChildConnection(context, SANDBOXED_SPARE_CONNECTION_FATORY,
-                                ChildProcessCreationParams.getDefault(), true /* sandboxed */);
-            }
-        });
-    }
-
-    /**
-     * Spawns and connects to a child process. It will not block, but will instead callback to
-     * {@link #LaunchCallback} on the launcher thread when the connection is established on.
-     *
-     * @param context Context used to obtain the application context.
-     * @param paramId Key used to retrieve ChildProcessCreationParams.
-     * @param serviceBundle The Bundle passed in the intent used to bind to the service.
-     * @param connectionBundle The Bundle passed in setupConnection call.
-     * @param launchCallback Callback invoked when the connection is established.
-     * @param childProcessCallback IBinder callback passed to the service.
-     */
-    @SuppressFBWarnings("LI_LAZY_INIT_STATIC") // Method is single thread.
-    @VisibleForTesting
-    public static boolean start(final Context context, final Bundle serviceBundle,
-            final Bundle connectionBundle, final LaunchCallback launchCallback,
-            final IBinder childProcessCallback, final boolean sandboxed,
-            final boolean useStrongBinding, final ChildProcessCreationParams creationParams) {
-        assert LauncherThread.runningOnLauncherThread();
-        try {
-            TraceEvent.begin("ChildProcessLauncher.start");
-
-            final ChildProcessConnection.StartCallback startCallback =
-                    new ChildProcessConnection.StartCallback() {
-                        @Override
-                        public void onChildStarted() {}
-
-                        @Override
-                        public void onChildStartFailed() {
-                            assert LauncherThread.runningOnLauncherThread();
-                            Log.e(TAG, "ChildProcessConnection.start failed, trying again");
-                            LauncherThread.post(new Runnable() {
-                                @Override
-                                public void run() {
-                                    // The child process may already be bound to another client
-                                    // (this can happen if multi-process WebView is used in more
-                                    // than one process), so try starting the process again.
-                                    // This connection that failed to start has not been freed,
-                                    // so a new bound connection will be allocated.
-                                    start(context, serviceBundle, connectionBundle, launchCallback,
-                                            childProcessCallback, sandboxed, useStrongBinding,
-                                            creationParams);
-                                }
-                            });
-                        }
-                    };
-
-            final ChildConnectionAllocator connectionAllocator =
-                    getConnectionAllocator(context, creationParams, sandboxed);
-            ChildProcessConnection connection = null;
-            // Try to use the spare connection if there's one.
-            if (sSpareSandboxedConnection != null) {
-                connection = sSpareSandboxedConnection.getConnection(
-                        creationParams, sandboxed, startCallback);
-                if (connection != null) {
-                    Log.d(TAG, "Using warmed-up connection for service %s.",
-                            connection.getServiceName());
-                    // Clear the spare connection now that it's been used.
-                    sSpareSandboxedConnection = null;
-                }
-            }
-
-            final boolean useBindingManager = sandboxed;
-            if (connection == null) {
-                // No spare connection was available, create one.
-                connection = allocateBoundConnection(context, connectionAllocator,
-                        useBindingManager, useStrongBinding, serviceBundle, startCallback,
-                        null /* deathCallback */);
-                if (connection == null) {
-                    // No connection is available at this time. Add a listener so when one becomes
-                    // available we create the service then.
-                    connectionAllocator.addListener(new ChildConnectionAllocator.Listener() {
-                        @Override
-                        public void onConnectionFreed(ChildConnectionAllocator allocator,
-                                ChildProcessConnection connection) {
-                            if (!allocator.isFreeConnectionAvailable()) return;
-                            allocator.removeListener(this);
-                            ChildProcessConnection newConnection = allocateBoundConnection(context,
-                                    connectionAllocator, useBindingManager, useStrongBinding,
-                                    serviceBundle, startCallback, null /* deathCallback */);
-                            assert newConnection != null;
-                            setupConnection(newConnection, connectionBundle, childProcessCallback,
-                                    launchCallback, useBindingManager);
-                        }
-                    });
-                    return false;
-                }
-            }
-            setupConnection(connection, connectionBundle, childProcessCallback, launchCallback,
-                    useBindingManager);
-            return true;
-        } finally {
-            TraceEvent.end("ChildProcessLauncher.start");
-        }
-    }
-
-    @VisibleForTesting
-    static void setupConnection(final ChildProcessConnection connection, Bundle connectionBundle,
-            final IBinder childProcessCallback, final LaunchCallback launchCallback,
-            final boolean addToBindingmanager) {
-        assert LauncherThread.runningOnLauncherThread();
-        Log.d(TAG, "Setting up connection to process, connection name=%s",
-                connection.getServiceName());
-        ChildProcessConnection.ConnectionCallback connectionCallback =
-                new ChildProcessConnection.ConnectionCallback() {
-                    @Override
-                    public void onConnected(ChildProcessConnection connection) {
-                        assert LauncherThread.runningOnLauncherThread();
-                        if (connection != null) {
-                            int pid = connection.getPid();
-                            Log.d(TAG, "on connect callback, pid=%d", pid);
-                            if (addToBindingmanager) {
-                                getBindingManager().addNewConnection(pid, connection);
-                            }
-                            sServiceMap.put(pid, connection);
-                        }
-                        // If the connection fails and pid == 0, the Java-side cleanup was already
-                        // handled by DeathCallback. We still have to call back to native for
-                        // cleanup there.
-                        if (launchCallback != null) { // Will be null in Java instrumentation tests.
-                            launchCallback.onChildProcessStarted(connection);
-                        }
-                    }
-                };
-
-        connection.setupConnection(connectionBundle, childProcessCallback, connectionCallback);
-    }
-
-    /**
-     * Terminates a child process. This may be called from any thread.
-     *
-     * @param pid The pid (process handle) of the service connection obtained from {@link #start}.
-     */
-    static void stop(int pid) {
-        assert LauncherThread.runningOnLauncherThread();
-        Log.d(TAG, "stopping child connection: pid=%d", pid);
-        ChildProcessConnection connection = sServiceMap.remove(pid);
-        if (connection == null) {
-            // Can happen for single process.
-            return;
-        }
-        getBindingManager().removeConnection(pid);
-        connection.stop();
-        freeConnection(connection);
-    }
-
-    public static int getNumberOfSandboxedServices(Context context, String packageName) {
-        assert ThreadUtils.runningOnUiThread();
-        if (sSandboxedServicesCountForTesting != -1) {
-            return sSandboxedServicesCountForTesting;
-        }
-        return ChildConnectionAllocator.getNumberOfServices(
-                context, packageName, NUM_SANDBOXED_SERVICES_KEY);
-    }
-
-    /** @return the count of services set up and working */
-    @VisibleForTesting
-    static int connectedServicesCountForTesting() {
-        return sServiceMap.size();
-    }
-
-    @VisibleForTesting
-    public static void setSandboxServicesSettingsForTesting(
-            ChildConnectionAllocator.ConnectionFactory factory, int serviceCount,
-            String serviceName) {
-        sSandboxedServiceFactoryForTesting = factory;
-        sSandboxedServicesCountForTesting = serviceCount;
-        sSandboxedServicesNameForTesting = serviceName;
-    }
-
-    @VisibleForTesting
-    static ChildProcessConnection allocateSandboxedBoundConnectionForTesting(
-            Context context, ChildProcessCreationParams creationParams) {
-        ChildConnectionAllocator allocator =
-                getConnectionAllocator(context, creationParams, true /* sandboxed */);
-        return allocateBoundConnection(context, allocator, true /* useBindingManager */,
-                false /* useStrongBinding */, null /* serviceBundle */, null /* startCallback */,
-                null /* deathCallback */);
-    }
-
-    /**
-     * Kills the child process for testing.
-     * @return true iff the process was killed as expected
-     */
-    @VisibleForTesting
-    public static boolean crashProcessForTesting(int pid) {
-        if (sServiceMap.get(pid) == null) return false;
-
-        try {
-            sServiceMap.get(pid).crashServiceForTesting();
-        } catch (RemoteException ex) {
-            return false;
-        }
-
-        return true;
-    }
-
-    @VisibleForTesting
-    public static Map<String, ChildConnectionAllocator> getSandboxedAllocatorMapForTesting() {
-        return sSandboxedChildConnectionAllocatorMap;
-    }
-
-    @VisibleForTesting
-    public static ChildProcessConnection getWarmUpConnectionForTesting() {
-        return sSpareSandboxedConnection == null ? null : sSpareSandboxedConnection.getConnection();
-    }
-}
diff --git a/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelper.java b/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelper.java
index 7b68e3b..6681b87 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelper.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelper.java
@@ -8,20 +8,28 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.text.TextUtils;
 
 import org.chromium.base.ContextUtils;
 import org.chromium.base.CpuFeatures;
 import org.chromium.base.Log;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.TraceEvent;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.SuppressFBWarnings;
 import org.chromium.base.library_loader.Linker;
 import org.chromium.base.process_launcher.ChildProcessCreationParams;
 import org.chromium.base.process_launcher.FileDescriptorInfo;
 import org.chromium.content.app.ChromiumLinkerParams;
+import org.chromium.content.app.SandboxedProcessService;
 import org.chromium.content.common.ContentSwitches;
 
 import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * This is the java counterpart to ChildProcessLauncherHelper. It is owned by native side and
@@ -32,23 +40,96 @@
 public class ChildProcessLauncherHelper {
     private static final String TAG = "ChildProcLH";
 
+    // Manifest values used to specify the service names.
+    private static final String NUM_SANDBOXED_SERVICES_KEY =
+            "org.chromium.content.browser.NUM_SANDBOXED_SERVICES";
+    private static final String SANDBOXED_SERVICES_NAME_KEY =
+            "org.chromium.content.browser.SANDBOXED_SERVICES_NAME";
+    private static final String NUM_PRIVILEGED_SERVICES_KEY =
+            "org.chromium.content.browser.NUM_PRIVILEGED_SERVICES";
+    private static final String PRIVILEGED_SERVICES_NAME_KEY =
+            "org.chromium.content.browser.PRIVILEGED_SERVICES_NAME";
+
     // Represents an invalid process handle; same as base/process/process.h kNullProcessHandle.
     private static final int NULL_PROCESS_HANDLE = 0;
 
+    // Delay between the call to freeConnection and the connection actually beeing   freed.
+    private static final long FREE_CONNECTION_DELAY_MILLIS = 1;
+
+    // Factory used by the SpareConnection to create the actual ChildProcessConnection.
+    private static final SpareChildConnection.ConnectionFactory SANDBOXED_CONNECTION_FACTORY =
+            new SpareChildConnection.ConnectionFactory() {
+                @Override
+                public ChildProcessConnection allocateBoundConnection(Context context,
+                        ChildProcessCreationParams creationParams,
+                        ChildProcessConnection.StartCallback startCallback,
+                        ChildProcessConnection.DeathCallback deathCallback) {
+                    boolean bindToCallerCheck =
+                            creationParams == null ? false : creationParams.getBindToCallerCheck();
+                    ChildConnectionAllocator allocator =
+                            getConnectionAllocator(context, creationParams, true /* sandboxed */);
+                    return ChildProcessLauncherHelper.allocateBoundConnection(context, allocator,
+                            false /* useStrongBinding */, true /* useBindingManager */,
+                            ChildProcessLauncherHelper.createServiceBundle(bindToCallerCheck),
+                            startCallback, deathCallback);
+                }
+            };
+
+    // A warmed-up connection to a sandboxed service.
+    private static SpareChildConnection sSpareSandboxedConnection;
+
+    // Map from package name to ChildConnectionAllocator.
+    private static final Map<String, ChildConnectionAllocator>
+            sSandboxedChildConnectionAllocatorMap = new HashMap<>();
+
+    // Allocator used for non-sandboxed services.
+    private static ChildConnectionAllocator sPrivilegedChildConnectionAllocator;
+
+    // Used by tests to override the default sandboxed service allocator settings.
+    private static ChildConnectionAllocator.ConnectionFactory sSandboxedServiceFactoryForTesting;
+    private static int sSandboxedServicesCountForTesting = -1;
+    private static String sSandboxedServicesNameForTesting;
+
+    // Map from PID to ChildService connection.
+    private static Map<Integer, ChildProcessLauncherHelper> sLauncherByPid = new HashMap<>();
+
+    // Manages OOM bindings used to bind chind services. Lazily initialized by getBindingManager().
+    private static BindingManager sBindingManager;
+
+    // Whether the main application is currently brought to the foreground.
+    private static boolean sApplicationInForeground = true;
+
     // The IBinder provided to the created service.
     private final IBinder mIBinderCallback;
 
+    // Whether the connection is managed by the BindingManager.
+    private final boolean mUseBindingManager;
+
+    // Whether the connection has a strong binding (which also means it is not managed by the
+    // BindingManager).
+    private final boolean mUseStrongBinding;
+
+    // Whether the connection should be setup once connected. Tests can set this to false to
+    // simulate a connected but not yet setup connection.
+    private final boolean mDoSetupConnection;
+
+    private final ChildProcessCreationParams mCreationParams;
+
+    private final String[] mCommandLine;
+
+    private final FileDescriptorInfo[] mFilesToBeMapped;
+
+    // The allocator used to create the connection.
+    private final ChildConnectionAllocator mConnectionAllocator;
+
+    // Whether the created process should be sandboxed.
+    private final boolean mSandboxed;
+
     // Note native pointer is only guaranteed live until nativeOnChildProcessStarted.
     private long mNativeChildProcessLauncherHelper;
 
     // The actual service connection. Set once we have connected to the service.
-    private ChildProcessConnection mChildProcessConnection;
-
-    // True if a connection is available for this launcher (mChildProcessConnection being set when
-    // the connection is eastblished and might still be null). If false it means no connection was
-    // available and a request for one has been queued.
-    // This is used by tests.
-    private boolean mHasConnection;
+    private ChildProcessConnection mConnection;
 
     @CalledByNative
     private static FileDescriptorInfo makeFdInfo(
@@ -72,121 +153,457 @@
     @VisibleForTesting
     @CalledByNative
     public static ChildProcessLauncherHelper createAndStart(long nativePointer, int paramId,
-            final String[] commandLine, FileDescriptorInfo[] filesToBeMapped) {
+            String[] commandLine, FileDescriptorInfo[] filesToBeMapped) {
         assert LauncherThread.runningOnLauncherThread();
-        String processType =
-                ContentSwitches.getSwitchValue(commandLine, ContentSwitches.SWITCH_PROCESS_TYPE);
-
-        ChildProcessCreationParams params = ChildProcessCreationParams.get(paramId);
-        if (paramId != ChildProcessCreationParams.DEFAULT_ID && params == null) {
+        ChildProcessCreationParams creationParams = ChildProcessCreationParams.get(paramId);
+        if (paramId != ChildProcessCreationParams.DEFAULT_ID && creationParams == null) {
             throw new RuntimeException("CreationParams id " + paramId + " not found");
         }
 
-        Context context = ContextUtils.getApplicationContext();
+        String processType =
+                ContentSwitches.getSwitchValue(commandLine, ContentSwitches.SWITCH_PROCESS_TYPE);
+
         boolean sandboxed = true;
-        boolean alwaysInForeground = false;
+        boolean useStrongBinding = false;
         if (!ContentSwitches.SWITCH_RENDERER_PROCESS.equals(processType)) {
             if (ContentSwitches.SWITCH_GPU_PROCESS.equals(processType)) {
                 sandboxed = false;
-                alwaysInForeground = true;
+                useStrongBinding = true;
             } else {
                 // We only support sandboxed utility processes now.
                 assert ContentSwitches.SWITCH_UTILITY_PROCESS.equals(processType);
             }
         }
 
-        ChildProcessLauncherHelper process_launcher =
-                new ChildProcessLauncherHelper(nativePointer, processType);
-        process_launcher.start(
-                context, commandLine, filesToBeMapped, params, sandboxed, alwaysInForeground);
+        IBinder binderCallback = ContentSwitches.SWITCH_GPU_PROCESS.equals(processType)
+                ? new GpuProcessCallback()
+                : null;
+
+        ChildProcessLauncherHelper process_launcher = new ChildProcessLauncherHelper(nativePointer,
+                creationParams, commandLine, filesToBeMapped, useStrongBinding, sandboxed,
+                binderCallback, true /* doSetupConnection */);
+        process_launcher.start();
         return process_launcher;
     }
 
-    private ChildProcessLauncherHelper(long nativePointer, String processType) {
+    /**
+     * Creates a ready to use sandboxed child process. Should be called early during startup so the
+     * child process is created while other startup work is happening.
+     * @param context the application context used for the connection.
+     */
+    public static void warmUp(final Context context) {
+        assert ThreadUtils.runningOnUiThread();
+        LauncherThread.post(new Runnable() {
+            @Override
+            public void run() {
+                if (sSpareSandboxedConnection != null && !sSpareSandboxedConnection.isEmpty()) {
+                    return;
+                }
+                sSpareSandboxedConnection =
+                        new SpareChildConnection(context, SANDBOXED_CONNECTION_FACTORY,
+                                ChildProcessCreationParams.getDefault(), true /* sandboxed */);
+            }
+        });
+    }
+
+    public static String getPackageNameFromCreationParams(
+            Context context, ChildProcessCreationParams params, boolean sandboxed) {
+        return (sandboxed && params != null) ? params.getPackageNameForSandboxedService()
+                                             : context.getPackageName();
+    }
+
+    public static boolean isServiceExternalFromCreationParams(
+            ChildProcessCreationParams params, boolean sandboxed) {
+        return sandboxed && params != null && params.getIsSandboxedServiceExternal();
+    }
+
+    /**
+     * Starts the moderate binding management that adjust a process priority in response to various
+     * signals (app sent to background/foreground for example).
+     * Note: WebAPKs and non WebAPKs share the same moderate binding pool, so the size of the
+     * shared moderate binding pool is always set based on the number of sandboxes processes
+     * used by Chrome.
+     * @param context Android's context.
+     */
+    public static void startModerateBindingManagement(final Context context) {
+        assert ThreadUtils.runningOnUiThread();
+        LauncherThread.post(new Runnable() {
+            @Override
+            public void run() {
+                ChildConnectionAllocator allocator = getConnectionAllocator(
+                        context, null /* creationParams */, true /* sandboxed */);
+                getBindingManager().startModerateBindingManagement(
+                        context, allocator.getNumberOfServices());
+            }
+        });
+    }
+
+    // Lazy initialize sBindingManager
+    // TODO(boliu): This should be internal to content.
+    @SuppressFBWarnings("LI_LAZY_INIT_STATIC") // Method is single thread.
+    public static BindingManager getBindingManager() {
         assert LauncherThread.runningOnLauncherThread();
+        if (sBindingManager == null) {
+            sBindingManager = BindingManagerImpl.createBindingManager();
+        }
+        return sBindingManager;
+    }
+
+    /**
+     * Called when the embedding application is sent to background.
+     */
+    public static void onSentToBackground() {
+        assert ThreadUtils.runningOnUiThread();
+        sApplicationInForeground = false;
+        LauncherThread.post(new Runnable() {
+            @Override
+            public void run() {
+                getBindingManager().onSentToBackground();
+            }
+        });
+    }
+
+    /**
+     * Called when the embedding application is brought to foreground.
+     */
+    public static void onBroughtToForeground() {
+        assert ThreadUtils.runningOnUiThread();
+        sApplicationInForeground = true;
+        LauncherThread.post(new Runnable() {
+            @Override
+            public void run() {
+                getBindingManager().onBroughtToForeground();
+            }
+        });
+    }
+
+    @VisibleForTesting
+    public static void setSandboxServicesSettingsForTesting(
+            ChildConnectionAllocator.ConnectionFactory factory, int serviceCount,
+            String serviceName) {
+        sSandboxedServiceFactoryForTesting = factory;
+        sSandboxedServicesCountForTesting = serviceCount;
+        sSandboxedServicesNameForTesting = serviceName;
+    }
+
+    @VisibleForTesting
+    public static void setBindingManagerForTesting(BindingManager manager) {
+        sBindingManager = manager;
+    }
+
+    @VisibleForTesting
+    static ChildConnectionAllocator getConnectionAllocator(
+            Context context, ChildProcessCreationParams creationParams, boolean sandboxed) {
+        assert LauncherThread.runningOnLauncherThread();
+        String packageName = getPackageNameFromCreationParams(context, creationParams, sandboxed);
+        boolean bindAsExternalService =
+                isServiceExternalFromCreationParams(creationParams, sandboxed);
+
+        if (!sandboxed) {
+            if (sPrivilegedChildConnectionAllocator == null) {
+                sPrivilegedChildConnectionAllocator = ChildConnectionAllocator.create(context,
+                        creationParams, packageName, PRIVILEGED_SERVICES_NAME_KEY,
+                        NUM_PRIVILEGED_SERVICES_KEY, bindAsExternalService);
+            }
+            return sPrivilegedChildConnectionAllocator;
+        }
+
+        if (!sSandboxedChildConnectionAllocatorMap.containsKey(packageName)) {
+            Log.w(TAG,
+                    "Create a new ChildConnectionAllocator with package name = %s,"
+                            + " sandboxed = true",
+                    packageName);
+            ChildConnectionAllocator connectionAllocator = null;
+            if (sSandboxedServicesCountForTesting != -1) {
+                // Testing case where allocator settings are overriden.
+                String serviceName = !TextUtils.isEmpty(sSandboxedServicesNameForTesting)
+                        ? sSandboxedServicesNameForTesting
+                        : SandboxedProcessService.class.getName();
+                connectionAllocator = ChildConnectionAllocator.createForTest(creationParams,
+                        packageName, serviceName, sSandboxedServicesCountForTesting,
+                        bindAsExternalService);
+            } else {
+                connectionAllocator = ChildConnectionAllocator.create(context, creationParams,
+                        packageName, SANDBOXED_SERVICES_NAME_KEY, NUM_SANDBOXED_SERVICES_KEY,
+                        bindAsExternalService);
+            }
+            if (sSandboxedServiceFactoryForTesting != null) {
+                connectionAllocator.setConnectionFactoryForTesting(
+                        sSandboxedServiceFactoryForTesting);
+            }
+            sSandboxedChildConnectionAllocatorMap.put(packageName, connectionAllocator);
+        }
+        return sSandboxedChildConnectionAllocatorMap.get(packageName);
+    }
+
+    private static ChildProcessConnection allocateBoundConnection(Context context,
+            final ChildConnectionAllocator connectionAllocator, boolean useStrongBinding,
+            boolean useBindingManager, Bundle serviceBundle,
+            ChildProcessConnection.StartCallback startCallback,
+            final ChildProcessConnection.DeathCallback deathCallback) {
+        assert LauncherThread.runningOnLauncherThread();
+
+        ChildProcessConnection.DeathCallback deathCallbackWrapper =
+                new ChildProcessConnection.DeathCallback() {
+                    @Override
+                    public void onChildProcessDied(ChildProcessConnection connection) {
+                        assert LauncherThread.runningOnLauncherThread();
+                        if (connection.getPid() != 0) {
+                            stop(connection.getPid());
+                        } else {
+                            freeConnection(connectionAllocator, connection);
+                        }
+                        // Forward the call to the provided callback if any. The spare connection
+                        // uses that for clean-up.
+                        if (deathCallback != null) {
+                            deathCallback.onChildProcessDied(connection);
+                        }
+                    }
+                };
+
+        ChildProcessConnection connection =
+                connectionAllocator.allocate(context, serviceBundle, deathCallbackWrapper);
+        if (connection != null) {
+            connection.start(useStrongBinding, startCallback);
+            if (useBindingManager && !connectionAllocator.isFreeConnectionAvailable()) {
+                // Proactively releases all the moderate bindings once all the sandboxed services
+                // are allocated, which will be very likely to have some of them killed by OOM
+                // killer.
+                getBindingManager().releaseAllModerateBindings();
+            }
+        }
+        return connection;
+    }
+
+    private ChildProcessLauncherHelper(long nativePointer,
+            ChildProcessCreationParams creationParams, String[] commandLine,
+            FileDescriptorInfo[] filesToBeMapped, boolean useStrongBinding, boolean sandboxed,
+            IBinder binderCallback, boolean doSetupConnection) {
+        assert LauncherThread.runningOnLauncherThread();
+
+        mCreationParams = creationParams;
+        mCommandLine = commandLine;
+        mFilesToBeMapped = filesToBeMapped;
+        mUseStrongBinding = useStrongBinding;
+        mUseBindingManager = sandboxed;
         mNativeChildProcessLauncherHelper = nativePointer;
-        mIBinderCallback = ContentSwitches.SWITCH_GPU_PROCESS.equals(processType)
-                ? new GpuProcessCallback()
-                : null;
+        mIBinderCallback = binderCallback;
+        mDoSetupConnection = doSetupConnection;
+        mSandboxed = sandboxed;
+
+        mConnectionAllocator = getConnectionAllocator(
+                ContextUtils.getApplicationContext(), mCreationParams, sandboxed);
+
         initLinker();
     }
 
-    private void start(Context context, String[] commandLine,
-            final FileDescriptorInfo[] filesToBeMapped, ChildProcessCreationParams params,
-            boolean sandboxed, boolean alwaysInForeground) {
-        boolean bindToCallerCheck = params == null ? false : params.getBindToCallerCheck();
-        Bundle serviceBundle = createServiceBundle(bindToCallerCheck);
-        onBeforeConnectionAllocated(serviceBundle);
+    private void start() {
+        assert LauncherThread.runningOnLauncherThread();
+        assert mConnection == null;
+        try {
+            TraceEvent.begin("ChildProcessLauncher.start");
 
-        Bundle connectionBundle = createConnectionBundle(commandLine, filesToBeMapped);
-        mHasConnection = ChildProcessLauncher.start(context, serviceBundle,
-                connectionBundle, new ChildProcessLauncher.LaunchCallback() {
-                    @Override
-                    public void onChildProcessStarted(ChildProcessConnection connection) {
-                        mChildProcessConnection = connection;
-                        mHasConnection = true;
+            ChildProcessConnection.StartCallback startCallback =
+                    new ChildProcessConnection.StartCallback() {
+                        @Override
+                        public void onChildStarted() {}
 
-                        // Proactively close the FDs rather than waiting for the GC to do it.
-                        try {
-                            for (FileDescriptorInfo fileInfo : filesToBeMapped) {
-                                fileInfo.fd.close();
-                            }
-                        } catch (IOException ioe) {
-                            Log.w(TAG, "Failed to close FD.", ioe);
+                        @Override
+                        public void onChildStartFailed() {
+                            assert LauncherThread.runningOnLauncherThread();
+                            Log.e(TAG, "ChildProcessConnection.start failed, trying again");
+                            LauncherThread.post(new Runnable() {
+                                @Override
+                                public void run() {
+                                    // The child process may already be bound to another client
+                                    // (this can happen if multi-process WebView is used in more
+                                    // than one process), so try starting the process again.
+                                    // This connection that failed to start has not been freed,
+                                    // so a new bound connection will be allocated.
+                                    mConnection = null;
+                                    start();
+                                }
+                            });
                         }
+                    };
 
-                        if (mNativeChildProcessLauncherHelper != 0) {
-                            nativeOnChildProcessStarted(
-                                    mNativeChildProcessLauncherHelper, getPid());
-                        }
-                        mNativeChildProcessLauncherHelper = 0;
-                    }
-                }, getIBinderCallback(), sandboxed, alwaysInForeground, params);
+            // Try to use the spare connection if there's one.
+            if (sSpareSandboxedConnection != null) {
+                mConnection = sSpareSandboxedConnection.getConnection(
+                        mCreationParams, mSandboxed, startCallback);
+                if (mConnection != null) {
+                    Log.d(TAG, "Using warmed-up connection for service %s.",
+                            mConnection.getServiceName());
+                    // Clear the spare connection now that it's been used.
+                    sSpareSandboxedConnection = null;
+                }
+            }
+
+            allocateAndSetupConnection(startCallback);
+        } finally {
+            TraceEvent.end("ChildProcessLauncher.start");
+        }
     }
 
-    private int getPid() {
-        return mChildProcessConnection == null ? NULL_PROCESS_HANDLE
-                                               : mChildProcessConnection.getPid();
+    // Allocates a new connection and calls setUp on it.
+    // If there are no available connections, it will retry when one becomes available.
+    private boolean allocateAndSetupConnection(
+            final ChildProcessConnection.StartCallback startCallback) {
+        if (mConnection == null) {
+            final Context context = ContextUtils.getApplicationContext();
+            boolean bindToCallerCheck =
+                    mCreationParams == null ? false : mCreationParams.getBindToCallerCheck();
+            Bundle serviceBundle = createServiceBundle(bindToCallerCheck);
+            onBeforeConnectionAllocated(serviceBundle);
+
+            mConnection = allocateBoundConnection(context, mConnectionAllocator, mUseStrongBinding,
+                    mUseBindingManager, serviceBundle, startCallback, null /* deathCallback */);
+            if (mConnection == null) {
+                // No connection is available at this time. Add a listener so when one becomes
+                // available we can create the service.
+                mConnectionAllocator.addListener(new ChildConnectionAllocator.Listener() {
+                    @Override
+                    public void onConnectionFreed(
+                            ChildConnectionAllocator allocator, ChildProcessConnection connection) {
+                        assert allocator == mConnectionAllocator;
+                        if (!allocator.isFreeConnectionAvailable()) return;
+                        allocator.removeListener(this);
+                        boolean success = allocateAndSetupConnection(startCallback);
+                        assert success;
+                    }
+                });
+                return false;
+            }
+        }
+
+        assert mConnection != null;
+        Bundle connectionBundle = createConnectionBundle();
+        onConnectionBound(mConnection, mConnectionAllocator, connectionBundle);
+
+        if (mDoSetupConnection) {
+            setupConnection(connectionBundle);
+        }
+        return true;
+    }
+
+    private void setupConnection(Bundle connectionBundle) {
+        ChildProcessConnection.ConnectionCallback connectionCallback =
+                new ChildProcessConnection.ConnectionCallback() {
+                    @Override
+                    public void onConnected(ChildProcessConnection connection) {
+                        assert mConnection == connection;
+                        onChildProcessStarted();
+                    }
+                };
+        mConnection.setupConnection(connectionBundle, getIBinderCallback(), connectionCallback);
+    }
+
+    private static void freeConnection(final ChildConnectionAllocator connectionAllocator,
+            final ChildProcessConnection connection) {
+        assert LauncherThread.runningOnLauncherThread();
+
+        // Freeing a service should be delayed. This is so that we avoid immediately reusing the
+        // freed service (see http://crbug.com/164069): the framework might keep a service process
+        // alive when it's been unbound for a short time. If a new connection to the same service
+        // is bound at that point, the process is reused and bad things happen (mostly static
+        // variables are set when we don't expect them to).
+        LauncherThread.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                assert connectionAllocator != null;
+                connectionAllocator.free(connection);
+                String packageName = connectionAllocator.getPackageName();
+                if (!connectionAllocator.anyConnectionAllocated()
+                        && sSandboxedChildConnectionAllocatorMap.get(packageName)
+                                == connectionAllocator) {
+                    sSandboxedChildConnectionAllocatorMap.remove(packageName);
+                }
+            }
+        }, FREE_CONNECTION_DELAY_MILLIS);
+    }
+
+    public void onChildProcessStarted() {
+        assert LauncherThread.runningOnLauncherThread();
+
+        int pid = mConnection.getPid();
+        Log.d(TAG, "on connect callback, pid=%d", pid);
+
+        onConnectionEstablished(mConnection);
+
+        sLauncherByPid.put(pid, this);
+
+        // Proactively close the FDs rather than waiting for the GC to do it.
+        try {
+            for (FileDescriptorInfo fileInfo : mFilesToBeMapped) {
+                fileInfo.fd.close();
+            }
+        } catch (IOException ioe) {
+            Log.w(TAG, "Failed to close FD.", ioe);
+        }
+
+        // If the connection fails and pid == 0, the Java-side cleanup was already handled by
+        // DeathCallback. We still have to call back to native for cleanup there.
+        if (mNativeChildProcessLauncherHelper != 0) {
+            nativeOnChildProcessStarted(mNativeChildProcessLauncherHelper, getPid());
+        }
+        mNativeChildProcessLauncherHelper = 0;
+    }
+
+    public int getPid() {
+        assert LauncherThread.runningOnLauncherThread();
+        return mConnection == null ? NULL_PROCESS_HANDLE : mConnection.getPid();
     }
 
     // Called on client (UI or IO) thread.
     @CalledByNative
     private boolean isOomProtected() {
-        // mChildProcessConnection is set on a different thread but does not change once it's been
-        // set. So it is safe to test whether it's null from a different thread.
-        if (mChildProcessConnection == null) {
+        // mConnection is set on a different thread but does not change once it's been set. So it is
+        // safe to test whether it's null from a different thread.
+        if (mConnection == null) {
             return false;
         }
 
         // We consider the process to be child protected if it has a strong or moderate binding and
         // the app is in the foreground.
-        return ChildProcessLauncher.isApplicationInForeground()
-                && !mChildProcessConnection.isWaivedBoundOnlyOrWasWhenDied();
+        return sApplicationInForeground && !mConnection.isWaivedBoundOnlyOrWasWhenDied();
     }
 
     @CalledByNative
     private void setInForeground(int pid, boolean foreground, boolean boostForPendingViews) {
         assert LauncherThread.runningOnLauncherThread();
-        assert mChildProcessConnection != null;
+        assert mConnection != null;
         assert getPid() == pid;
-        ChildProcessLauncher.getBindingManager().setPriority(pid, foreground, boostForPendingViews);
+        getBindingManager().setPriority(pid, foreground, boostForPendingViews);
     }
 
     @CalledByNative
-    private static void stop(int pid) {
+    static void stop(int pid) {
         assert LauncherThread.runningOnLauncherThread();
-        ChildProcessLauncher.stop(pid);
+        Log.d(TAG, "stopping child connection: pid=%d", pid);
+        ChildProcessLauncherHelper launcher = sLauncherByPid.remove(pid);
+        if (launcher == null) {
+            // Can happen for single process.
+            return;
+        }
+        launcher.onConnectionLost(launcher.mConnection, pid);
+        launcher.mConnection.stop();
+        freeConnection(launcher.mConnectionAllocator, launcher.mConnection);
     }
 
-    // Called on UI thread.
     @CalledByNative
     private static int getNumberOfRendererSlots() {
+        assert ThreadUtils.runningOnUiThread();
+        if (sSandboxedServicesCountForTesting != -1) {
+            return sSandboxedServicesCountForTesting;
+        }
+
         final ChildProcessCreationParams params = ChildProcessCreationParams.getDefault();
         final Context context = ContextUtils.getApplicationContext();
-        final String packageName = ChildProcessLauncher.getPackageNameFromCreationParams(
+        final String packageName = ChildProcessLauncherHelper.getPackageNameFromCreationParams(
                 context, params, true /* inSandbox */);
         try {
-            return ChildProcessLauncher.getNumberOfSandboxedServices(context, packageName);
+            return ChildConnectionAllocator.getNumberOfServices(
+                    context, packageName, NUM_SANDBOXED_SERVICES_KEY);
         } catch (RuntimeException e) {
             // Unittest packages do not declare services. Some tests require a realistic number
             // to test child process policies, so pick a high-ish number here.
@@ -239,9 +656,7 @@
      * @param commandLine Command line params to be passed to the service.
      * @param linkerParams Linker params to start the service.
      */
-    // TODO(jcivelli): make private once warmup connection code is move from ChildProcessLauncher to
-    // this class and remove initLinker call.
-    static Bundle createServiceBundle(boolean bindToCallerCheck) {
+    private static Bundle createServiceBundle(boolean bindToCallerCheck) {
         initLinker();
         Bundle bundle = new Bundle();
         bundle.putBoolean(ChildProcessConstants.EXTRA_BIND_TO_CALLER, bindToCallerCheck);
@@ -250,18 +665,10 @@
         return bundle;
     }
 
-    @VisibleForTesting
-    public static Bundle createConnectionBundle(
-            String[] commandLine, FileDescriptorInfo[] filesToBeMapped) {
-        assert sLinkerInitialized;
-
+    public Bundle createConnectionBundle() {
         Bundle bundle = new Bundle();
-        bundle.putStringArray(ChildProcessConstants.EXTRA_COMMAND_LINE, commandLine);
-        bundle.putParcelableArray(ChildProcessConstants.EXTRA_FILES, filesToBeMapped);
-        // content specific parameters.
-        bundle.putInt(ChildProcessConstants.EXTRA_CPU_COUNT, CpuFeatures.getCount());
-        bundle.putLong(ChildProcessConstants.EXTRA_CPU_FEATURES, CpuFeatures.getMask());
-        bundle.putBundle(Linker.EXTRA_LINKER_SHARED_RELROS, Linker.getInstance().getSharedRelros());
+        bundle.putStringArray(ChildProcessConstants.EXTRA_COMMAND_LINE, mCommandLine);
+        bundle.putParcelableArray(ChildProcessConstants.EXTRA_FILES, mFilesToBeMapped);
         return bundle;
     }
 
@@ -271,32 +678,115 @@
         // TODO(jcivelli): move createServiceBundle in there.
     }
 
+    // Called once a connection has been allocated.
+    private void onConnectionBound(ChildProcessConnection connection,
+            ChildConnectionAllocator connectionAllocator, Bundle connectionBundle) {
+        if (mUseBindingManager && !connectionAllocator.isFreeConnectionAvailable()) {
+            // Proactively releases all the moderate bindings once all the sandboxed services are
+            // allocated, which will be very likely to have some of them killed by OOM killer.
+            getBindingManager().releaseAllModerateBindings();
+        }
+
+        // Popuplate the bundle passed to the service setup call with content specific parameters.
+        connectionBundle.putInt(ChildProcessConstants.EXTRA_CPU_COUNT, CpuFeatures.getCount());
+        connectionBundle.putLong(ChildProcessConstants.EXTRA_CPU_FEATURES, CpuFeatures.getMask());
+        connectionBundle.putBundle(
+                Linker.EXTRA_LINKER_SHARED_RELROS, Linker.getInstance().getSharedRelros());
+    }
+
+    private void onConnectionEstablished(ChildProcessConnection connection) {
+        if (mUseBindingManager) {
+            getBindingManager().addNewConnection(connection.getPid(), connection);
+        }
+    }
+
+    // Called when a connection has been disconnected. Only invoked if onConnectionEstablished was
+    // called, meaning the connection was already established.
+    private void onConnectionLost(ChildProcessConnection connection, int pid) {
+        if (mUseBindingManager) {
+            getBindingManager().removeConnection(pid);
+        }
+    }
+
     private IBinder getIBinderCallback() {
         return mIBinderCallback;
     }
 
     // Testing only related methods.
     @VisibleForTesting
-    public static ChildProcessLauncherHelper createAndStartForTesting(long nativePointer,
-            String[] commandLine, FileDescriptorInfo[] filesToBeMapped,
-            ChildProcessCreationParams creationParams, boolean sandboxed,
-            boolean alwaysInForeground) {
-        String processType =
-                ContentSwitches.getSwitchValue(commandLine, ContentSwitches.SWITCH_PROCESS_TYPE);
+    public static ChildProcessLauncherHelper createAndStartForTesting(
+            ChildProcessCreationParams creationParams, String[] commandLine,
+            FileDescriptorInfo[] filesToBeMapped, boolean useStrongBinding, boolean sandboxed,
+            IBinder binderCallback, boolean doSetupConnection) {
         ChildProcessLauncherHelper launcherHelper =
-                new ChildProcessLauncherHelper(nativePointer, processType);
-        launcherHelper.start(ContextUtils.getApplicationContext(), commandLine, filesToBeMapped,
-                creationParams, sandboxed, alwaysInForeground);
+                new ChildProcessLauncherHelper(0L, creationParams, commandLine, filesToBeMapped,
+                        useStrongBinding, sandboxed, binderCallback, doSetupConnection);
+        launcherHelper.start();
         return launcherHelper;
     }
 
     @VisibleForTesting
-    public ChildProcessConnection getChildProcessConnection() {
-        return mChildProcessConnection;
+    public static ChildProcessLauncherHelper getLauncherForPid(int pid) {
+        return sLauncherByPid.get(pid);
+    }
+
+    /** @return the count of services set-up and working. */
+    @VisibleForTesting
+    static int getConnectedServicesCountForTesting() {
+        int count = sPrivilegedChildConnectionAllocator == null
+                ? 0
+                : sPrivilegedChildConnectionAllocator.allocatedConnectionsCountForTesting();
+        return count + getConnectedSandboxedServicesCountForTesting(null /* packageName */);
     }
 
     @VisibleForTesting
-    public boolean hasConnection() {
-        return mHasConnection;
+    public static int getConnectedSandboxedServicesCountForTesting(String packageName) {
+        int count = 0;
+        for (ChildConnectionAllocator allocator : sSandboxedChildConnectionAllocatorMap.values()) {
+            if (packageName == null || packageName.equals(allocator.getPackageName())) {
+                count += allocator.allocatedConnectionsCountForTesting();
+            }
+        }
+        return count;
+    }
+
+    @VisibleForTesting
+    static boolean hasSandboxedConnectionAllocatorForPackage(String packageName) {
+        return sSandboxedChildConnectionAllocatorMap.containsKey(packageName);
+    }
+
+    @VisibleForTesting
+    ChildProcessConnection getConnection() {
+        return mConnection;
+    }
+
+    @VisibleForTesting
+    public ChildProcessConnection getChildProcessConnection() {
+        return mConnection;
+    }
+
+    @VisibleForTesting
+    public ChildConnectionAllocator getChildConnectionAllocatorForTesting() {
+        return mConnectionAllocator;
+    }
+
+    @VisibleForTesting
+    public static ChildProcessConnection getWarmUpConnectionForTesting() {
+        return sSpareSandboxedConnection == null ? null : sSpareSandboxedConnection.getConnection();
+    }
+
+    @VisibleForTesting
+    public static boolean crashProcessForTesting(int pid) {
+        if (sLauncherByPid.get(pid) == null) return false;
+
+        ChildProcessConnection connection = sLauncherByPid.get(pid).mConnection;
+        if (connection == null) return false;
+
+        try {
+            connection.crashServiceForTesting();
+            return true;
+        } catch (RemoteException ex) {
+            return false;
+        }
     }
 }
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherIntegrationTest.java b/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherIntegrationTest.java
index e797add..9cc917e 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherIntegrationTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherIntegrationTest.java
@@ -114,7 +114,7 @@
     public void testCrossDomainNavigationDoNotLoseImportance() throws Throwable {
         final TestChildProcessConnectionFactory factory = new TestChildProcessConnectionFactory();
         final List<TestChildProcessConnection> connections = factory.getConnections();
-        ChildProcessLauncher.setSandboxServicesSettingsForTesting(factory,
+        ChildProcessLauncherHelper.setSandboxServicesSettingsForTesting(factory,
                 10 /* arbitrary number, only realy need 2 */, null /* use default service name */);
 
         ContentShellActivity activity =
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherTest.java b/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherTest.java
index d22e89e..5698e61 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherTest.java
@@ -8,7 +8,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -34,11 +33,9 @@
 import org.chromium.content.browser.test.ContentJUnit4ClassRunner;
 import org.chromium.content.browser.test.util.Criteria;
 import org.chromium.content.browser.test.util.CriteriaHelper;
-import org.chromium.content.common.ContentSwitches;
 import org.chromium.content_shell_apk.ChildProcessLauncherTestHelperService;
 import org.chromium.content_shell_apk.ChildProcessLauncherTestUtils;
 
-import java.util.Map;
 import java.util.concurrent.Callable;
 
 /**
@@ -55,6 +52,10 @@
     private static final String DEFAULT_SANDBOXED_PROCESS_SERVICE =
             "org.chromium.content.app.SandboxedProcessService";
 
+    private static final int DONT_BLOCK = 0;
+    private static final int BLOCK_UNTIL_CONNECTED = 1;
+    private static final int BLOCK_UNTIL_SETUP = 2;
+
     @Before
     public void setUp() throws Exception {
         LibraryLoader.get(LibraryProcessType.PROCESS_CHILD).ensureInitialized();
@@ -74,30 +75,17 @@
     @Feature({"ProcessManagement"})
     @ChildProcessAllocatorSettings(sandboxedServiceCount = 4)
     public void testServiceFailedToBind() {
-        Assert.assertEquals(0, allocatedChromeSandboxedConnectionsCount());
-        Assert.assertEquals(0, ChildProcessLauncher.connectedServicesCountForTesting());
+        Assert.assertEquals(0, getConnectedSandboxedServicesCount());
 
         // Try to allocate a connection to service class in incorrect package. We can do that by
         // using the instrumentation context (getContext()) instead of the app context
         // (getTargetContext()).
         Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        allocateBoundConnectionForTesting(
-                context, getDefaultChildProcessCreationParams(context.getPackageName()));
+        startSandboxedChildProcess(
+                context.getPackageName(), DONT_BLOCK, true /* doSetupConnection */);
 
         // Verify that the connection is not considered as allocated.
-        CriteriaHelper.pollInstrumentationThread(Criteria.equals(0, new Callable<Integer>() {
-            @Override
-            public Integer call() {
-                return allocatedChromeSandboxedConnectionsCount();
-            }
-        }));
-
-        CriteriaHelper.pollInstrumentationThread(Criteria.equals(0, new Callable<Integer>() {
-            @Override
-            public Integer call() {
-                return ChildProcessLauncher.connectedServicesCountForTesting();
-            }
-        }));
+        waitForConnectedSandboxedServicesCount(0);
     }
 
     /**
@@ -107,34 +95,24 @@
     @MediumTest
     @Feature({"ProcessManagement"})
     public void testServiceCrashedBeforeSetup() throws RemoteException {
-        Assert.assertEquals(0, allocatedChromeSandboxedConnectionsCount());
-        Assert.assertEquals(0, ChildProcessLauncher.connectedServicesCountForTesting());
+        Assert.assertEquals(0, getConnectedSandboxedServicesCount());
 
         // Start and connect to a new service.
-        final ChildProcessConnection connection = startConnection();
-        Assert.assertEquals(1, allocatedChromeSandboxedConnectionsCount());
+        ChildProcessLauncherHelper launcher = startSandboxedChildProcess(
+                null /* packageName */, BLOCK_UNTIL_CONNECTED, false /* doSetupConnection */);
 
-        // Verify that the service is not yet set up.
+        // Verify that the service is bound but not yet set up.
+        Assert.assertEquals(1, getConnectedSandboxedServicesCount());
+        ChildProcessConnection connection = retrieveConnection(launcher);
+        Assert.assertNotNull(connection);
+        Assert.assertTrue(connection.isConnected());
         Assert.assertEquals(0, ChildProcessLauncherTestUtils.getConnectionPid(connection));
-        Assert.assertEquals(0, ChildProcessLauncher.connectedServicesCountForTesting());
 
         // Crash the service.
         connection.crashServiceForTesting();
 
         // Verify that the connection gets cleaned-up.
-        CriteriaHelper.pollInstrumentationThread(Criteria.equals(0, new Callable<Integer>() {
-            @Override
-            public Integer call() {
-                return allocatedChromeSandboxedConnectionsCount();
-            }
-        }));
-
-        CriteriaHelper.pollInstrumentationThread(Criteria.equals(0, new Callable<Integer>() {
-            @Override
-            public Integer call() {
-                return ChildProcessLauncher.connectedServicesCountForTesting();
-            }
-        }));
+        waitForConnectedSandboxedServicesCount(0);
     }
 
     /**
@@ -144,48 +122,21 @@
     @MediumTest
     @Feature({"ProcessManagement"})
     public void testServiceCrashedAfterSetup() throws RemoteException {
-        Assert.assertEquals(0, allocatedChromeSandboxedConnectionsCount());
+        Assert.assertEquals(0, getConnectedSandboxedServicesCount());
 
         // Start and connect to a new service.
-        final ChildProcessConnection connection = startConnection();
-        Assert.assertEquals(1, allocatedChromeSandboxedConnectionsCount());
+        ChildProcessLauncherHelper launcher = startSandboxedChildProcess(
+                null /* packageName */, BLOCK_UNTIL_SETUP, true /* doSetupConnection */);
+        Assert.assertEquals(1, getConnectedSandboxedServicesCount());
 
-        // Initiate the connection setup.
-        triggerConnectionSetup(connection);
-
-        // Verify that the connection completes the setup.
-        CriteriaHelper.pollInstrumentationThread(Criteria.equals(1, new Callable<Integer>() {
-            @Override
-            public Integer call() {
-                return ChildProcessLauncher.connectedServicesCountForTesting();
-            }
-        }));
-
-        CriteriaHelper.pollInstrumentationThread(
-                new Criteria("The connection failed to get a pid in setup.") {
-                    @Override
-                    public boolean isSatisfied() {
-                        return ChildProcessLauncherTestUtils.getConnectionPid(connection) != 0;
-                    }
-                });
+        Assert.assertNotEquals(0, getPid(launcher));
 
         // Crash the service.
+        ChildProcessConnection connection = retrieveConnection(launcher);
         connection.crashServiceForTesting();
 
         // Verify that the connection gets cleaned-up.
-        CriteriaHelper.pollInstrumentationThread(Criteria.equals(0, new Callable<Integer>() {
-            @Override
-            public Integer call() {
-                return allocatedChromeSandboxedConnectionsCount();
-            }
-        }));
-
-        CriteriaHelper.pollInstrumentationThread(Criteria.equals(0, new Callable<Integer>() {
-            @Override
-            public Integer call() {
-                return ChildProcessLauncher.connectedServicesCountForTesting();
-            }
-        }));
+        waitForConnectedSandboxedServicesCount(0);
 
         // Verify that the connection pid remains set after termination.
         Assert.assertTrue(ChildProcessLauncherTestUtils.getConnectionPid(connection) != 0);
@@ -200,54 +151,25 @@
     @Feature({"ProcessManagement"})
     @ChildProcessAllocatorSettings(sandboxedServiceCount = 1)
     public void testPendingSpawnQueue() throws RemoteException {
-        final Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
-        Assert.assertEquals(0, allocatedChromeSandboxedConnectionsCount());
+        Assert.assertEquals(0, getConnectedSandboxedServicesCount());
 
-        ChildProcessCreationParams creationParams =
-                getDefaultChildProcessCreationParams(appContext.getPackageName());
+        ChildProcessLauncherHelper launcher1 = startSandboxedChildProcess(
+                null /* packageName */, BLOCK_UNTIL_SETUP, true /* doSetupConnection */);
+        Assert.assertEquals(1, getConnectedSandboxedServicesCount());
+        Assert.assertNotNull(ChildProcessLauncherTestUtils.getConnection(launcher1));
 
-        final ChildProcessLauncherHelper connectionLauncher1 =
-                ChildProcessLauncherTestUtils.startForTesting(appContext, true /* sandboxed */,
-                        false /* alwaysInForeground */, sProcessWaitArguments,
-                        new FileDescriptorInfo[0], creationParams);
-        Assert.assertTrue(ChildProcessLauncherTestUtils.hasConnection(connectionLauncher1));
-
-        final ChildProcessLauncherHelper connectionLauncher2 =
-                ChildProcessLauncherTestUtils.startForTesting(appContext, true /* sandboxed */,
-                        false /* alwaysInForeground */, sProcessWaitArguments,
-                        new FileDescriptorInfo[0], creationParams);
-        Assert.assertFalse(ChildProcessLauncherTestUtils.hasConnection(connectionLauncher2));
-
-        CriteriaHelper.pollInstrumentationThread(Criteria.equals(true, new Callable<Boolean>() {
-            @Override
-            public Boolean call() {
-                return ChildProcessLauncherTestUtils.getConnection(connectionLauncher1) != null;
-            }
-        }));
+        ChildProcessLauncherHelper launcher2 = startSandboxedChildProcess(
+                null /* packageName */, DONT_BLOCK, true /* doSetupConnection */);
+        Assert.assertEquals(1, getConnectedSandboxedServicesCount());
+        Assert.assertNull(ChildProcessLauncherTestUtils.getConnection(launcher2));
 
         final ChildProcessConnection connection1 =
-                ChildProcessLauncherTestUtils.getConnection(connectionLauncher1);
+                ChildProcessLauncherTestUtils.getConnection(launcher1);
+        connection1.crashServiceForTesting();
 
-        ChildProcessLauncherTestUtils.runOnLauncherThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                try {
-                    connection1.crashServiceForTesting();
-                } catch (RemoteException e) {
-                }
-            }
-        });
-
-        CriteriaHelper.pollInstrumentationThread(Criteria.equals(true, new Callable<Boolean>() {
-            @Override
-            public Boolean call() {
-                return ChildProcessLauncherTestUtils.getConnection(connectionLauncher2) != null;
-            }
-        }));
-
-        final ChildProcessConnection connection2 =
-                ChildProcessLauncherTestUtils.getConnection(connectionLauncher2);
-        Assert.assertNotNull(connection2);
+        // The previous service crashing should have freed a connection that should be used for the
+        // pending process.
+        blockUntilConnected(launcher2);
     }
 
     /**
@@ -293,23 +215,23 @@
             @Override
             public void onServiceDisconnected(ComponentName name) {}
         }
-        final HelperConnection serviceConn = new HelperConnection();
+        final HelperConnection serviceConnection = new HelperConnection();
 
         Intent intent = new Intent();
         intent.setComponent(new ComponentName(context.getPackageName(),
                 context.getPackageName() + ".ChildProcessLauncherTestHelperService"));
-        Assert.assertTrue(context.bindService(intent, serviceConn, Context.BIND_AUTO_CREATE));
+        Assert.assertTrue(context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE));
 
         // Wait for the Helper service to connect.
         CriteriaHelper.pollInstrumentationThread(
                 new Criteria("Failed to get helper service Messenger") {
                     @Override
                     public boolean isSatisfied() {
-                        return serviceConn.mMessenger != null;
+                        return serviceConnection.mMessenger != null;
                     }
                 });
 
-        Assert.assertNotNull(serviceConn.mMessenger);
+        Assert.assertNotNull(serviceConnection.mMessenger);
 
         class ReplyHandler implements Handler.Callback {
             Message mMessage;
@@ -325,11 +247,10 @@
         final ReplyHandler replyHandler = new ReplyHandler();
 
         // Send a message to the Helper and wait for the reply. This will cause the slot 0
-        // sandboxed service connection to be bound by a different PID (i.e., not this
-        // process).
+        // sandboxed service connection to be bound by a different PID (i.e., not this process).
         Message msg = Message.obtain(null, ChildProcessLauncherTestHelperService.MSG_BIND_SERVICE);
         msg.replyTo = new Messenger(new Handler(Looper.getMainLooper(), replyHandler));
-        serviceConn.mMessenger.send(msg);
+        serviceConnection.mMessenger.send(msg);
 
         CriteriaHelper.pollInstrumentationThread(
                 new Criteria("Failed waiting for helper service reply") {
@@ -346,29 +267,29 @@
         Assert.assertEquals(
                 "Connection slot from helper service is not 0", 0, replyHandler.mMessage.arg2);
 
-        final int helperConnPid = replyHandler.mMessage.arg1;
-        Assert.assertTrue(helperConnPid > 0);
+        final int helperConnectionPid = replyHandler.mMessage.arg1;
+        Assert.assertTrue(helperConnectionPid > 0);
 
         // Launch a service from this process. Since slot 0 is already bound by the Helper, it
         // will fail to start and the ChildProcessLauncher will retry and use the slot 1.
-        final ChildProcessCreationParams creationParams = new ChildProcessCreationParams(
+        ChildProcessCreationParams creationParams = new ChildProcessCreationParams(
                 context.getPackageName(), false /* isExternalService */,
                 LibraryProcessType.PROCESS_CHILD, true /* bindToCallerCheck */);
-        final ChildProcessLauncherHelper launcherHelper =
-                ChildProcessLauncherTestUtils.startForTesting(context, true /* sandboxed */,
-                        false /* alwaysInForeground */, sProcessWaitArguments,
-                        new FileDescriptorInfo[0], creationParams);
+        ChildProcessLauncherHelper launcher = startSandboxedChildProcessWithCreationParams(
+                creationParams, BLOCK_UNTIL_SETUP, true /* doSetupConnection */);
 
-        // Retrieve the connection (this waits for the service to connect).
-        final ChildProcessConnection retryConn = retrieveConnection(launcherHelper);
-        Assert.assertEquals(1, ChildProcessLauncherTestUtils.getConnectionServiceNumber(retryConn));
+        final ChildProcessConnection retryConnection =
+                ChildProcessLauncherTestUtils.getConnection(launcher);
+        Assert.assertEquals(
+                1, ChildProcessLauncherTestUtils.getConnectionServiceNumber(retryConnection));
 
-        final ChildProcessConnection[] sandboxedConnections =
-                getSandboxedConnectionArrayForTesting(context, context.getPackageName());
+        ChildConnectionAllocator connectionAllocator =
+                launcher.getChildConnectionAllocatorForTesting();
 
         // Check that only two connections are created.
-        for (int i = 0; i < sandboxedConnections.length; ++i) {
-            ChildProcessConnection sandboxedConn = sandboxedConnections[i];
+        for (int i = 0; i < connectionAllocator.getNumberOfServices(); ++i) {
+            ChildProcessConnection sandboxedConn =
+                    connectionAllocator.getChildProcessConnectionAtSlotForTesting(i);
             if (i <= 1) {
                 Assert.assertNotNull(sandboxedConn);
                 Assert.assertNotNull(
@@ -378,34 +299,38 @@
             }
         }
 
-        Assert.assertTrue(retryConn == sandboxedConnections[1]);
+        Assert.assertEquals(
+                connectionAllocator.getChildProcessConnectionAtSlotForTesting(1), retryConnection);
 
-        ChildProcessConnection conn = sandboxedConnections[0];
-        Assert.assertEquals(0, ChildProcessLauncherTestUtils.getConnectionServiceNumber(conn));
-        Assert.assertEquals(0, ChildProcessLauncherTestUtils.getConnectionPid(conn));
-        Assert.assertFalse(ChildProcessLauncherTestUtils.getConnectionService(conn).bindToCaller());
+        ChildProcessConnection failedConnection =
+                connectionAllocator.getChildProcessConnectionAtSlotForTesting(0);
+        Assert.assertEquals(0, ChildProcessLauncherTestUtils.getConnectionPid(failedConnection));
+        Assert.assertFalse(ChildProcessLauncherTestUtils.getConnectionService(failedConnection)
+                                   .bindToCaller());
 
-        Assert.assertEquals(1, ChildProcessLauncherTestUtils.getConnectionServiceNumber(retryConn));
         CriteriaHelper.pollInstrumentationThread(
                 new Criteria("Failed waiting retry connection to get pid") {
                     @Override
                     public boolean isSatisfied() {
-                        return ChildProcessLauncherTestUtils.getConnectionPid(retryConn) > 0;
+                        return ChildProcessLauncherTestUtils.getConnectionPid(retryConnection) > 0;
                     }
                 });
+        Assert.assertTrue(ChildProcessLauncherTestUtils.getConnectionPid(retryConnection)
+                != helperConnectionPid);
         Assert.assertTrue(
-                ChildProcessLauncherTestUtils.getConnectionPid(retryConn) != helperConnPid);
-        Assert.assertTrue(
-                ChildProcessLauncherTestUtils.getConnectionService(retryConn).bindToCaller());
+                ChildProcessLauncherTestUtils.getConnectionService(retryConnection).bindToCaller());
     }
 
     private static void warmUpOnUiThreadBlocking(final Context context) {
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                ChildProcessLauncher.warmUp(context);
+                ChildProcessLauncherHelper.warmUp(context);
             }
         });
+        ChildProcessConnection connection = getWarmUpConnection();
+        Assert.assertNotNull(connection);
+        blockUntilConnected(connection);
     }
 
     private void testWarmUpWithCreationParams(ChildProcessCreationParams creationParams) {
@@ -416,31 +341,17 @@
         Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
         warmUpOnUiThreadBlocking(context);
 
-        Assert.assertEquals(1, allocatedChromeSandboxedConnectionsCount());
+        Assert.assertEquals(1, getConnectedSandboxedServicesCount());
 
-        ChildProcessLauncherHelper launcherHelper = ChildProcessLauncherTestUtils.startForTesting(
-                context, true /* sandboxed */, false /* alwaysInForeground */, new String[0],
-                new FileDescriptorInfo[0], creationParams /* creationParams */);
+        ChildProcessLauncherHelper launcherHelper = startSandboxedChildProcessWithCreationParams(
+                creationParams, BLOCK_UNTIL_SETUP, true /* doSetupConnection */);
 
-        final ChildProcessConnection conn = retrieveConnection(launcherHelper);
+        // The warm-up connection was used, so no new process should have been created.
+        Assert.assertEquals(1, getConnectedSandboxedServicesCount());
 
-        Assert.assertEquals(
-                1, allocatedChromeSandboxedConnectionsCount()); // Used warmup connection.
+        stopProcess(launcherHelper);
 
-        ChildProcessLauncherTestUtils.runOnLauncherThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                ChildProcessLauncher.stop(ChildProcessLauncherTestUtils.getConnectionPid(conn));
-            }
-        });
-
-        CriteriaHelper.pollInstrumentationThread(
-                new Criteria("Failed waiting for connection to be freed") {
-                    @Override
-                    public boolean isSatisfied() {
-                        return allocatedChromeSandboxedConnectionsCount() == 0;
-                    }
-                });
+        waitForConnectedSandboxedServicesCount(0);
     }
 
     @Test
@@ -467,70 +378,51 @@
     @MediumTest
     @Feature({"ProcessManagement"})
     public void testWarmUpProcessCrash() throws RemoteException {
+        Assert.assertEquals(0, getConnectedSandboxedServicesCount());
+
         Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
         warmUpOnUiThreadBlocking(context);
 
-        Assert.assertEquals(1, allocatedChromeSandboxedConnectionsCount());
+        Assert.assertEquals(1, getConnectedSandboxedServicesCount());
 
-        final ChildProcessConnection warmupConnection = getWarmUpConnection();
-        Assert.assertNotNull(warmupConnection);
+        // Crash the warm-up connection before it gets used.
+        ChildProcessConnection connection = getWarmUpConnection();
+        Assert.assertNotNull(connection);
+        connection.crashServiceForTesting();
 
-        waitForConnectionToConnect(warmupConnection);
+        // It should get cleaned-up.
+        waitForConnectedSandboxedServicesCount(0);
 
-        warmupConnection.crashServiceForTesting();
-
-        CriteriaHelper.pollInstrumentationThread(
-                new Criteria("Failed waiting for connection to be freed") {
-                    @Override
-                    public boolean isSatisfied() {
-                        return allocatedChromeSandboxedConnectionsCount() == 0;
-                    }
-                });
+        // And subsequent process launches should work.
+        ChildProcessLauncherHelper launcher = startSandboxedChildProcess(
+                null /* packageName */, BLOCK_UNTIL_SETUP, true /* doSetupConnection */);
+        Assert.assertEquals(1, getConnectedSandboxedServicesCount());
+        Assert.assertNotNull(ChildProcessLauncherTestUtils.getConnection(launcher));
     }
 
     @Test
     @MediumTest
     @Feature({"ProcessManagement"})
     public void testSandboxedAllocatorFreed() {
-        final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
-        ChildProcessLauncherHelper launcherHelper = ChildProcessLauncherTestUtils.startForTesting(
-                context, true /* sandboxed */, false /* alwaysInForeground */, new String[0],
-                new FileDescriptorInfo[0], null);
-        ChildProcessLauncherTestUtils.runOnLauncherThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                Map<String, ChildConnectionAllocator> allocatorMap =
-                        ChildProcessLauncher.getSandboxedAllocatorMapForTesting();
-                Assert.assertTrue(allocatorMap.containsKey(context.getPackageName()));
-            }
-        });
+        final String packageName =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName();
 
-        final ChildProcessConnection conn = retrieveConnection(launcherHelper);
-        ChildProcessLauncherTestUtils.runOnLauncherThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                ChildProcessLauncher.stop(ChildProcessLauncherTestUtils.getConnectionPid(conn));
-            }
-        });
+        ChildProcessLauncherHelper launcher = startSandboxedChildProcess(
+                null /* packageName */, BLOCK_UNTIL_SETUP, true /* doSetupConnection */);
+
+        Assert.assertTrue(hasSandboxedConnectionAllocatorForPackage(packageName));
+
+        stopProcess(launcher);
 
         // Poll until allocator is removed. Need to poll here because actually freeing a connection
         // from allocator is a posted task, rather than a direct call from stop.
         CriteriaHelper.pollInstrumentationThread(
-                Criteria.equals(Boolean.FALSE, new Callable<Boolean>() {
+                new Criteria("The connection allocator was not removed.") {
                     @Override
-                    public Boolean call() {
-                        return ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(
-                                new Callable<Boolean>() {
-                                    @Override
-                                    public Boolean call() {
-                                        Map<String, ChildConnectionAllocator> allocatorMap =
-                                                ChildProcessLauncher
-                                                        .getSandboxedAllocatorMapForTesting();
-                                        return allocatorMap.containsKey(context.getPackageName());
-                                    }
-                                });
+                    public boolean isSatisfied() {
+                        return !hasSandboxedConnectionAllocatorForPackage(packageName);
                     }
-                }));
+                });
     }
 
     @Test
@@ -539,69 +431,131 @@
     public void testCustomCreationParamDoesNotReuseWarmupConnection() {
         // Since warmUp only uses default params.
         final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
-        // Check uses object identity, having the params match exactly is fine.
-        ChildProcessCreationParams.registerDefault(
-                getDefaultChildProcessCreationParams(context.getPackageName()));
-        final int paramId = ChildProcessCreationParams.register(
-                getDefaultChildProcessCreationParams(context.getPackageName()));
+        ChildProcessCreationParams defaultCreationParams =
+                getDefaultChildProcessCreationParams(context.getPackageName());
+        ChildProcessCreationParams otherCreationParams =
+                getDefaultChildProcessCreationParams(context.getPackageName());
+        ChildProcessCreationParams.registerDefault(defaultCreationParams);
 
         warmUpOnUiThreadBlocking(context);
-        ChildProcessLauncherTestUtils.runOnLauncherThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                Assert.assertEquals(1, allocatedChromeSandboxedConnectionsCount());
+        Assert.assertEquals(1, getConnectedSandboxedServicesCount());
 
-                startRendererProcess(context, paramId, new FileDescriptorInfo[0]);
-                Assert.assertEquals(
-                        2, allocatedChromeSandboxedConnectionsCount()); // Warmup not used.
+        // First create a connnection with different creation params then the default. It should not
+        // use the warm-up connection which uses the default creation params (check uses object
+        // identity, having the params match exactly is fine).
+        startSandboxedChildProcessWithCreationParams(
+                otherCreationParams, BLOCK_UNTIL_SETUP, true /* doSetupConnection */);
+        Assert.assertEquals(2, getConnectedSandboxedServicesCount());
+        Assert.assertNotNull(getWarmUpConnection());
 
-                startRendererProcess(
-                        context, ChildProcessCreationParams.DEFAULT_ID, new FileDescriptorInfo[0]);
-                Assert.assertEquals(2, allocatedChromeSandboxedConnectionsCount()); // Warmup used.
-
-                ChildProcessCreationParams.unregister(paramId);
-            }
-        });
+        // Then start a process with the default creation params, the warmup-connection should be
+        // used.
+        startSandboxedChildProcessWithCreationParams(
+                defaultCreationParams, BLOCK_UNTIL_SETUP, true /* doSetupConnection */);
+        Assert.assertEquals(2, getConnectedSandboxedServicesCount());
+        Assert.assertNull(getWarmUpConnection());
     }
 
     @Test
     @MediumTest
     @Feature({"ProcessManagement"})
-    public void testAlwaysInForegroundConnection() {
+    public void testUseStrongBindingConnection() {
         // Since warmUp only uses default params.
         Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
         ChildProcessCreationParams creationParams = new ChildProcessCreationParams(
                 context.getPackageName(), false /* isExternalService */,
                 LibraryProcessType.PROCESS_CHILD, true /* bindToCallerCheck */);
 
-        for (final boolean alwaysInForeground : new boolean[] {true, false}) {
-            ChildProcessLauncherHelper launcherHelper =
-                    ChildProcessLauncherTestUtils.startForTesting(context, false /* sandboxed */,
-                            alwaysInForeground, sProcessWaitArguments, new FileDescriptorInfo[0],
-                            creationParams);
-            final ChildProcessConnection connection = retrieveConnection(launcherHelper);
+        for (final boolean useStrongBinding : new boolean[] {true, false}) {
+            ChildProcessLauncherHelper launcher = ChildProcessLauncherTestUtils.startForTesting(
+                    false /* sandboxed */, useStrongBinding, sProcessWaitArguments,
+                    new FileDescriptorInfo[0], creationParams, true /* doSetupConnection */);
+            final ChildProcessConnection connection =
+                    ChildProcessLauncherTestUtils.getConnection(launcher);
             Assert.assertNotNull(connection);
             ChildProcessLauncherTestUtils.runOnLauncherThreadBlocking(new Runnable() {
                 @Override
                 public void run() {
-                    Assert.assertEquals(alwaysInForeground, connection.isStrongBindingBound());
+                    Assert.assertEquals(useStrongBinding, connection.isStrongBindingBound());
                 }
             });
         }
     }
 
-    private ChildProcessConnection startConnection() {
-        // Allocate a new connection.
-        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
-        final ChildProcessConnection connection = allocateBoundConnectionForTesting(
-                context, getDefaultChildProcessCreationParams(context.getPackageName()));
+    @Test
+    @MediumTest
+    @Feature({"ProcessManagement"})
+    public void testLauncherCleanup() throws RemoteException {
+        ChildProcessLauncherHelper launcher = startSandboxedChildProcess(
+                null /* packageName */, BLOCK_UNTIL_SETUP, true /* doSetupConnection */);
+        int pid = getPid(launcher);
+        Assert.assertNotEquals(0, pid);
+        Assert.assertNotNull(ChildProcessLauncherHelper.getLauncherForPid(pid));
 
-        // Wait for the service to connect.
-        waitForConnectionToConnect(connection);
-        return connection;
+        // Stop the process explicitly, the launcher should get cleared.
+        stopProcess(launcher);
+        waitForConnectedSandboxedServicesCount(0);
+        Assert.assertNull(ChildProcessLauncherHelper.getLauncherForPid(pid));
+
+        launcher = startSandboxedChildProcess(
+                null /* packageName */, BLOCK_UNTIL_SETUP, true /* doSetupConnection */);
+        pid = getPid(launcher);
+        Assert.assertNotEquals(0, pid);
+        Assert.assertNotNull(ChildProcessLauncherHelper.getLauncherForPid(pid));
+
+        // This time crash the connection, the launcher should also get cleared.
+        ChildProcessConnection connection = retrieveConnection(launcher);
+        connection.crashServiceForTesting();
+        waitForConnectedSandboxedServicesCount(0);
+        Assert.assertNull(ChildProcessLauncherHelper.getLauncherForPid(pid));
     }
 
-    private void waitForConnectionToConnect(final ChildProcessConnection connection) {
+    private static ChildProcessLauncherHelper startSandboxedChildProcess(
+            final String packageName, int blockingPolicy, final boolean doSetupConnection) {
+        ChildProcessCreationParams creationParams =
+                packageName == null ? null : getDefaultChildProcessCreationParams(packageName);
+        return startSandboxedChildProcessWithCreationParams(
+                creationParams, blockingPolicy, doSetupConnection);
+    }
+
+    private static ChildProcessLauncherHelper startSandboxedChildProcessWithCreationParams(
+            final ChildProcessCreationParams creationParams, int blockingPolicy,
+            final boolean doSetupConnection) {
+        assert doSetupConnection || blockingPolicy != BLOCK_UNTIL_SETUP;
+        ChildProcessLauncherHelper launcher =
+                ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(
+                        new Callable<ChildProcessLauncherHelper>() {
+                            @Override
+                            public ChildProcessLauncherHelper call() {
+                                return ChildProcessLauncherHelper.createAndStartForTesting(
+                                        creationParams, sProcessWaitArguments,
+                                        new FileDescriptorInfo[0], false /* useStrongBinding */,
+                                        true /* sandboxed */, null /* binderCallback */,
+                                        doSetupConnection);
+                            }
+                        });
+        if (blockingPolicy != DONT_BLOCK) {
+            assert blockingPolicy == BLOCK_UNTIL_CONNECTED || blockingPolicy == BLOCK_UNTIL_SETUP;
+            blockUntilConnected(launcher);
+            if (blockingPolicy == BLOCK_UNTIL_SETUP) {
+                blockUntilSetup(launcher);
+            }
+        }
+        return launcher;
+    }
+
+    private static void blockUntilConnected(final ChildProcessLauncherHelper launcher) {
+        CriteriaHelper.pollInstrumentationThread(
+                new Criteria("The connection wasn't established.") {
+                    @Override
+                    public boolean isSatisfied() {
+                        return launcher.getConnection() != null
+                                && launcher.getConnection().isConnected();
+                    }
+                });
+    }
+
+    private static void blockUntilConnected(final ChildProcessConnection connection) {
         CriteriaHelper.pollInstrumentationThread(
                 new Criteria("The connection wasn't established.") {
                     @Override
@@ -611,29 +565,14 @@
                 });
     }
 
-    private ChildProcessConnection startChildService() {
-        // Allocate a new connection.
-        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
-        final ChildProcessConnection connection = allocateBoundConnectionForTesting(
-                context, getDefaultChildProcessCreationParams(context.getPackageName()));
-
-        // Wait for the service to connect.
+    private static void blockUntilSetup(final ChildProcessLauncherHelper launcher) {
         CriteriaHelper.pollInstrumentationThread(
                 new Criteria("The connection wasn't established.") {
                     @Override
                     public boolean isSatisfied() {
-                        return connection.isConnected();
+                        return getPid(launcher) != 0;
                     }
                 });
-        return connection;
-    }
-
-    private static void startRendererProcess(
-            Context context, int paramId, FileDescriptorInfo[] filesToMap) {
-        ChildProcessLauncherHelper.createAndStart(0L, paramId,
-                new String[] {"--" + ContentSwitches.SWITCH_PROCESS_TYPE + "="
-                        + ContentSwitches.SWITCH_RENDERER_PROCESS},
-                filesToMap);
     }
 
     private static ChildConnectionAllocator getChildConnectionAllocator(
@@ -642,66 +581,39 @@
                 new Callable<ChildConnectionAllocator>() {
                     @Override
                     public ChildConnectionAllocator call() {
-                        return ChildProcessLauncher.getConnectionAllocator(context,
+                        return ChildProcessLauncherHelper.getConnectionAllocator(context,
                                 getDefaultChildProcessCreationParams(packageName), sandboxed);
                     }
                 });
     }
 
-    private static ChildProcessConnection allocateBoundConnectionForTesting(
-            final Context context, final ChildProcessCreationParams creationParams) {
-        return ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(
-                new Callable<ChildProcessConnection>() {
-                    @Override
-                    public ChildProcessConnection call() {
-                        return ChildProcessLauncher.allocateSandboxedBoundConnectionForTesting(
-                                context, creationParams);
-                    }
-                });
+    // Returns the number of sandboxed connection currently connected,
+    private static int getConnectedSandboxedServicesCount() {
+        return getConnectedSandboxedServicesCountForPackage(null /* packageName */);
     }
 
-    private static int allocatedSandboxedConnectionsCountForTesting(
-            final Context context, final String packageName) {
+    // Returns the number of sandboxed connection matching the specificed package name that are
+    // connected,
+    private static int getConnectedSandboxedServicesCountForPackage(final String packageName) {
         return ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(
                 new Callable<Integer>() {
                     @Override
                     public Integer call() {
-                        ChildProcessCreationParams creationParams = null;
-                        if (packageName != null) {
-                            // The creationParams is just used for the package name.
-                            creationParams = new ChildProcessCreationParams(packageName,
-                                    true /* isExternalService */, 0 /* libraryProcessType */,
-                                    false /* bindToCallerCheck */);
-                        }
-                        return ChildProcessLauncher
-                                .getConnectionAllocator(
-                                        context, creationParams, true /* isSandboxed */)
-                                .allocatedConnectionsCountForTesting();
+                        return ChildProcessLauncherHelper
+                                .getConnectedSandboxedServicesCountForTesting(packageName);
                     }
                 });
     }
 
-    private static ChildProcessConnection[] getSandboxedConnectionArrayForTesting(
-            final Context context, final String packageName) {
-        return ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(
-                new Callable<ChildProcessConnection[]>() {
+    // Blocks until the number of sandboxed connections reaches targetCount.
+    private static void waitForConnectedSandboxedServicesCount(int targetCount) {
+        CriteriaHelper.pollInstrumentationThread(
+                Criteria.equals(targetCount, new Callable<Integer>() {
                     @Override
-                    public ChildProcessConnection[] call() {
-                        return ChildProcessLauncher
-                                .getConnectionAllocator(context,
-                                        getDefaultChildProcessCreationParams(packageName),
-                                        true /*isSandboxed */)
-                                .connectionArrayForTesting();
+                    public Integer call() {
+                        return getConnectedSandboxedServicesCountForPackage(null /* packageName */);
                     }
-                });
-    }
-
-    /**
-     * Returns the number of Chrome's sandboxed connections.
-     */
-    private int allocatedChromeSandboxedConnectionsCount() {
-        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
-        return allocatedSandboxedConnectionsCountForTesting(context, null /* packageName */);
+                }));
     }
 
     private static ChildProcessCreationParams getDefaultChildProcessCreationParams(
@@ -712,15 +624,12 @@
                           LibraryProcessType.PROCESS_CHILD, false /* bindToCallerCheck */);
     }
 
-    private void triggerConnectionSetup(final ChildProcessConnection connection) {
-        ChildProcessLauncherTestUtils.runOnLauncherThreadBlocking(new Runnable() {
+    private static boolean hasSandboxedConnectionAllocatorForPackage(final String packageName) {
+        return ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(new Callable<Boolean>() {
             @Override
-            public void run() {
-                Bundle connectionBundle = ChildProcessLauncherHelper.createConnectionBundle(
-                        sProcessWaitArguments, new FileDescriptorInfo[0]);
-                ChildProcessLauncher.setupConnection(connection, connectionBundle,
-                        null /* launchCallback */, null /* childProcessCallback */,
-                        true /* addToBindingmanager */);
+            public Boolean call() {
+                return ChildProcessLauncherHelper.hasSandboxedConnectionAllocatorForPackage(
+                        packageName);
             }
         });
     }
@@ -738,12 +647,31 @@
         return ChildProcessLauncherTestUtils.getConnection(launcherHelper);
     }
 
+    private static void stopProcess(ChildProcessLauncherHelper launcherHelper) {
+        final ChildProcessConnection connection = retrieveConnection(launcherHelper);
+        ChildProcessLauncherTestUtils.runOnLauncherThreadBlocking(new Runnable() {
+            @Override
+            public void run() {
+                ChildProcessLauncherHelper.stop(connection.getPid());
+            }
+        });
+    }
+
+    private static int getPid(final ChildProcessLauncherHelper launcherHelper) {
+        return ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(new Callable<Integer>() {
+            @Override
+            public Integer call() {
+                return launcherHelper.getPid();
+            }
+        });
+    }
+
     private static ChildProcessConnection getWarmUpConnection() {
         return ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(
                 new Callable<ChildProcessConnection>() {
                     @Override
                     public ChildProcessConnection call() {
-                        return ChildProcessLauncher.getWarmUpConnectionForTesting();
+                        return ChildProcessLauncherHelper.getWarmUpConnectionForTesting();
                     }
                 });
     }
diff --git a/content/public/test/android/javatests/src/org/chromium/content/browser/test/ChildProcessAllocatorSettingsHook.java b/content/public/test/android/javatests/src/org/chromium/content/browser/test/ChildProcessAllocatorSettingsHook.java
index b6b82de..083278c 100644
--- a/content/public/test/android/javatests/src/org/chromium/content/browser/test/ChildProcessAllocatorSettingsHook.java
+++ b/content/public/test/android/javatests/src/org/chromium/content/browser/test/ChildProcessAllocatorSettingsHook.java
@@ -7,7 +7,7 @@
 import android.content.Context;
 
 import org.chromium.base.test.BaseTestResult.PreTestHook;
-import org.chromium.content.browser.ChildProcessLauncher;
+import org.chromium.content.browser.ChildProcessLauncherHelper;
 
 import java.lang.reflect.Method;
 
@@ -22,7 +22,7 @@
         ChildProcessAllocatorSettings annotation =
                 testMethod.getAnnotation(ChildProcessAllocatorSettings.class);
         if (annotation != null) {
-            ChildProcessLauncher.setSandboxServicesSettingsForTesting(null /* factory */,
+            ChildProcessLauncherHelper.setSandboxServicesSettingsForTesting(null /* factory */,
                     annotation.sandboxedServiceCount(), annotation.sandboxedServiceName());
         }
     }
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 398c49a8..98d81103 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -1834,7 +1834,7 @@
   // See https://crbug.com/569683 for details.
   in_browser_initiated_detach_ = true;
 
-  // This will result in a call to RendeFrameImpl::frameDetached, which
+  // This will result in a call to RenderFrameImpl::frameDetached, which
   // deletes the object. Do not access |this| after detach.
   frame_->Detach();
 }
@@ -2950,6 +2950,11 @@
       ChildThreadImpl::current()->thread_safe_sender(), provider->context());
 }
 
+service_manager::InterfaceProvider* RenderFrameImpl::GetInterfaceProvider() {
+  DCHECK(remote_interfaces_);
+  return remote_interfaces_.get();
+}
+
 void RenderFrameImpl::DidAccessInitialDocument() {
   DCHECK(!frame_->Parent());
   // NOTE: Do not call back into JavaScript here, since this call is made from a
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index 81015a3f..60b1cd5f 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -499,6 +499,7 @@
   blink::BlameContext* GetFrameBlameContext() override;
   std::unique_ptr<blink::WebServiceWorkerProvider> CreateServiceWorkerProvider()
       override;
+  service_manager::InterfaceProvider* GetInterfaceProvider() override;
   void DidAccessInitialDocument() override;
   blink::WebLocalFrame* CreateChildFrame(
       blink::WebLocalFrame* parent,
diff --git a/content/shell/BUILD.gn b/content/shell/BUILD.gn
index 7ed4121..2f2d607 100644
--- a/content/shell/BUILD.gn
+++ b/content/shell/BUILD.gn
@@ -232,6 +232,7 @@
     "//content/shell/test_runner:test_runner",
     "//content/test:content_test_mojo_bindings",
     "//content/test:layouttest_support",
+    "//content/test:mojo_layouttest_bindings",
     "//content/test:test_support",
     "//device/bluetooth",
     "//device/bluetooth:fake_bluetooth",
diff --git a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ChildProcessLauncherTestHelperService.java b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ChildProcessLauncherTestHelperService.java
index 1e6b6e55..370d145 100644
--- a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ChildProcessLauncherTestHelperService.java
+++ b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ChildProcessLauncherTestHelperService.java
@@ -72,9 +72,9 @@
         ChildProcessCreationParams params = new ChildProcessCreationParams(
                 getPackageName(), false, LibraryProcessType.PROCESS_CHILD, bindToCaller);
         final ChildProcessLauncherHelper processLauncher =
-                ChildProcessLauncherTestUtils.startForTesting(this, true /* sandboxed */,
-                        false /* alwaysInForeground */, commandLine, new FileDescriptorInfo[0],
-                        params);
+                ChildProcessLauncherTestUtils.startForTesting(true /* sandboxed */,
+                        false /* useStrongBinding */, commandLine, new FileDescriptorInfo[0],
+                        params, true /* doSetupConnection */);
 
         // Poll the launcher until the connection is set up. The main test in
         // ChildProcessLauncherTest, which has bound the connection to this service, manages the
@@ -85,18 +85,21 @@
 
             @Override
             public void run() {
+                int pid = 0;
                 ChildProcessConnection conn = processLauncher.getChildProcessConnection();
                 if (conn != null) {
-                    int pid = ChildProcessLauncherTestUtils.getConnectionPid(conn);
-                    assert pid != 0;
-                    try {
-                        mReplyTo.send(Message.obtain(null, MSG_BIND_SERVICE_REPLY, pid,
-                                ChildProcessLauncherTestUtils.getConnectionServiceNumber(conn)));
-                    } catch (RemoteException ex) {
-                        throw new RuntimeException(ex);
-                    }
-                } else {
+                    pid = ChildProcessLauncherTestUtils.getConnectionPid(conn);
+                }
+                if (pid == 0) {
                     handler.postDelayed(this, 10 /* milliseconds */);
+                    return;
+                }
+
+                try {
+                    mReplyTo.send(Message.obtain(null, MSG_BIND_SERVICE_REPLY, pid,
+                            ChildProcessLauncherTestUtils.getConnectionServiceNumber(conn)));
+                } catch (RemoteException ex) {
+                    throw new RuntimeException(ex);
                 }
             }
         };
diff --git a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ChildProcessLauncherTestUtils.java b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ChildProcessLauncherTestUtils.java
index 4519988..51a3b47 100644
--- a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ChildProcessLauncherTestUtils.java
+++ b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ChildProcessLauncherTestUtils.java
@@ -4,8 +4,6 @@
 
 package org.chromium.content_shell_apk;
 
-import android.content.Context;
-
 import org.chromium.base.process_launcher.ChildProcessCreationParams;
 import org.chromium.base.process_launcher.FileDescriptorInfo;
 import org.chromium.base.process_launcher.IChildProcessService;
@@ -55,23 +53,16 @@
         }
     }
 
-    public static ChildProcessLauncherHelper startForTesting(final Context context,
-            final boolean sandboxed, final boolean alwaysInForeground, final String[] commandLine,
-            final FileDescriptorInfo[] filesToBeMapped, final ChildProcessCreationParams params) {
+    public static ChildProcessLauncherHelper startForTesting(final boolean sandboxed,
+            final boolean useStrongBinding, final String[] commandLine,
+            final FileDescriptorInfo[] filesToBeMapped, final ChildProcessCreationParams params,
+            final boolean doSetupConnection) {
         return runOnLauncherAndGetResult(new Callable<ChildProcessLauncherHelper>() {
             @Override
             public ChildProcessLauncherHelper call() {
-                return ChildProcessLauncherHelper.createAndStartForTesting(0L /* nativePointer */,
-                        commandLine, filesToBeMapped, params, sandboxed, alwaysInForeground);
-            }
-        });
-    }
-
-    public static boolean hasConnection(final ChildProcessLauncherHelper childProcessLauncher) {
-        return runOnLauncherAndGetResult(new Callable<Boolean>() {
-            @Override
-            public Boolean call() {
-                return childProcessLauncher.hasConnection();
+                return ChildProcessLauncherHelper.createAndStartForTesting(params, commandLine,
+                        filesToBeMapped, useStrongBinding, sandboxed, null /* binderCallback */,
+                        doSetupConnection);
             }
         });
     }
diff --git a/content/shell/browser/content_shell_browser_manifest_overlay.json b/content/shell/browser/content_shell_browser_manifest_overlay.json
index 3e5d67c..2697e0d 100644
--- a/content/shell/browser/content_shell_browser_manifest_overlay.json
+++ b/content/shell/browser/content_shell_browser_manifest_overlay.json
@@ -8,6 +8,13 @@
           "bluetooth::mojom::FakeBluetooth"
         ]
       }
+    },
+    "navigation:frame": {
+      "provides": {
+        "renderer": [
+          "content::mojom::MojoLayoutTestHelper"
+        ]
+      }
     }
   }
 }
diff --git a/content/shell/browser/shell_content_browser_client.cc b/content/shell/browser/shell_content_browser_client.cc
index 0aeb7e2..86dcf15 100644
--- a/content/shell/browser/shell_content_browser_client.cc
+++ b/content/shell/browser/shell_content_browser_client.cc
@@ -13,6 +13,7 @@
 #include "base/files/file_util.h"
 #include "base/json/json_reader.h"
 #include "base/macros.h"
+#include "base/memory/ptr_util.h"
 #include "base/path_service.h"
 #include "base/strings/pattern.h"
 #include "base/strings/utf_string_conversions.h"
@@ -38,8 +39,11 @@
 #include "content/shell/common/shell_messages.h"
 #include "content/shell/common/shell_switches.h"
 #include "content/shell/grit/shell_resources.h"
+#include "content/test/data/mojo_layouttest_test.mojom.h"
 #include "media/mojo/features.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
 #include "net/url_request/url_request_context_getter.h"
+#include "services/service_manager/public/cpp/bind_source_info.h"
 #include "storage/browser/quota/quota_settings.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "url/gurl.h"
@@ -123,6 +127,26 @@
 }
 #endif  // defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
 
+class MojoLayoutTestHelper : public mojom::MojoLayoutTestHelper {
+ public:
+  MojoLayoutTestHelper() {}
+  ~MojoLayoutTestHelper() override {}
+
+  // mojom::MojoLayoutTestHelper:
+  void Reverse(const std::string& message, ReverseCallback callback) override {
+    std::move(callback).Run(std::string(message.rbegin(), message.rend()));
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MojoLayoutTestHelper);
+};
+
+void BindLayoutTestHelper(const service_manager::BindSourceInfo& source_info,
+                          mojom::MojoLayoutTestHelperRequest request) {
+  mojo::MakeStrongBinding(base::MakeUnique<MojoLayoutTestHelper>(),
+                          std::move(request));
+}
+
 }  // namespace
 
 ShellContentBrowserClient* ShellContentBrowserClient::Get() {
@@ -192,6 +216,12 @@
   return false;
 }
 
+void ShellContentBrowserClient::ExposeInterfacesToFrame(
+    service_manager::BinderRegistry* registry,
+    RenderFrameHost* frame_host) {
+  registry->AddInterface(base::Bind(&BindLayoutTestHelper));
+}
+
 void ShellContentBrowserClient::RegisterInProcessServices(
     StaticServiceMap* services) {
 #if BUILDFLAG(ENABLE_MOJO_MEDIA_IN_BROWSER_PROCESS)
diff --git a/content/shell/browser/shell_content_browser_client.h b/content/shell/browser/shell_content_browser_client.h
index f254b164..efbd133 100644
--- a/content/shell/browser/shell_content_browser_client.h
+++ b/content/shell/browser/shell_content_browser_client.h
@@ -36,6 +36,8 @@
   bool DoesSiteRequireDedicatedProcess(BrowserContext* browser_context,
                                        const GURL& effective_site_url) override;
   bool IsHandledURL(const GURL& url) override;
+  void ExposeInterfacesToFrame(service_manager::BinderRegistry* registry,
+                               RenderFrameHost* frame_host) override;
   void RegisterInProcessServices(StaticServiceMap* services) override;
   void RegisterOutOfProcessServices(OutOfProcessServiceMap* services) override;
   std::unique_ptr<base::Value> GetServiceManifestOverlay(
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 896e91b..9f84aae 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -478,6 +478,13 @@
   use_new_js_bindings = false
 }
 
+mojom("mojo_layouttest_bindings") {
+  testonly = true
+  sources = [
+    "data/mojo_layouttest_test.mojom",
+  ]
+}
+
 static_library("layouttest_support") {
   testonly = true
 
diff --git a/content/test/data/OWNERS b/content/test/data/OWNERS
index f59ec20..6bcff427 100644
--- a/content/test/data/OWNERS
+++ b/content/test/data/OWNERS
@@ -1 +1,4 @@
-*
\ No newline at end of file
+*
+
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
diff --git a/content/test/data/mojo_layouttest_test.mojom b/content/test/data/mojo_layouttest_test.mojom
new file mode 100644
index 0000000..4f6ec84a
--- /dev/null
+++ b/content/test/data/mojo_layouttest_test.mojom
@@ -0,0 +1,10 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module content.mojom;
+
+interface MojoLayoutTestHelper {
+  Reverse(string message) => (string reversed);
+};
+
diff --git a/content/test/gpu/gpu_tests/context_lost_integration_test.py b/content/test/gpu/gpu_tests/context_lost_integration_test.py
index 98dabb1..935537e1 100644
--- a/content/test/gpu/gpu_tests/context_lost_integration_test.py
+++ b/content/test/gpu/gpu_tests/context_lost_integration_test.py
@@ -58,15 +58,14 @@
   def Name(cls):
     return 'context_lost'
 
-  @classmethod
-  def CustomizeOptions(cls):
-    options = cls._finder_options.browser_options
-    options.AppendExtraBrowserArgs(
-        '--disable-domain-blocking-for-3d-apis')
-    options.AppendExtraBrowserArgs(
-        '--disable-gpu-process-crash-limit')
-    # Required for about:gpucrash handling from Telemetry.
-    options.AppendExtraBrowserArgs('--enable-gpu-benchmarking')
+  @staticmethod
+  def _AddDefaultArgs(browser_args):
+    # These are options specified for every test.
+    return [
+      '--disable-domain-blocking-for-3d-apis',
+      '--disable-gpu-process-crash-limit',
+      # Required for about:gpucrash handling from Telemetry.
+      '--enable-gpu-benchmarking'] + browser_args
 
   @classmethod
   def GenerateGpuTests(cls, options):
@@ -98,9 +97,8 @@
 
   @classmethod
   def SetUpProcess(cls):
-    super(cls, ContextLostIntegrationTest).SetUpProcess()
-    cls.CustomizeOptions()
-    cls.SetBrowserOptions(cls._finder_options)
+    super(ContextLostIntegrationTest, cls).SetUpProcess()
+    cls.CustomizeBrowserArgs(cls._AddDefaultArgs([]))
     cls.StartBrowser()
     cls.SetStaticServerDirs([data_path])
 
diff --git a/content/test/gpu/gpu_tests/depth_capture_integration_test.py b/content/test/gpu/gpu_tests/depth_capture_integration_test.py
index 00e9359..cdb9c39 100644
--- a/content/test/gpu/gpu_tests/depth_capture_integration_test.py
+++ b/content/test/gpu/gpu_tests/depth_capture_integration_test.py
@@ -52,17 +52,6 @@
     return 'depth_capture'
 
   @classmethod
-  def CustomizeOptions(cls):
-    options = cls._finder_options.browser_options
-    options.AppendExtraBrowserArgs('--disable-domain-blocking-for-3d-apis')
-    options.AppendExtraBrowserArgs('--enable-es3-apis')
-    options.AppendExtraBrowserArgs('--use-fake-ui-for-media-stream')
-    options.AppendExtraBrowserArgs(
-        '--use-fake-device-for-media-stream=device-count=2')
-    # Required for about:gpucrash handling from Telemetry.
-    options.AppendExtraBrowserArgs('--enable-gpu-benchmarking')
-
-  @classmethod
   def GenerateGpuTests(cls, options):
     tests = (('DepthCapture_depthStreamToRGBAUint8Texture',
               'getusermedia-depth-capture.html?query=RGBAUint8'),
@@ -89,9 +78,14 @@
 
   @classmethod
   def SetUpProcess(cls):
-    super(cls, DepthCaptureIntegrationTest).SetUpProcess()
-    cls.CustomizeOptions()
-    cls.SetBrowserOptions(cls._finder_options)
+    super(DepthCaptureIntegrationTest, cls).SetUpProcess()
+    cls.CustomizeBrowserArgs([
+      '--disable-domain-blocking-for-3d-apis',
+      '--enable-es3-apis',
+      '--use-fake-ui-for-media-stream',
+      '--use-fake-device-for-media-stream=device-count=2',
+      # Required for about:gpucrash handling from Telemetry.
+      '--enable-gpu-benchmarking'])
     cls.StartBrowser()
     cls.SetStaticServerDirs([data_path])
 
diff --git a/content/test/gpu/gpu_tests/gpu_integration_test.py b/content/test/gpu/gpu_tests/gpu_integration_test.py
index 908c8164..ef67ed4 100644
--- a/content/test/gpu/gpu_tests/gpu_integration_test.py
+++ b/content/test/gpu/gpu_tests/gpu_integration_test.py
@@ -16,6 +16,55 @@
 
   _cached_expectations = None
 
+  # Several of the tests in this directory need to be able to relaunch
+  # the browser on demand with a new set of command line arguments
+  # than were originally specified. To enable this, the necessary
+  # static state is hoisted here.
+
+  # We store a deep copy of the original browser finder options in
+  # order to be able to restart the browser multiple times, with a
+  # different set of command line arguments each time.
+  _original_finder_options = None
+
+  # We keep track of the set of command line arguments used to launch
+  # the browser most recently in order to figure out whether we need
+  # to relaunch it, if a new pixel test requires a different set of
+  # arguments.
+  _last_launched_browser_args = set()
+
+  @classmethod
+  def SetUpProcess(cls):
+    super(GpuIntegrationTest, cls).SetUpProcess()
+    cls._original_finder_options = cls._finder_options.Copy()
+
+  @classmethod
+  def CustomizeBrowserArgs(cls, browser_args):
+    """Customizes the browser's command line arguments.
+
+    NOTE that redefining this method in subclasses will NOT do what
+    you expect! Do not attempt to redefine this method!
+    """
+    if not browser_args:
+      browser_args = []
+    cls._finder_options = cls._original_finder_options.Copy()
+    browser_options = cls._finder_options.browser_options
+    # Append the new arguments.
+    browser_options.AppendExtraBrowserArgs(browser_args)
+    cls._last_launched_browser_args = set(browser_args)
+    cls.SetBrowserOptions(cls._finder_options)
+
+  @classmethod
+  def RestartBrowserIfNecessaryWithArgs(cls, browser_args):
+    if not browser_args:
+      browser_args = []
+    if set(browser_args) != cls._last_launched_browser_args:
+      logging.info('Restarting browser with arguments: ' + str(browser_args))
+      cls.StopBrowser()
+      cls.CustomizeBrowserArgs(browser_args)
+      cls.StartBrowser()
+
+  # The following is the rest of the framework for the GPU integration tests.
+
   @classmethod
   def GenerateTestCases__RunGpuTest(cls, options):
     for test_name, url, args in cls.GenerateGpuTests(options):
diff --git a/content/test/gpu/gpu_tests/gpu_process_integration_test.py b/content/test/gpu/gpu_tests/gpu_process_integration_test.py
index da43f20..08cfa9b32f 100644
--- a/content/test/gpu/gpu_tests/gpu_process_integration_test.py
+++ b/content/test/gpu/gpu_tests/gpu_process_integration_test.py
@@ -2,7 +2,6 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import logging
 import os
 import sys
 
@@ -33,17 +32,6 @@
 """
 
 class GpuProcessIntegrationTest(gpu_integration_test.GpuIntegrationTest):
-  # We store a deep copy of the original browser finder options in
-  # order to be able to restart the browser multiple times, with a
-  # different set of command line arguments each time.
-  _original_finder_options = None
-
-  # We keep track of the set of command line arguments used to launch
-  # the browser most recently in order to figure out whether we need
-  # to relaunch it, if a new pixel test requires a different set of
-  # arguments.
-  _last_launched_browser_args = set()
-
   @classmethod
   def Name(cls):
     """The name by which this test is invoked on the command line."""
@@ -51,40 +39,20 @@
 
   @classmethod
   def SetUpProcess(cls):
-    super(cls, GpuProcessIntegrationTest).SetUpProcess()
-    cls._original_finder_options = cls._finder_options.Copy()
-    cls.CustomizeBrowserArgs([])
+    super(GpuProcessIntegrationTest, cls).SetUpProcess()
+    cls.CustomizeBrowserArgs(cls._AddDefaultArgs([]))
     cls.StartBrowser()
     cls.SetStaticServerDirs([data_path])
 
-  @classmethod
-  def CustomizeBrowserArgs(cls, browser_args):
-    if not browser_args:
-      browser_args = []
-    cls._finder_options = cls._original_finder_options.Copy()
-    browser_options = cls._finder_options.browser_options
-    # All tests receive the following options. They aren't recorded in
-    # the _last_launched_browser_args.
-    browser_options.AppendExtraBrowserArgs([
+  @staticmethod
+  def _AddDefaultArgs(browser_args):
+    # All tests receive the following options.
+    return [
       '--enable-gpu-benchmarking',
       # TODO(kbr): figure out why the following option seems to be
       # needed on Android for robustness.
       # https://github.com/catapult-project/catapult/issues/3122
-      '--no-first-run'])
-    # Append the new arguments.
-    browser_options.AppendExtraBrowserArgs(browser_args)
-    cls._last_launched_browser_args = set(browser_args)
-    cls.SetBrowserOptions(cls._finder_options)
-
-  @classmethod
-  def RestartBrowserIfNecessaryWithArgs(cls, browser_args):
-    if not browser_args:
-      browser_args = []
-    if set(browser_args) != cls._last_launched_browser_args:
-      logging.info('Restarting browser with arguments: ' + str(browser_args))
-      cls.StopBrowser()
-      cls.CustomizeBrowserArgs(browser_args)
-      cls.StartBrowser()
+      '--no-first-run'] + browser_args
 
   @classmethod
   def _CreateExpectations(cls):
diff --git a/content/test/gpu/gpu_tests/hardware_accelerated_feature_integration_test.py b/content/test/gpu/gpu_tests/hardware_accelerated_feature_integration_test.py
index 2fd35ef..3452432 100644
--- a/content/test/gpu/gpu_tests/hardware_accelerated_feature_integration_test.py
+++ b/content/test/gpu/gpu_tests/hardware_accelerated_feature_integration_test.py
@@ -39,7 +39,7 @@
   @classmethod
   def SetUpProcess(cls):
     super(cls, HardwareAcceleratedFeatureIntegrationTest).SetUpProcess()
-    cls.SetBrowserOptions(cls._finder_options)
+    cls.CustomizeBrowserArgs([])
     cls.StartBrowser()
     cls.SetStaticServerDirs([])
 
diff --git a/content/test/gpu/gpu_tests/info_collection_test.py b/content/test/gpu/gpu_tests/info_collection_test.py
index 640999fc..c256a05 100644
--- a/content/test/gpu/gpu_tests/info_collection_test.py
+++ b/content/test/gpu/gpu_tests/info_collection_test.py
@@ -31,7 +31,7 @@
   @classmethod
   def SetUpProcess(cls):
     super(cls, InfoCollectionTest).SetUpProcess()
-    cls.SetBrowserOptions(cls._finder_options)
+    cls.CustomizeBrowserArgs([])
     cls.StartBrowser()
 
   def RunActualGpuTest(self, test_path, *args):
diff --git a/content/test/gpu/gpu_tests/maps_integration_test.py b/content/test/gpu/gpu_tests/maps_integration_test.py
index f505da1..e540cd7 100644
--- a/content/test/gpu/gpu_tests/maps_integration_test.py
+++ b/content/test/gpu/gpu_tests/maps_integration_test.py
@@ -61,8 +61,8 @@
 
   @classmethod
   def SetUpProcess(cls):
-    super(cls, MapsIntegrationTest).SetUpProcess()
-    cls.SetBrowserOptions(cls._finder_options)
+    super(MapsIntegrationTest, cls).SetUpProcess()
+    cls.CustomizeBrowserArgs([])
     cls.StartWPRServer(os.path.join(data_path, 'maps_004.wpr.updated'),
                        cloud_storage.PUBLIC_BUCKET)
     cls.StartBrowser()
diff --git a/content/test/gpu/gpu_tests/pixel_integration_test.py b/content/test/gpu/gpu_tests/pixel_integration_test.py
index 7fe9830..cbeca3d3 100644
--- a/content/test/gpu/gpu_tests/pixel_integration_test.py
+++ b/content/test/gpu/gpu_tests/pixel_integration_test.py
@@ -2,7 +2,6 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 import glob
-import logging
 import os
 import re
 import sys
@@ -49,18 +48,6 @@
 
 class PixelIntegrationTest(
     cloud_storage_integration_test_base.CloudStorageIntegrationTestBase):
-
-  # We store a deep copy of the original browser finder options in
-  # order to be able to restart the browser multiple times, with a
-  # different set of command line arguments each time.
-  _original_finder_options = None
-
-  # We keep track of the set of command line arguments used to launch
-  # the browser most recently in order to figure out whether we need
-  # to relaunch it, if a new pixel test requires a different set of
-  # arguments.
-  _last_launched_browser_args = set()
-
   @classmethod
   def Name(cls):
     """The name by which this test is invoked on the command line."""
@@ -68,37 +55,24 @@
 
   @classmethod
   def SetUpProcess(cls):
-    super(cls, PixelIntegrationTest).SetUpProcess()
-    cls._original_finder_options = cls._finder_options.Copy()
-    cls.CustomizeBrowserArgs([])
+    super(PixelIntegrationTest, cls).SetUpProcess()
+    cls.CustomizeBrowserArgs(cls._AddDefaultArgs([]))
     cls.StartBrowser()
     cls.SetStaticServerDirs(test_data_dirs)
 
-  @classmethod
-  def CustomizeBrowserArgs(cls, browser_args):
+  @staticmethod
+  def _AddDefaultArgs(browser_args):
     if not browser_args:
       browser_args = []
-    cls._finder_options = cls._original_finder_options.Copy()
-    browser_options = cls._finder_options.browser_options
-    # All tests receive these options. They aren't recorded in the
-    # _last_launched_browser_args.
-    browser_options.AppendExtraBrowserArgs(['--enable-gpu-benchmarking',
-                                            '--test-type=gpu'])
-    # Append the new arguments.
-    browser_options.AppendExtraBrowserArgs(browser_args)
-    cls._last_launched_browser_args = set(browser_args)
-    cls.SetBrowserOptions(cls._finder_options)
+    # All tests receive the following options.
+    return [
+      '--enable-gpu-benchmarking',
+      '--test-type=gpu'] + browser_args
 
   @classmethod
-  def RestartBrowserIfNecessaryWithArgs(cls, browser_args):
-    if not browser_args:
-      browser_args = []
-    if set(browser_args) != cls._last_launched_browser_args:
-      logging.warning('Restarting browser with arguments: ' + str(browser_args))
-      cls.StopBrowser()
-      cls.ResetGpuInfo()
-      cls.CustomizeBrowserArgs(browser_args)
-      cls.StartBrowser()
+  def StopBrowser(cls):
+    super(PixelIntegrationTest, cls).StopBrowser()
+    cls.ResetGpuInfo()
 
   @classmethod
   def AddCommandlineArgs(cls, parser):
@@ -132,7 +106,8 @@
     # Some pixel tests require non-standard browser arguments. Need to
     # check before running each page that it can run in the current
     # browser instance.
-    self.RestartBrowserIfNecessaryWithArgs(page.browser_args)
+    self.RestartBrowserIfNecessaryWithArgs(self._AddDefaultArgs(
+      page.browser_args))
     url = self.UrlOfStaticFilePath(test_path)
     # This property actually comes off the class, not 'self'.
     tab = self.tab
diff --git a/content/test/gpu/gpu_tests/screenshot_sync_integration_test.py b/content/test/gpu/gpu_tests/screenshot_sync_integration_test.py
index 1a22046..0d0c00dc 100644
--- a/content/test/gpu/gpu_tests/screenshot_sync_integration_test.py
+++ b/content/test/gpu/gpu_tests/screenshot_sync_integration_test.py
@@ -2,7 +2,6 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import logging
 import os
 import random
 import sys
@@ -22,17 +21,6 @@
   which they were requested.
   """
 
-  # We store a deep copy of the original browser finder options in
-  # order to be able to restart the browser multiple times, with a
-  # different set of command line arguments each time.
-  _original_finder_options = None
-
-  # We keep track of the set of command line arguments used to launch
-  # the browser most recently in order to figure out whether we need
-  # to relaunch it, if a new pixel test requires a different set of
-  # arguments.
-  _last_launched_browser_args = set()
-
   @classmethod
   def Name(cls):
     """The name by which this test is invoked on the command line."""
@@ -41,37 +29,15 @@
   @classmethod
   def SetUpProcess(cls):
     super(cls, ScreenshotSyncIntegrationTest).SetUpProcess()
-    cls._original_finder_options = cls._finder_options.Copy()
-    cls.CustomizeBrowserArgs([])
+    cls.CustomizeBrowserArgs(cls._AddDefaultArgs([]))
     cls.StartBrowser()
     cls.SetStaticServerDirs([data_path])
 
-  @classmethod
-  def CustomizeBrowserArgs(cls, browser_args):
-    if not browser_args:
-      browser_args = []
-    cls._finder_options = cls._original_finder_options.Copy()
-    browser_options = cls._finder_options.browser_options
-    # All tests receive the following options. They aren't recorded in
-    # the _last_launched_browser_args.
-    #
-    # --test-type=gpu is used only to suppress the "Google API Keys are missing"
-    # infobar, which causes flakiness in tests.
-    browser_options.AppendExtraBrowserArgs(['--test-type=gpu'])
-    # Append the new arguments.
-    browser_options.AppendExtraBrowserArgs(browser_args)
-    cls._last_launched_browser_args = set(browser_args)
-    cls.SetBrowserOptions(cls._finder_options)
-
-  @classmethod
-  def RestartBrowserIfNecessaryWithArgs(cls, browser_args):
-    if not browser_args:
-      browser_args = []
-    if set(browser_args) != cls._last_launched_browser_args:
-      logging.info('Restarting browser with arguments: ' + str(browser_args))
-      cls.StopBrowser()
-      cls.CustomizeBrowserArgs(browser_args)
-      cls.StartBrowser()
+  @staticmethod
+  def _AddDefaultArgs(browser_args):
+    # --test-type=gpu is used to suppress the "Google API Keys are
+    # missing" infobar, which causes flakiness in tests.
+    return ['--test-type=gpu'] + browser_args
 
   @classmethod
   def _CreateExpectations(cls):
@@ -129,7 +95,7 @@
 
   def RunActualGpuTest(self, test_path, *args):
     browser_arg = args[0]
-    self.RestartBrowserIfNecessaryWithArgs([browser_arg])
+    self.RestartBrowserIfNecessaryWithArgs(self._AddDefaultArgs([browser_arg]))
     self._Navigate(test_path)
     repetitions = 20
     for _ in range(0, repetitions):
diff --git a/content/test/gpu/gpu_tests/trace_integration_test.py b/content/test/gpu/gpu_tests/trace_integration_test.py
index f0f8282..0c9330e 100644
--- a/content/test/gpu/gpu_tests/trace_integration_test.py
+++ b/content/test/gpu/gpu_tests/trace_integration_test.py
@@ -64,12 +64,6 @@
     return 'trace_test'
 
   @classmethod
-  def CustomizeOptions(cls):
-    options = cls._finder_options.browser_options
-    options.AppendExtraBrowserArgs('--enable-logging')
-    options.AppendExtraBrowserArgs('--enable-experimental-canvas-features')
-
-  @classmethod
   def GenerateGpuTests(cls, options):
     # Include the device level trace tests, even though they're
     # currently skipped on all platforms, to give a hint that they
@@ -125,10 +119,11 @@
 
   @classmethod
   def SetUpProcess(cls):
-    super(cls, TraceIntegrationTest).SetUpProcess()
+    super(TraceIntegrationTest, cls).SetUpProcess()
     path_util.SetupTelemetryPaths()
-    cls.CustomizeOptions()
-    cls.SetBrowserOptions(cls._finder_options)
+    cls.CustomizeBrowserArgs([
+      '--enable-logging',
+      '--enable-experimental-canvas-features'])
     cls.StartBrowser()
     cls.SetStaticServerDirs(data_paths)
 
diff --git a/content/test/gpu/gpu_tests/webgl_conformance_integration_test.py b/content/test/gpu/gpu_tests/webgl_conformance_integration_test.py
index 81baa386..1fc4fbc 100644
--- a/content/test/gpu/gpu_tests/webgl_conformance_integration_test.py
+++ b/content/test/gpu/gpu_tests/webgl_conformance_integration_test.py
@@ -11,8 +11,6 @@
 from gpu_tests import webgl_conformance_expectations
 from gpu_tests import webgl2_conformance_expectations
 
-from telemetry.internal.browser import browser_finder
-
 conformance_relcomps = (
   'third_party', 'webgl', 'src', 'sdk', 'tests')
 
@@ -249,23 +247,24 @@
     self._CheckTestCompletion()
 
   @classmethod
-  def CustomizeOptions(cls):
-    assert cls._webgl_version == 1 or cls._webgl_version == 2
-    browser_options = cls._finder_options.browser_options
+  def SetupWebGLBrowserArgs(cls, browser_args):
     # --test-type=gpu is used only to suppress the "Google API Keys are missing"
     # infobar, which causes flakiness in tests.
-    browser_options.AppendExtraBrowserArgs([
-        '--ignore-autoplay-restrictions',
-        '--disable-domain-blocking-for-3d-apis',
-        '--disable-gpu-process-crash-limit',
-        '--test-type=gpu',
-        '--enable-experimental-canvas-features',
-        # Try disabling the GPU watchdog to see if this affects the
-        # intermittent GPU process hangs that have been seen on the
-        # waterfall. crbug.com/596622 crbug.com/609252
-        '--disable-gpu-watchdog'
-    ])
-
+    browser_args += [
+      '--ignore-autoplay-restrictions',
+      '--disable-domain-blocking-for-3d-apis',
+      '--disable-gpu-process-crash-limit',
+      '--test-type=gpu',
+      '--enable-experimental-canvas-features',
+      # Try disabling the GPU watchdog to see if this affects the
+      # intermittent GPU process hangs that have been seen on the
+      # waterfall. crbug.com/596622 crbug.com/609252
+      '--disable-gpu-watchdog'
+    ]
+    # Note that the overriding of the default --js-flags probably
+    # won't interact well with RestartBrowserIfNecessaryWithArgs, but
+    # we don't use that in this test.
+    browser_options = cls._finder_options.browser_options
     builtin_js_flags = '--js-flags=--expose-gc'
     found_js_flags = False
     user_js_flags = ''
@@ -280,25 +279,8 @@
       logging.warning(' Original flags: ' + builtin_js_flags)
       logging.warning(' New flags: ' + user_js_flags)
     else:
-      browser_options.AppendExtraBrowserArgs([builtin_js_flags])
-
-    if cls._webgl_version == 2:
-      browser_options.AppendExtraBrowserArgs([
-        '--enable-es3-apis',
-      ])
-    browser = browser_finder.FindBrowser(browser_options.finder_options)
-    if (browser.target_os.startswith('android') and
-      browser.browser_type == 'android-webview-instrumentation'):
-      # TODO(kbr): this is overly broad. We'd like to do this only on
-      # Nexus 9. It'll go away shortly anyway. crbug.com/499928
-      #
-      # The --ignore_egl_sync_failures is only there to work around
-      # some strange failure on the Nexus 9 bot, not reproducible on
-      # local hardware.
-      browser_options.AppendExtraBrowserArgs([
-        '--disable-gl-extensions=GL_EXT_disjoint_timer_query',
-        '--ignore_egl_sync_failures',
-      ])
+      browser_args += [builtin_js_flags]
+    cls.CustomizeBrowserArgs(browser_args)
 
   @classmethod
   def _CreateExpectations(cls):
@@ -315,8 +297,7 @@
   @classmethod
   def SetUpProcess(cls):
     super(WebGLConformanceIntegrationTest, cls).SetUpProcess()
-    cls.CustomizeOptions()
-    cls.SetBrowserOptions(cls._finder_options)
+    cls.SetupWebGLBrowserArgs([])
     cls.StartBrowser()
     # By setting multiple server directories, the root of the server
     # implicitly becomes the common base directory, i.e., the Chromium
diff --git a/device/bluetooth/public/interfaces/test/fake_bluetooth.mojom b/device/bluetooth/public/interfaces/test/fake_bluetooth.mojom
index a9d71fa..98da605 100644
--- a/device/bluetooth/public/interfaces/test/fake_bluetooth.mojom
+++ b/device/bluetooth/public/interfaces/test/fake_bluetooth.mojom
@@ -30,6 +30,10 @@
   SimulateCentral(CentralState state) => (FakeCentral fake_central);
 };
 
+// HCI Error Codes from BT 4.2 Vol 2 Part D 1.3 List Of Error Codes.
+const uint16 kHCISuccess = 0x0000;
+const uint16 kHCIConnectionTimeout = 0x0008;
+
 // FakeCentral allows clients to simulate events that a device in the
 // Central/Observer role would receive as well as monitor the operations
 // performed by the device in the Central/Observer role.
@@ -48,7 +52,20 @@
   // connected to the system or weren't connected through the UA e.g. a user
   // connected a peripheral through the system's settings. This method is
   // intended to simulate peripherals that those methods would return.
+  // Even though these devices are already connected to the OS, clients
+  // need to call the respective connect functions to signal they intend to keep
+  // the connection alive.
   SimulatePreconnectedPeripheral(string address,
                                  string name,
                                  array<UUID> known_service_uuids) => ();
+
+  // Sets the next GATT Connection request response for peripheral with
+  // |address| to |code|. |code| could be an HCI Error Code from
+  // BT 4.2 Vol 2 Part D 1.3 List Of Error Codes or a number outside that range
+  // returned by specific platforms e.g. Android returns 0x101 to signal a GATT
+  // failure
+  // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#GATT_FAILURE
+  // Calls callback with false if there was any error when simulating the next
+  // response.
+  SetNextGATTConnectionResponse(string address, uint16 code) => (bool success);
 };
diff --git a/device/bluetooth/test/fake_central.cc b/device/bluetooth/test/fake_central.cc
index 8bc994f..2b11378 100644
--- a/device/bluetooth/test/fake_central.cc
+++ b/device/bluetooth/test/fake_central.cc
@@ -37,13 +37,29 @@
   FakePeripheral* fake_peripheral =
       static_cast<FakePeripheral*>(device_iter->second.get());
   fake_peripheral->SetName(name);
-  fake_peripheral->SetGattConnected(true);
+  fake_peripheral->SetSystemConnected(true);
   fake_peripheral->SetServiceUUIDs(device::BluetoothDevice::UUIDSet(
       known_service_uuids.begin(), known_service_uuids.end()));
 
   std::move(callback).Run();
 }
 
+void FakeCentral::SetNextGATTConnectionResponse(
+    const std::string& address,
+    uint16_t code,
+    SetNextGATTConnectionResponseCallback callback) {
+  auto device_iter = devices_.find(address);
+  if (device_iter == devices_.end()) {
+    std::move(callback).Run(false);
+    return;
+  }
+
+  FakePeripheral* fake_peripheral =
+      static_cast<FakePeripheral*>(device_iter->second.get());
+  fake_peripheral->SetNextGATTConnectionResponse(code);
+  std::move(callback).Run(true);
+}
+
 std::string FakeCentral::GetAddress() const {
   NOTREACHED();
   return std::string();
diff --git a/device/bluetooth/test/fake_central.h b/device/bluetooth/test/fake_central.h
index 93fbd93..4b096eca 100644
--- a/device/bluetooth/test/fake_central.h
+++ b/device/bluetooth/test/fake_central.h
@@ -29,6 +29,10 @@
       const std::string& name,
       const std::vector<device::BluetoothUUID>& known_service_uuids,
       SimulatePreconnectedPeripheralCallback callback) override;
+  void SetNextGATTConnectionResponse(
+      const std::string& address,
+      uint16_t code,
+      SetNextGATTConnectionResponseCallback) override;
 
   // BluetoothAdapter overrides:
   std::string GetAddress() const override;
diff --git a/device/bluetooth/test/fake_peripheral.cc b/device/bluetooth/test/fake_peripheral.cc
index acf93c73..a8524e2 100644
--- a/device/bluetooth/test/fake_peripheral.cc
+++ b/device/bluetooth/test/fake_peripheral.cc
@@ -4,13 +4,17 @@
 
 #include "device/bluetooth/test/fake_peripheral.h"
 
+#include "base/memory/weak_ptr.h"
+
 namespace bluetooth {
 
 FakePeripheral::FakePeripheral(FakeCentral* fake_central,
                                const std::string& address)
     : device::BluetoothDevice(fake_central),
       address_(address),
-      gatt_connected_(false) {}
+      system_connected_(false),
+      gatt_connected_(false),
+      weak_ptr_factory_(this) {}
 
 FakePeripheral::~FakePeripheral() {}
 
@@ -18,14 +22,20 @@
   name_ = std::move(name);
 }
 
-void FakePeripheral::SetGattConnected(bool connected) {
-  gatt_connected_ = connected;
+void FakePeripheral::SetSystemConnected(bool connected) {
+  system_connected_ = connected;
 }
 
 void FakePeripheral::SetServiceUUIDs(UUIDSet service_uuids) {
   service_uuids_ = std::move(service_uuids);
 }
 
+void FakePeripheral::SetNextGATTConnectionResponse(uint16_t code) {
+  DCHECK(!next_connection_response_);
+  DCHECK(create_gatt_connection_error_callbacks_.empty());
+  next_connection_response_ = code;
+}
+
 uint32_t FakePeripheral::GetBluetoothClass() const {
   NOTREACHED();
   return 0;
@@ -92,7 +102,10 @@
 }
 
 bool FakePeripheral::IsGattConnected() const {
-  return gatt_connected_;
+  // TODO(crbug.com/728870): Return gatt_connected_ only once system connected
+  // peripherals are supported and Web Bluetooth uses them. See issue for more
+  // details.
+  return system_connected_ || gatt_connected_;
 }
 
 bool FakePeripheral::IsConnectable() const {
@@ -184,11 +197,43 @@
   NOTREACHED();
 }
 
+void FakePeripheral::CreateGattConnection(
+    const GattConnectionCallback& callback,
+    const ConnectErrorCallback& error_callback) {
+  create_gatt_connection_success_callbacks_.push_back(callback);
+  create_gatt_connection_error_callbacks_.push_back(error_callback);
+
+  // TODO(crbug.com/728870): Stop overriding CreateGattConnection once
+  // IsGattConnected() is fixed. See issue for more details.
+  if (gatt_connected_)
+    return DidConnectGatt();
+
+  CreateGattConnectionImpl();
+}
+
 void FakePeripheral::CreateGattConnectionImpl() {
-  NOTREACHED();
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::Bind(&FakePeripheral::DispatchConnectionResponse,
+                            weak_ptr_factory_.GetWeakPtr()));
+}
+
+void FakePeripheral::DispatchConnectionResponse() {
+  DCHECK(next_connection_response_);
+
+  uint16_t code = next_connection_response_.value();
+  next_connection_response_.reset();
+
+  if (code == mojom::kHCISuccess) {
+    gatt_connected_ = true;
+    DidConnectGatt();
+  } else if (code == mojom::kHCIConnectionTimeout) {
+    DidFailToConnectGatt(ERROR_FAILED);
+  } else {
+    DidFailToConnectGatt(ERROR_UNKNOWN);
+  }
 }
 
 void FakePeripheral::DisconnectGatt() {
-  NOTREACHED();
 }
+
 }  // namespace bluetooth
diff --git a/device/bluetooth/test/fake_peripheral.h b/device/bluetooth/test/fake_peripheral.h
index 8e6222234..b105c4f1 100644
--- a/device/bluetooth/test/fake_peripheral.h
+++ b/device/bluetooth/test/fake_peripheral.h
@@ -8,6 +8,7 @@
 
 #include "base/compiler_specific.h"
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "base/optional.h"
 #include "device/bluetooth/bluetooth_device.h"
 #include "device/bluetooth/test/fake_central.h"
@@ -24,13 +25,20 @@
   // Changes the name of the device.
   void SetName(base::Optional<std::string> name);
 
-  // Set it to indicate if the Peripheral is connected or not.
-  void SetGattConnected(bool gatt_connected);
+  // Set it to indicate if the system has connected to the Peripheral outside of
+  // the Bluetooth interface e.g. the user connected to the device through
+  // system settings.
+  void SetSystemConnected(bool gatt_connected);
 
   // Updates the peripheral's UUIDs that are returned by
   // BluetoothDevice::GetUUIDs().
   void SetServiceUUIDs(UUIDSet service_uuids);
 
+  // If |code| is kHCISuccess calls a pending success callback for
+  // CreateGattConnection. Otherwise calls a pending error callback
+  // with the ConnectErrorCode corresponding to |code|.
+  void SetNextGATTConnectionResponse(uint16_t code);
+
   // BluetoothDevice overrides:
   uint32_t GetBluetoothClass() const override;
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
@@ -78,16 +86,31 @@
       const device::BluetoothUUID& uuid,
       const ConnectToServiceCallback& callback,
       const ConnectToServiceErrorCallback& error_callback) override;
+  void CreateGattConnection(
+      const GattConnectionCallback& callback,
+      const ConnectErrorCallback& error_callback) override;
 
  protected:
   void CreateGattConnectionImpl() override;
   void DisconnectGatt() override;
 
  private:
+  void DispatchConnectionResponse();
+
   const std::string address_;
   base::Optional<std::string> name_;
-  bool gatt_connected_;
   UUIDSet service_uuids_;
+  // True when the system has connected to the device outside of the Bluetooth
+  // interface e.g. the user connected to the device through system settings.
+  bool system_connected_;
+  // True when this Bluetooth interface is connected to the device.
+  bool gatt_connected_;
+
+  // Used to decide which callback should be called when
+  // CreateGattConnection is called.
+  base::Optional<uint16_t> next_connection_response_;
+
+  base::WeakPtrFactory<FakePeripheral> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(FakePeripheral);
 };
diff --git a/docs/testing/web_platform_tests.md b/docs/testing/web_platform_tests.md
index eec59f4..949c7af 100644
--- a/docs/testing/web_platform_tests.md
+++ b/docs/testing/web_platform_tests.md
@@ -46,6 +46,21 @@
 
 Automatic imports are intended to run at least once every 24 hours.
 
+### Failures caused by automatic imports.
+
+If there are new test failures that start after an auto-import,
+there are several possible causes, including:
+
+ 1. New baselines for flaky tests were added (http://crbug.com/701234).
+ 2. Modified tests should have new results for non-Release builds but they weren't added (http://crbug.com/725160).
+ 3. New baselines were added for tests with non-deterministic test results (http://crbug.com/705125).
+
+Because these tests are imported from the Web Platform tests, it is better
+to have them in the repository (and marked failing) than not, so prefer to
+[add test expectations](layout_test_expectations.md) rather than reverting.
+However, if a huge number of tests are failing, please revert the CL so we
+can fix it manually.
+
 ### Automatic export process
 
 If a commit to Chromium master changes any files in the
diff --git a/gpu/ipc/client/command_buffer_proxy_impl.cc b/gpu/ipc/client/command_buffer_proxy_impl.cc
index 6d269fd..77c08eb 100644
--- a/gpu/ipc/client/command_buffer_proxy_impl.cc
+++ b/gpu/ipc/client/command_buffer_proxy_impl.cc
@@ -53,9 +53,8 @@
 CommandBufferProxyImpl::CommandBufferProxyImpl(int channel_id,
                                                int32_t route_id,
                                                int32_t stream_id)
-    : lock_(nullptr),
-      gpu_control_client_(nullptr),
-      command_buffer_id_(CommandBufferProxyID(channel_id, route_id)),
+    : command_buffer_id_(CommandBufferProxyID(channel_id, route_id)),
+      channel_id_(channel_id),
       route_id_(route_id),
       stream_id_(stream_id),
       weak_this_(AsWeakPtr()) {
@@ -671,15 +670,14 @@
     const gpu::SyncToken& sync_token) {
   // Can only wait on an unverified sync token if it is from the same channel.
   int sync_token_channel_id = GetChannelID(sync_token.command_buffer_id());
-  int channel_id = GetChannelID(command_buffer_id_);
   if (sync_token.namespace_id() != gpu::CommandBufferNamespace::GPU_IO ||
-      sync_token_channel_id != channel_id) {
+      sync_token_channel_id != channel_id_) {
     return false;
   }
 
   // If waiting on a different stream, flush pending commands on that stream.
   int32_t release_stream_id = sync_token.extra_data_field();
-  if (release_stream_id != stream_id_)
+  if (channel_ && release_stream_id != stream_id_)
     channel_->FlushPendingStream(release_stream_id);
 
   return true;
diff --git a/gpu/ipc/client/command_buffer_proxy_impl.h b/gpu/ipc/client/command_buffer_proxy_impl.h
index 24e53f8b..835140b 100644
--- a/gpu/ipc/client/command_buffer_proxy_impl.h
+++ b/gpu/ipc/client/command_buffer_proxy_impl.h
@@ -250,17 +250,18 @@
   // There should be a lock_ if this is going to be used across multiple
   // threads, or we guarantee it is used by a single thread by using a thread
   // checker if no lock_ is set.
-  base::Lock* lock_;
+  base::Lock* lock_ = nullptr;
   base::ThreadChecker lockless_thread_checker_;
 
   // Client that wants to listen for important events on the GpuControl.
-  gpu::GpuControlClient* gpu_control_client_;
+  gpu::GpuControlClient* gpu_control_client_ = nullptr;
 
   // Unowned list of DeletionObservers.
   base::ObserverList<DeletionObserver> deletion_observers_;
 
   scoped_refptr<GpuChannelHost> channel_;
   const gpu::CommandBufferId command_buffer_id_;
+  const int channel_id_;
   const int32_t route_id_;
   const int32_t stream_id_;
   uint32_t flush_count_ = 0;
diff --git a/ios/build/bots/chromium.fyi/ios-simulator.json b/ios/build/bots/chromium.fyi/ios-simulator.json
index 872baca2..fa1ec08f 100644
--- a/ios/build/bots/chromium.fyi/ios-simulator.json
+++ b/ios/build/bots/chromium.fyi/ios-simulator.json
@@ -75,6 +75,66 @@
       "device type": "iPad Retina",
       "os": "9.0",
       "xcode version": "8.0"
+    },
+    {
+      "include": "fyi_tests.json",
+      "device type": "iPhone 6s Plus",
+      "os": "10.0",
+      "xcode version": "8.0"
+    },
+    {
+      "include": "fyi_tests.json",
+      "device type": "iPhone 6s",
+      "os": "10.0",
+      "xcode version": "8.0"
+    },
+    {
+      "include": "fyi_tests.json",
+      "device type": "iPhone 5",
+      "os": "10.0",
+      "xcode version": "8.0"
+    },
+    {
+      "include": "fyi_tests.json",
+      "device type": "iPad Air 2",
+      "os": "10.0",
+      "xcode version": "8.0"
+    },
+    {
+      "include": "fyi_tests.json",
+      "device type": "iPad Retina",
+      "os": "10.0",
+      "xcode version": "8.0"
+    },
+    {
+      "include": "fyi_tests.json",
+      "device type": "iPhone 6s Plus",
+      "os": "9.0",
+      "xcode version": "8.0"
+    },
+    {
+      "include": "fyi_tests.json",
+      "device type": "iPhone 6s",
+      "os": "9.0",
+      "xcode version": "8.0"
+    },
+    {
+      "include": "fyi_tests.json",
+      "device type": "iPhone 5",
+      "os": "9.0",
+      "xcode version": "8.0"
+    },
+    {
+      "include": "fyi_tests.json",
+      "device type": "iPad Air 2",
+      "os": "9.0",
+      "xcode version": "8.0"
+    },
+    {
+      "include": "fyi_tests.json",
+      "device type": "iPad Retina",
+      "os": "9.0",
+      "xcode version": "8.0"
     }
   ]
 }
diff --git a/ios/build/bots/tests/fyi_tests.json b/ios/build/bots/tests/fyi_tests.json
new file mode 100644
index 0000000..f79aaee
--- /dev/null
+++ b/ios/build/bots/tests/fyi_tests.json
@@ -0,0 +1,10 @@
+{
+  "tests": [
+    {
+      "app": "ios_chrome_unittests"
+    },
+    {
+      "app": "net_unittests"
+    }
+  ]
+}
diff --git a/ios/chrome/app/multitasking_test_application_delegate.mm b/ios/chrome/app/multitasking_test_application_delegate.mm
index f5a3a8eb..7fddf09 100644
--- a/ios/chrome/app/multitasking_test_application_delegate.mm
+++ b/ios/chrome/app/multitasking_test_application_delegate.mm
@@ -36,9 +36,9 @@
     didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
   // Configure application window size for multitasking tests.
   CGSize newWindowSize = [self windowSize];
-  self.window = [[[ChromeOverlayWindow alloc]
-      initWithFrame:CGRectMake(0, 0, newWindowSize.width, newWindowSize.height)]
-      autorelease];
+  self.window = [[ChromeOverlayWindow alloc]
+      initWithFrame:CGRectMake(0, 0, newWindowSize.width,
+                               newWindowSize.height)];
 
   BOOL inBackground =
       [application applicationState] == UIApplicationStateBackground;
diff --git a/ios/chrome/browser/payments/payment_request.h b/ios/chrome/browser/payments/payment_request.h
index 65ea580..8bc25f60 100644
--- a/ios/chrome/browser/payments/payment_request.h
+++ b/ios/chrome/browser/payments/payment_request.h
@@ -163,7 +163,7 @@
   // Returns whether the current PaymentRequest can be used to make a payment.
   bool CanMakePayment() const;
 
- private:
+ protected:
   // Fetches the autofill profiles for this user from the PersonalDataManager,
   // and stores copies of them, owned by this PaymentRequest, in profile_cache_.
   void PopulateProfileCache();
diff --git a/ios/chrome/browser/payments/test_payment_request.h b/ios/chrome/browser/payments/test_payment_request.h
index 025d38eb..1d6eda9 100644
--- a/ios/chrome/browser/payments/test_payment_request.h
+++ b/ios/chrome/browser/payments/test_payment_request.h
@@ -15,6 +15,7 @@
 
 namespace web {
 class PaymentRequest;
+class PaymentShippingOption;
 }  // namespace web
 
 // PaymentRequest for use in tests.
@@ -31,6 +32,24 @@
     region_data_loader_ = region_data_loader;
   }
 
+  // Returns the web::PaymentRequest instance that was used to build this
+  // object.
+  web::PaymentRequest& web_payment_request() { return web_payment_request_; }
+
+  // Removes all the shipping profiles.
+  void ClearShippingProfiles();
+
+  // Removes all the contact profiles.
+  void ClearContactProfiles();
+
+  // Removes all the credit cards.
+  void ClearCreditCards();
+
+  // Sets the currently selected shipping option for this PaymentRequest flow.
+  void set_selected_shipping_option(web::PaymentShippingOption* option) {
+    selected_shipping_option_ = option;
+  }
+
   // PaymentRequest
   autofill::RegionDataLoader* GetRegionDataLoader() override;
 
diff --git a/ios/chrome/browser/payments/test_payment_request.mm b/ios/chrome/browser/payments/test_payment_request.mm
index 37f20d9..a2e1e49 100644
--- a/ios/chrome/browser/payments/test_payment_request.mm
+++ b/ios/chrome/browser/payments/test_payment_request.mm
@@ -12,6 +12,18 @@
 #error "This file requires ARC support."
 #endif
 
+void TestPaymentRequest::ClearShippingProfiles() {
+  shipping_profiles_.clear();
+}
+
+void TestPaymentRequest::ClearContactProfiles() {
+  contact_profiles_.clear();
+}
+
+void TestPaymentRequest::ClearCreditCards() {
+  credit_cards_.clear();
+}
+
 autofill::RegionDataLoader* TestPaymentRequest::GetRegionDataLoader() {
   return region_data_loader_;
 }
diff --git a/ios/chrome/browser/ui/payments/BUILD.gn b/ios/chrome/browser/ui/payments/BUILD.gn
index feeadd8..a827903 100644
--- a/ios/chrome/browser/ui/payments/BUILD.gn
+++ b/ios/chrome/browser/ui/payments/BUILD.gn
@@ -45,9 +45,6 @@
     "payment_request_manager.mm",
     "payment_request_mediator.h",
     "payment_request_mediator.mm",
-    "payment_request_view_controller.h",
-    "payment_request_view_controller.mm",
-    "payment_request_view_controller_actions.h",
     "region_data_loader.h",
     "region_data_loader.mm",
     "shipping_address_selection_coordinator.h",
@@ -119,6 +116,10 @@
     "payment_request_selector_view_controller.mm",
     "payment_request_selector_view_controller_actions.h",
     "payment_request_selector_view_controller_data_source.h",
+    "payment_request_view_controller.h",
+    "payment_request_view_controller.mm",
+    "payment_request_view_controller_actions.h",
+    "payment_request_view_controller_data_source.h",
   ]
   deps = [
     "//base",
@@ -156,6 +157,7 @@
     "payment_request_edit_view_controller_unittest.mm",
     "payment_request_error_coordinator_unittest.mm",
     "payment_request_error_view_controller_unittest.mm",
+    "payment_request_mediator_unittest.mm",
     "payment_request_selector_view_controller_unittest.mm",
     "payment_request_view_controller_unittest.mm",
     "region_data_loader_unittest.mm",
diff --git a/ios/chrome/browser/ui/payments/payment_request_coordinator.mm b/ios/chrome/browser/ui/payments/payment_request_coordinator.mm
index 08653996..1ada066 100644
--- a/ios/chrome/browser/ui/payments/payment_request_coordinator.mm
+++ b/ios/chrome/browser/ui/payments/payment_request_coordinator.mm
@@ -55,10 +55,10 @@
 
 - (void)start {
   _mediator =
-      [[PaymentRequestMediator alloc] initWithBrowserState:_browserState];
+      [[PaymentRequestMediator alloc] initWithBrowserState:_browserState
+                                            paymentRequest:_paymentRequest];
 
-  _viewController = [[PaymentRequestViewController alloc]
-      initWithPaymentRequest:_paymentRequest];
+  _viewController = [[PaymentRequestViewController alloc] init];
   [_viewController setPageFavicon:_pageFavicon];
   [_viewController setPageTitle:_pageTitle];
   [_viewController setPageHost:_pageHost];
@@ -124,6 +124,7 @@
 - (void)updatePaymentDetails:(web::PaymentDetails)paymentDetails {
   BOOL totalValueChanged =
       (_paymentRequest->payment_details().total != paymentDetails.total);
+  [_mediator setTotalValueChanged:totalValueChanged];
   _paymentRequest->UpdatePaymentDetails(paymentDetails);
 
   if (_paymentRequest->shipping_options().empty()) {
@@ -138,29 +139,25 @@
     [_viewController loadModel];
     [[_viewController collectionView] reloadData];
   } else {
-    // Update the payment summary section.
-    [_viewController
-        updatePaymentSummaryWithTotalValueChanged:totalValueChanged];
+    // Update the payment summary item.
+    [_viewController updatePaymentSummaryItem];
 
     if (_shippingAddressSelectionCoordinator) {
-      // Set the selected shipping address and update the selected shipping
-      // address in the payment request summary view.
+      // Set the selected shipping address.
       _paymentRequest->set_selected_shipping_profile(_pendingShippingAddress);
       _pendingShippingAddress = nil;
-      [_viewController updateSelectedShippingAddressUI];
 
       // Dismiss the shipping address selection view.
       [_shippingAddressSelectionCoordinator stop];
       _shippingAddressSelectionCoordinator = nil;
     } else if (_shippingOptionSelectionCoordinator) {
-      // Update the selected shipping option in the payment request summary
-      // view. The updated selection is already reflected in |_paymentRequest|.
-      [_viewController updateSelectedShippingOptionUI];
-
       // Dismiss the shipping option selection view.
       [_shippingOptionSelectionCoordinator stop];
       _shippingOptionSelectionCoordinator = nil;
     }
+
+    // Update the Shipping section in the payment request summary view.
+    [_viewController updateShippingSection];
   }
 }
 
@@ -260,7 +257,7 @@
 - (void)paymentItemsDisplayCoordinatorDidReturn:
     (PaymentItemsDisplayCoordinator*)coordinator {
   // Clear the 'Updated' label on the payment summary item, if there is one.
-  [_viewController updatePaymentSummaryWithTotalValueChanged:NO];
+  [_viewController updatePaymentSummaryItem];
 
   [_itemsDisplayCoordinator stop];
   _itemsDisplayCoordinator = nil;
@@ -278,7 +275,7 @@
         didSelectContactProfile:(autofill::AutofillProfile*)contactProfile {
   _paymentRequest->set_selected_contact_profile(contactProfile);
 
-  [_viewController updateSelectedContactInfoUI];
+  [_viewController updateContactInfoSection];
 
   [_contactInfoSelectionCoordinator stop];
   _contactInfoSelectionCoordinator = nil;
@@ -306,7 +303,7 @@
 - (void)shippingAddressSelectionCoordinatorDidReturn:
     (ShippingAddressSelectionCoordinator*)coordinator {
   // Clear the 'Updated' label on the payment summary item, if there is one.
-  [_viewController updatePaymentSummaryWithTotalValueChanged:NO];
+  [_viewController updatePaymentSummaryItem];
 
   [_shippingAddressSelectionCoordinator stop];
   _shippingAddressSelectionCoordinator = nil;
@@ -325,7 +322,7 @@
 - (void)shippingOptionSelectionCoordinatorDidReturn:
     (ShippingAddressSelectionCoordinator*)coordinator {
   // Clear the 'Updated' label on the payment summary item, if there is one.
-  [_viewController updatePaymentSummaryWithTotalValueChanged:NO];
+  [_viewController updatePaymentSummaryItem];
 
   [_shippingOptionSelectionCoordinator stop];
   _shippingOptionSelectionCoordinator = nil;
@@ -338,10 +335,10 @@
                    didSelectPaymentMethod:(autofill::CreditCard*)creditCard {
   _paymentRequest->set_selected_credit_card(creditCard);
 
-  [_viewController updateSelectedPaymentMethodUI];
+  [_viewController updatePaymentMethodSection];
 
   // Clear the 'Updated' label on the payment summary item, if there is one.
-  [_viewController updatePaymentSummaryWithTotalValueChanged:NO];
+  [_viewController updatePaymentSummaryItem];
 
   [_methodSelectionCoordinator stop];
   _methodSelectionCoordinator = nil;
@@ -350,7 +347,7 @@
 - (void)paymentMethodSelectionCoordinatorDidReturn:
     (PaymentMethodSelectionCoordinator*)coordinator {
   // Clear the 'Updated' label on the payment summary item, if there is one.
-  [_viewController updatePaymentSummaryWithTotalValueChanged:NO];
+  [_viewController updatePaymentSummaryItem];
 
   [_methodSelectionCoordinator stop];
   _methodSelectionCoordinator = nil;
diff --git a/ios/chrome/browser/ui/payments/payment_request_mediator.h b/ios/chrome/browser/ui/payments/payment_request_mediator.h
index 7835b29e..e24f2b5 100644
--- a/ios/chrome/browser/ui/payments/payment_request_mediator.h
+++ b/ios/chrome/browser/ui/payments/payment_request_mediator.h
@@ -5,17 +5,27 @@
 #ifndef IOS_CHROME_BROWSER_UI_PAYMENTS_PAYMENT_REQUEST_MEDIATOR_H_
 #define IOS_CHROME_BROWSER_UI_PAYMENTS_PAYMENT_REQUEST_MEDIATOR_H_
 
-#include "ios/chrome/browser/ui/payments/payment_request_view_controller.h"
+#import "ios/chrome/browser/ui/payments/payment_request_view_controller_data_source.h"
 
 namespace ios {
 class ChromeBrowserState;
 }  // namespace ios
 
+class PaymentRequest;
+
 // A mediator object that provides data for a PaymentRequestViewController.
 @interface PaymentRequestMediator
     : NSObject<PaymentRequestViewControllerDataSource>
 
+// Whether or not the total price value was changed by the merchant.
+@property(nonatomic, assign) BOOL totalValueChanged;
+
+// Initializes this object with an instance of ios::ChromeBrowserState and an
+// instance of PaymentRequest which has a copy of web::PaymentRequest as
+// provided by the page invoking the Payment Request API. This object will not
+// take ownership of |browserState| or |paymentRequest|.
 - (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
+                      paymentRequest:(PaymentRequest*)paymentRequest
     NS_DESIGNATED_INITIALIZER;
 
 - (instancetype)init NS_UNAVAILABLE;
diff --git a/ios/chrome/browser/ui/payments/payment_request_mediator.mm b/ios/chrome/browser/ui/payments/payment_request_mediator.mm
index ce5c2d6f..494729a0 100644
--- a/ios/chrome/browser/ui/payments/payment_request_mediator.mm
+++ b/ios/chrome/browser/ui/payments/payment_request_mediator.mm
@@ -2,28 +2,279 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import <Foundation/Foundation.h>
+
 #include "ios/chrome/browser/ui/payments/payment_request_mediator.h"
 
 #include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/autofill/core/browser/autofill_data_util.h"
+#include "components/autofill/core/browser/autofill_profile.h"
+#include "components/autofill/core/browser/credit_card.h"
+#include "components/autofill/core/browser/field_types.h"
+#include "components/payments/core/currency_formatter.h"
+#include "components/payments/core/strings_util.h"
 #include "components/signin/core/browser/signin_manager.h"
+#include "components/strings/grit/components_strings.h"
+#include "ios/chrome/browser/payments/payment_request.h"
+#include "ios/chrome/browser/payments/payment_request_util.h"
 #include "ios/chrome/browser/signin/signin_manager_factory.h"
+#import "ios/chrome/browser/ui/collection_view/cells/collection_view_detail_item.h"
+#import "ios/chrome/browser/ui/collection_view/cells/collection_view_footer_item.h"
+#import "ios/chrome/browser/ui/collection_view/cells/collection_view_item.h"
+#import "ios/chrome/browser/ui/payments/cells/autofill_profile_item.h"
+#import "ios/chrome/browser/ui/payments/cells/payment_method_item.h"
+#import "ios/chrome/browser/ui/payments/cells/payments_text_item.h"
+#import "ios/chrome/browser/ui/payments/cells/price_item.h"
+#include "ios/chrome/browser/ui/uikit_ui_util.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
 
-@implementation PaymentRequestMediator {
-  ios::ChromeBrowserState* _browserState;
-}
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
 
-- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState {
+namespace {
+// String used as the "URL" to take the user to the settings page for card and
+// address options. Needs to be URL-like; otherwise, the link will not appear
+// as a link in the UI (see setLabelLinkURL: in CollectionViewFooterCell).
+const char kSettingsURL[] = "settings://card-and-address";
+
+using ::payments::GetShippingOptionSectionString;
+using ::payment_request_util::GetEmailLabelFromAutofillProfile;
+using ::payment_request_util::GetNameLabelFromAutofillProfile;
+using ::payment_request_util::GetPhoneNumberLabelFromAutofillProfile;
+using ::payment_request_util::GetShippingAddressLabelFromAutofillProfile;
+using ::payment_request_util::GetShippingSectionTitle;
+}  // namespace
+
+@interface PaymentRequestMediator ()
+
+@property(nonatomic, assign) ios::ChromeBrowserState* browserState;
+
+// The PaymentRequest object owning an instance of web::PaymentRequest as
+// provided by the page invoking the Payment Request API. This is a weak
+// pointer and should outlive this class.
+@property(nonatomic, assign) PaymentRequest* paymentRequest;
+
+@end
+
+@implementation PaymentRequestMediator
+
+@synthesize totalValueChanged = _totalValueChanged;
+@synthesize browserState = _browserState;
+@synthesize paymentRequest = _paymentRequest;
+
+- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
+                      paymentRequest:(PaymentRequest*)paymentRequest {
   DCHECK(browserState);
   self = [super init];
   if (self) {
     _browserState = browserState;
+    _paymentRequest = paymentRequest;
   }
   return self;
 }
 
+#pragma mark - PaymentRequestViewControllerDataSource
+
+- (BOOL)canPay {
+  return self.paymentRequest->selected_credit_card() != nullptr &&
+         (self.paymentRequest->selected_shipping_option() != nullptr ||
+          ![self requestShipping]) &&
+         (self.paymentRequest->selected_shipping_profile() != nullptr ||
+          ![self requestShipping]) &&
+         (self.paymentRequest->selected_contact_profile() != nullptr ||
+          ![self requestContactInfo]);
+}
+
+- (BOOL)canShip {
+  return !self.paymentRequest->shipping_options().empty() &&
+         self.paymentRequest->selected_shipping_profile() != nullptr;
+}
+
+- (BOOL)hasPaymentItems {
+  return !self.paymentRequest->payment_details().display_items.empty();
+}
+
+- (BOOL)requestShipping {
+  return self.paymentRequest->request_shipping();
+}
+
+- (BOOL)requestContactInfo {
+  return self.paymentRequest->request_payer_name() ||
+         self.paymentRequest->request_payer_email() ||
+         self.paymentRequest->request_payer_phone();
+}
+
+- (CollectionViewItem*)paymentSummaryItem {
+  PriceItem* item = [[PriceItem alloc] init];
+  item.item = base::SysUTF16ToNSString(
+      self.paymentRequest->payment_details().total.label);
+  payments::CurrencyFormatter* currencyFormatter =
+      self.paymentRequest->GetOrCreateCurrencyFormatter();
+  item.price = SysUTF16ToNSString(l10n_util::GetStringFUTF16(
+      IDS_PAYMENT_REQUEST_ORDER_SUMMARY_SHEET_TOTAL_FORMAT,
+      base::UTF8ToUTF16(currencyFormatter->formatted_currency_code()),
+      currencyFormatter->Format(base::UTF16ToASCII(
+          self.paymentRequest->payment_details().total.amount.value))));
+  item.notification = self.totalValueChanged
+                          ? l10n_util::GetNSString(IDS_PAYMENTS_UPDATED_LABEL)
+                          : nil;
+  self.totalValueChanged = NO;
+  if ([self hasPaymentItems]) {
+    item.accessoryType = MDCCollectionViewCellAccessoryDisclosureIndicator;
+  }
+  return item;
+}
+
+- (CollectionViewItem*)shippingSectionHeaderItem {
+  PaymentsTextItem* item = [[PaymentsTextItem alloc] init];
+  item.text = GetShippingSectionTitle(self.paymentRequest->shipping_type());
+  return item;
+}
+
+- (CollectionViewItem*)shippingAddressItem {
+  const autofill::AutofillProfile* profile =
+      self.paymentRequest->selected_shipping_profile();
+  if (profile) {
+    AutofillProfileItem* item = [[AutofillProfileItem alloc] init];
+    item.name = GetNameLabelFromAutofillProfile(*profile);
+    item.address = GetShippingAddressLabelFromAutofillProfile(*profile);
+    item.phoneNumber = GetPhoneNumberLabelFromAutofillProfile(*profile);
+    item.accessoryType = MDCCollectionViewCellAccessoryDisclosureIndicator;
+    return item;
+  }
+
+  CollectionViewDetailItem* item = [[CollectionViewDetailItem alloc] init];
+  item.text = SysUTF16ToNSString(
+      GetShippingAddressSectionString(self.paymentRequest->shipping_type()));
+  if (self.paymentRequest->shipping_profiles().empty()) {
+    item.detailText = [l10n_util::GetNSString(IDS_ADD)
+        uppercaseStringWithLocale:[NSLocale currentLocale]];
+  } else {
+    item.accessoryType = MDCCollectionViewCellAccessoryDisclosureIndicator;
+  }
+  return item;
+}
+
+- (CollectionViewItem*)shippingOptionItem {
+  const web::PaymentShippingOption* option =
+      self.paymentRequest->selected_shipping_option();
+  if (option) {
+    PaymentsTextItem* item = [[PaymentsTextItem alloc] init];
+    item.text = base::SysUTF16ToNSString(option->label);
+    payments::CurrencyFormatter* currencyFormatter =
+        self.paymentRequest->GetOrCreateCurrencyFormatter();
+    item.detailText = SysUTF16ToNSString(
+        currencyFormatter->Format(base::UTF16ToASCII(option->amount.value)));
+    item.accessoryType = MDCCollectionViewCellAccessoryDisclosureIndicator;
+    return item;
+  }
+
+  CollectionViewDetailItem* item = [[CollectionViewDetailItem alloc] init];
+  item.text = base::SysUTF16ToNSString(
+      GetShippingOptionSectionString(self.paymentRequest->shipping_type()));
+  item.accessoryType = MDCCollectionViewCellAccessoryDisclosureIndicator;
+  return item;
+}
+
+- (CollectionViewItem*)paymentMethodSectionHeaderItem {
+  if (!self.paymentRequest->selected_credit_card())
+    return nil;
+  PaymentsTextItem* item = [[PaymentsTextItem alloc] init];
+  item.text =
+      l10n_util::GetNSString(IDS_PAYMENT_REQUEST_PAYMENT_METHOD_SECTION_NAME);
+  return item;
+}
+
+- (CollectionViewItem*)paymentMethodItem {
+  const autofill::CreditCard* creditCard =
+      self.paymentRequest->selected_credit_card();
+  if (creditCard) {
+    PaymentMethodItem* item = [[PaymentMethodItem alloc] init];
+    item.methodID =
+        base::SysUTF16ToNSString(creditCard->NetworkAndLastFourDigits());
+    item.methodDetail = base::SysUTF16ToNSString(
+        creditCard->GetRawInfo(autofill::CREDIT_CARD_NAME_FULL));
+    int issuerNetworkIconID =
+        autofill::data_util::GetPaymentRequestData(creditCard->network())
+            .icon_resource_id;
+    item.methodTypeIcon = NativeImage(issuerNetworkIconID);
+    item.accessoryType = MDCCollectionViewCellAccessoryDisclosureIndicator;
+    return item;
+  }
+
+  CollectionViewDetailItem* item = [[CollectionViewDetailItem alloc] init];
+  item.text =
+      l10n_util::GetNSString(IDS_PAYMENT_REQUEST_PAYMENT_METHOD_SECTION_NAME);
+  if (self.paymentRequest->credit_cards().empty()) {
+    item.detailText = [l10n_util::GetNSString(IDS_ADD)
+        uppercaseStringWithLocale:[NSLocale currentLocale]];
+  } else {
+    item.accessoryType = MDCCollectionViewCellAccessoryDisclosureIndicator;
+  }
+  return item;
+}
+
+- (CollectionViewItem*)contactInfoSectionHeaderItem {
+  if (!self.paymentRequest->selected_contact_profile())
+    return nil;
+  PaymentsTextItem* item = [[PaymentsTextItem alloc] init];
+  item.text = l10n_util::GetNSString(IDS_PAYMENTS_CONTACT_DETAILS_LABEL);
+  return item;
+}
+
+- (CollectionViewItem*)contactInfoItem {
+  const autofill::AutofillProfile* profile =
+      self.paymentRequest->selected_contact_profile();
+  if (profile) {
+    AutofillProfileItem* item = [[AutofillProfileItem alloc] init];
+    item.name = GetNameLabelFromAutofillProfile(*profile);
+    item.phoneNumber = GetPhoneNumberLabelFromAutofillProfile(*profile);
+    item.email = GetEmailLabelFromAutofillProfile(*profile);
+    item.accessoryType = MDCCollectionViewCellAccessoryDisclosureIndicator;
+    return item;
+  }
+
+  CollectionViewDetailItem* item = [[CollectionViewDetailItem alloc] init];
+  item.text = l10n_util::GetNSString(IDS_PAYMENTS_CONTACT_DETAILS_LABEL);
+  if (self.paymentRequest->contact_profiles().empty()) {
+    item.detailText = [l10n_util::GetNSString(IDS_ADD)
+        uppercaseStringWithLocale:[NSLocale currentLocale]];
+  } else {
+    item.accessoryType = MDCCollectionViewCellAccessoryDisclosureIndicator;
+  }
+  return item;
+}
+
+- (CollectionViewFooterItem*)footerItem {
+  CollectionViewFooterItem* item = [[CollectionViewFooterItem alloc] init];
+
+  // TODO(crbug.com/602666): Find out if the first payment has completed.
+  BOOL firstPaymentCompleted = YES;
+  if (!firstPaymentCompleted) {
+    item.text = l10n_util::GetNSString(IDS_PAYMENTS_CARD_AND_ADDRESS_SETTINGS);
+  } else if ([[self authenticatedAccountName] length]) {
+    const base::string16 accountName =
+        base::SysNSStringToUTF16([self authenticatedAccountName]);
+    const std::string formattedString = l10n_util::GetStringFUTF8(
+        IDS_PAYMENTS_CARD_AND_ADDRESS_SETTINGS_SIGNED_IN, accountName);
+    item.text = base::SysUTF8ToNSString(formattedString);
+  } else {
+    item.text = l10n_util::GetNSString(
+        IDS_PAYMENTS_CARD_AND_ADDRESS_SETTINGS_SIGNED_OUT);
+  }
+  item.linkURL = GURL(kSettingsURL);
+  return item;
+}
+
+#pragma mark - Helper methods
+
+// Returns the authenticated account name, or nil if user is not authenticated.
 - (NSString*)authenticatedAccountName {
   const SigninManager* signinManager =
-      ios::SigninManagerFactory::GetForBrowserStateIfExists(_browserState);
+      ios::SigninManagerFactory::GetForBrowserStateIfExists(self.browserState);
   if (signinManager && signinManager->IsAuthenticated()) {
     return base::SysUTF8ToNSString(
         signinManager->GetAuthenticatedAccountInfo().email);
diff --git a/ios/chrome/browser/ui/payments/payment_request_mediator_unittest.mm b/ios/chrome/browser/ui/payments/payment_request_mediator_unittest.mm
new file mode 100644
index 0000000..3bbd1d6
--- /dev/null
+++ b/ios/chrome/browser/ui/payments/payment_request_mediator_unittest.mm
@@ -0,0 +1,419 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/payments/payment_request_mediator.h"
+
+#import <Foundation/Foundation.h>
+
+#include "base/mac/foundation_util.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/autofill/core/browser/autofill_profile.h"
+#include "components/autofill/core/browser/autofill_test_utils.h"
+#include "components/autofill/core/browser/credit_card.h"
+#include "components/autofill/core/browser/test_personal_data_manager.h"
+#include "components/payments/core/strings_util.h"
+#include "components/signin/core/browser/signin_manager.h"
+#include "components/strings/grit/components_strings.h"
+#include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
+#include "ios/chrome/browser/payments/payment_request_test_util.h"
+#include "ios/chrome/browser/payments/payment_request_util.h"
+#include "ios/chrome/browser/payments/test_payment_request.h"
+#include "ios/chrome/browser/signin/fake_signin_manager_builder.h"
+#include "ios/chrome/browser/signin/signin_manager_factory.h"
+#import "ios/chrome/browser/ui/collection_view/cells/collection_view_detail_item.h"
+#import "ios/chrome/browser/ui/collection_view/cells/collection_view_footer_item.h"
+#import "ios/chrome/browser/ui/payments/cells/autofill_profile_item.h"
+#import "ios/chrome/browser/ui/payments/cells/payment_method_item.h"
+#import "ios/chrome/browser/ui/payments/cells/payments_text_item.h"
+#import "ios/chrome/browser/ui/payments/cells/price_item.h"
+#include "ios/web/public/payments/payment_request.h"
+#include "ios/web/public/test/test_web_thread_bundle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+#include "ui/base/l10n/l10n_util.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+using ::payments::GetShippingOptionSectionString;
+using ::payment_request_util::GetEmailLabelFromAutofillProfile;
+using ::payment_request_util::GetNameLabelFromAutofillProfile;
+using ::payment_request_util::GetPhoneNumberLabelFromAutofillProfile;
+using ::payment_request_util::GetShippingAddressLabelFromAutofillProfile;
+}  // namespace
+
+class PaymentRequestMediatorTest : public PlatformTest {
+ protected:
+  PaymentRequestMediatorTest()
+      : autofill_profile_(autofill::test::GetFullProfile()),
+        credit_card_(autofill::test::GetCreditCard()) {
+    // Add testing profile and credit card to autofill::TestPersonalDataManager.
+    personal_data_manager_.AddTestingProfile(&autofill_profile_);
+    personal_data_manager_.AddTestingCreditCard(&credit_card_);
+
+    payment_request_ = base::MakeUnique<TestPaymentRequest>(
+        payment_request_test_util::CreateTestWebPaymentRequest(),
+        &personal_data_manager_);
+
+    TestChromeBrowserState::Builder test_cbs_builder;
+    test_cbs_builder.AddTestingFactory(ios::SigninManagerFactory::GetInstance(),
+                                       &ios::BuildFakeSigninManager);
+    chrome_browser_state_ = test_cbs_builder.Build();
+    mediator_ = [[PaymentRequestMediator alloc]
+        initWithBrowserState:chrome_browser_state_.get()
+              paymentRequest:payment_request_.get()];
+  }
+
+  PaymentRequestMediator* GetPaymentRequestMediator() { return mediator_; }
+
+  web::TestWebThreadBundle thread_bundle_;
+
+  autofill::AutofillProfile autofill_profile_;
+  autofill::CreditCard credit_card_;
+  autofill::TestPersonalDataManager personal_data_manager_;
+  std::unique_ptr<TestPaymentRequest> payment_request_;
+  std::unique_ptr<TestChromeBrowserState> chrome_browser_state_;
+  PaymentRequestMediator* mediator_;
+};
+
+// Tests whether payment can be completed when expected.
+TEST_F(PaymentRequestMediatorTest, TestCanPay) {
+  // Payment cannot be completed if there is no selected credit card.
+  EXPECT_TRUE([GetPaymentRequestMediator() canPay]);
+  autofill::CreditCard* selected_credit_card =
+      payment_request_->selected_credit_card();
+  payment_request_->set_selected_credit_card(nullptr);
+  EXPECT_FALSE([GetPaymentRequestMediator() canPay]);
+
+  // Restore the selected credit card.
+  payment_request_->set_selected_credit_card(selected_credit_card);
+  EXPECT_TRUE([GetPaymentRequestMediator() canPay]);
+
+  // Payment cannot be completed if there is no selected shipping profile,
+  // unless no shipping information is requested.
+  autofill::AutofillProfile* selected_shipping_profile =
+      payment_request_->selected_shipping_profile();
+  payment_request_->set_selected_shipping_profile(nullptr);
+  EXPECT_FALSE([GetPaymentRequestMediator() canPay]);
+  payment_request_->web_payment_request().options.request_shipping = false;
+  EXPECT_FALSE([GetPaymentRequestMediator() requestShipping]);
+  EXPECT_TRUE([GetPaymentRequestMediator() canPay]);
+
+  // Restore the selected shipping profile and request for shipping information.
+  payment_request_->set_selected_shipping_profile(selected_shipping_profile);
+  payment_request_->web_payment_request().options.request_shipping = true;
+  EXPECT_TRUE([GetPaymentRequestMediator() requestShipping]);
+  EXPECT_TRUE([GetPaymentRequestMediator() canPay]);
+
+  // Payment cannot be completed if there is no selected shipping option,
+  // unless no shipping information is requested.
+  web::PaymentShippingOption* selected_shipping_option =
+      payment_request_->selected_shipping_option();
+  payment_request_->set_selected_shipping_option(nullptr);
+  EXPECT_FALSE([GetPaymentRequestMediator() canPay]);
+  payment_request_->web_payment_request().options.request_shipping = false;
+  EXPECT_TRUE([GetPaymentRequestMediator() canPay]);
+
+  // Restore the selected shipping option and request for shipping information.
+  payment_request_->set_selected_shipping_option(selected_shipping_option);
+  payment_request_->web_payment_request().options.request_shipping = true;
+  EXPECT_TRUE([GetPaymentRequestMediator() canPay]);
+
+  // Payment cannot be completed if there is no selected contact profile, unless
+  // no contact information is requested.
+  payment_request_->set_selected_contact_profile(nullptr);
+  EXPECT_FALSE([GetPaymentRequestMediator() canPay]);
+  payment_request_->web_payment_request().options.request_payer_name = false;
+  EXPECT_TRUE([GetPaymentRequestMediator() requestContactInfo]);
+  EXPECT_FALSE([GetPaymentRequestMediator() canPay]);
+  payment_request_->web_payment_request().options.request_payer_phone = false;
+  EXPECT_TRUE([GetPaymentRequestMediator() requestContactInfo]);
+  EXPECT_FALSE([GetPaymentRequestMediator() canPay]);
+  payment_request_->web_payment_request().options.request_payer_email = false;
+  EXPECT_FALSE([GetPaymentRequestMediator() requestContactInfo]);
+  EXPECT_TRUE([GetPaymentRequestMediator() canPay]);
+}
+
+// Tests that the Payment Summary item is created as expected.
+TEST_F(PaymentRequestMediatorTest, TestPaymentSummaryItem) {
+  EXPECT_TRUE([GetPaymentRequestMediator() hasPaymentItems]);
+
+  // Payment Summary item should be of type PriceItem.
+  id item = [GetPaymentRequestMediator() paymentSummaryItem];
+  ASSERT_TRUE([item isMemberOfClass:[PriceItem class]]);
+  PriceItem* payment_summary_item = base::mac::ObjCCastStrict<PriceItem>(item);
+  EXPECT_TRUE([payment_summary_item.item isEqualToString:@"Total"]);
+  EXPECT_TRUE([payment_summary_item.price isEqualToString:@"USD $1.00"]);
+  EXPECT_EQ(nil, payment_summary_item.notification);
+  EXPECT_EQ(MDCCollectionViewCellAccessoryDisclosureIndicator,
+            payment_summary_item.accessoryType);
+
+  // A label should indicate if the total value was changed.
+  GetPaymentRequestMediator().totalValueChanged = YES;
+  item = [GetPaymentRequestMediator() paymentSummaryItem];
+  payment_summary_item = base::mac::ObjCCastStrict<PriceItem>(item);
+  EXPECT_TRUE([payment_summary_item.notification
+      isEqualToString:l10n_util::GetNSString(IDS_PAYMENTS_UPDATED_LABEL)]);
+
+  // The next time the data source is queried for the Payment Summary item, the
+  // label should disappear.
+  item = [GetPaymentRequestMediator() paymentSummaryItem];
+  payment_summary_item = base::mac::ObjCCastStrict<PriceItem>(item);
+  EXPECT_EQ(nil, payment_summary_item.notification);
+
+  // Remove the display items.
+  web::PaymentRequest web_payment_request =
+      payment_request_->web_payment_request();
+  web_payment_request.details.display_items.clear();
+  payment_request_->UpdatePaymentDetails(web_payment_request.details);
+  EXPECT_FALSE([GetPaymentRequestMediator() hasPaymentItems]);
+
+  // No accessory view indicates there are no display items.
+  item = [GetPaymentRequestMediator() paymentSummaryItem];
+  payment_summary_item = base::mac::ObjCCastStrict<PriceItem>(item);
+  EXPECT_EQ(MDCCollectionViewCellAccessoryNone,
+            payment_summary_item.accessoryType);
+}
+
+// Tests that the Shipping section header item is created as expected.
+TEST_F(PaymentRequestMediatorTest, TestShippingHeaderItem) {
+  // Shipping section header item should be of type PaymentsTextItem.
+  id item = [GetPaymentRequestMediator() shippingSectionHeaderItem];
+  ASSERT_TRUE([item isMemberOfClass:[PaymentsTextItem class]]);
+  PaymentsTextItem* shipping_section_header_item =
+      base::mac::ObjCCastStrict<PaymentsTextItem>(item);
+  EXPECT_TRUE([shipping_section_header_item.text
+      isEqualToString:l10n_util::GetNSString(
+                          IDS_PAYMENTS_SHIPPING_SUMMARY_LABEL)]);
+  EXPECT_EQ(nil, shipping_section_header_item.detailText);
+}
+
+// Tests that the Shipping Address item is created as expected.
+TEST_F(PaymentRequestMediatorTest, TestShippingAddressItem) {
+  // Shipping Address item should be of type AutofillProfileItem.
+  id item = [GetPaymentRequestMediator() shippingAddressItem];
+  ASSERT_TRUE([item isMemberOfClass:[AutofillProfileItem class]]);
+  AutofillProfileItem* shipping_address_item =
+      base::mac::ObjCCastStrict<AutofillProfileItem>(item);
+  EXPECT_TRUE([shipping_address_item.name
+      isEqualToString:GetNameLabelFromAutofillProfile(
+                          *payment_request_->selected_shipping_profile())]);
+  EXPECT_TRUE([shipping_address_item.address
+      isEqualToString:GetShippingAddressLabelFromAutofillProfile(
+                          *payment_request_->selected_shipping_profile())]);
+  EXPECT_TRUE([shipping_address_item.phoneNumber
+      isEqualToString:GetPhoneNumberLabelFromAutofillProfile(
+                          *payment_request_->selected_shipping_profile())]);
+  EXPECT_EQ(MDCCollectionViewCellAccessoryDisclosureIndicator,
+            shipping_address_item.accessoryType);
+
+  // Reset the selected shipping profile.
+  payment_request_->set_selected_shipping_profile(nullptr);
+
+  // When there is no selected shipping address, the Shipping Address item
+  // should be of type CollectionViewDetailItem.
+  item = [GetPaymentRequestMediator() shippingAddressItem];
+  ASSERT_TRUE([item isMemberOfClass:[CollectionViewDetailItem class]]);
+  CollectionViewDetailItem* add_shipping_address_item =
+      base::mac::ObjCCastStrict<CollectionViewDetailItem>(item);
+  EXPECT_TRUE([add_shipping_address_item.text
+      isEqualToString:l10n_util::GetNSString(
+                          IDS_PAYMENTS_SHIPPING_ADDRESS_LABEL)]);
+  EXPECT_EQ(nil, add_shipping_address_item.detailText);
+  EXPECT_EQ(MDCCollectionViewCellAccessoryDisclosureIndicator,
+            add_shipping_address_item.accessoryType);
+
+  // Remove the shipping profiles.
+  payment_request_->ClearShippingProfiles();
+
+  // No accessory view indicates there are no shipping profiles to choose from.
+  item = [GetPaymentRequestMediator() shippingAddressItem];
+  add_shipping_address_item =
+      base::mac::ObjCCastStrict<CollectionViewDetailItem>(item);
+  EXPECT_TRUE([add_shipping_address_item.detailText
+      isEqualToString:[l10n_util::GetNSString(IDS_ADD)
+                          uppercaseStringWithLocale:[NSLocale currentLocale]]]);
+  EXPECT_EQ(MDCCollectionViewCellAccessoryNone,
+            add_shipping_address_item.accessoryType);
+}
+
+// Tests that the Shipping Option item is created as expected.
+TEST_F(PaymentRequestMediatorTest, TestShippingOptionItem) {
+  // Shipping Option item should be of type PaymentsTextItem.
+  id item = [GetPaymentRequestMediator() shippingOptionItem];
+  ASSERT_TRUE([item isMemberOfClass:[PaymentsTextItem class]]);
+  PaymentsTextItem* shipping_option_item =
+      base::mac::ObjCCastStrict<PaymentsTextItem>(item);
+  EXPECT_TRUE([shipping_option_item.text isEqualToString:@"1-Day"]);
+  EXPECT_TRUE([shipping_option_item.detailText isEqualToString:@"$0.99"]);
+  EXPECT_EQ(MDCCollectionViewCellAccessoryDisclosureIndicator,
+            shipping_option_item.accessoryType);
+
+  // Reset the selected shipping option.
+  payment_request_->set_selected_shipping_option(nullptr);
+
+  // When there is no selected shipping option, the Shipping Option item should
+  // be of type CollectionViewDetailItem.
+  item = [GetPaymentRequestMediator() shippingOptionItem];
+  ASSERT_TRUE([item isMemberOfClass:[CollectionViewDetailItem class]]);
+  CollectionViewDetailItem* add_shipping_option_item =
+      base::mac::ObjCCastStrict<CollectionViewDetailItem>(item);
+  EXPECT_TRUE([add_shipping_option_item.text
+      isEqualToString:l10n_util::GetNSString(
+                          IDS_PAYMENTS_SHIPPING_OPTION_LABEL)]);
+  EXPECT_EQ(nil, add_shipping_option_item.detailText);
+  EXPECT_EQ(MDCCollectionViewCellAccessoryDisclosureIndicator,
+            add_shipping_option_item.accessoryType);
+}
+
+// Tests that the Payment Method section header item is created as expected.
+TEST_F(PaymentRequestMediatorTest, TestPaymentMethodHeaderItem) {
+  // Payment Method section header item should be of type PaymentsTextItem.
+  id item = [GetPaymentRequestMediator() paymentMethodSectionHeaderItem];
+  ASSERT_TRUE([item isMemberOfClass:[PaymentsTextItem class]]);
+  PaymentsTextItem* payment_method_section_header_item =
+      base::mac::ObjCCastStrict<PaymentsTextItem>(item);
+  EXPECT_TRUE([payment_method_section_header_item.text
+      isEqualToString:l10n_util::GetNSString(
+                          IDS_PAYMENT_REQUEST_PAYMENT_METHOD_SECTION_NAME)]);
+  EXPECT_EQ(nil, payment_method_section_header_item.detailText);
+}
+
+// Tests that the Payment Method item is created as expected.
+TEST_F(PaymentRequestMediatorTest, TestPaymentMethodItem) {
+  // Payment Method item should be of type PaymentsTextItem.
+  id item = [GetPaymentRequestMediator() paymentMethodItem];
+  ASSERT_TRUE([item isMemberOfClass:[PaymentMethodItem class]]);
+  PaymentMethodItem* payment_method_item =
+      base::mac::ObjCCastStrict<PaymentMethodItem>(item);
+  EXPECT_TRUE([payment_method_item.methodID hasPrefix:@"Visa"]);
+  EXPECT_TRUE([payment_method_item.methodID hasSuffix:@"1111"]);
+  EXPECT_TRUE([payment_method_item.methodDetail isEqualToString:@"Test User"]);
+  EXPECT_EQ(MDCCollectionViewCellAccessoryDisclosureIndicator,
+            payment_method_item.accessoryType);
+
+  // Reset the selected credit card.
+  payment_request_->set_selected_credit_card(nullptr);
+
+  // When there is no selected credit card, the Payment Method item should be of
+  // type CollectionViewDetailItem.
+  item = [GetPaymentRequestMediator() paymentMethodItem];
+  ASSERT_TRUE([item isMemberOfClass:[CollectionViewDetailItem class]]);
+  CollectionViewDetailItem* add_payment_method_item =
+      base::mac::ObjCCastStrict<CollectionViewDetailItem>(item);
+  EXPECT_TRUE([add_payment_method_item.text
+      isEqualToString:l10n_util::GetNSString(
+                          IDS_PAYMENT_REQUEST_PAYMENT_METHOD_SECTION_NAME)]);
+  EXPECT_EQ(nil, add_payment_method_item.detailText);
+  EXPECT_EQ(MDCCollectionViewCellAccessoryDisclosureIndicator,
+            add_payment_method_item.accessoryType);
+
+  // Remove the credit cards.
+  payment_request_->ClearCreditCards();
+
+  // No accessory view indicates there are no payment methods to choose from.
+  item = [GetPaymentRequestMediator() paymentMethodItem];
+  add_payment_method_item =
+      base::mac::ObjCCastStrict<CollectionViewDetailItem>(item);
+  EXPECT_TRUE([add_payment_method_item.detailText
+      isEqualToString:[l10n_util::GetNSString(IDS_ADD)
+                          uppercaseStringWithLocale:[NSLocale currentLocale]]]);
+  EXPECT_EQ(MDCCollectionViewCellAccessoryNone,
+            add_payment_method_item.accessoryType);
+}
+
+// Tests that the Contact Info section header item is created as expected.
+TEST_F(PaymentRequestMediatorTest, TestContactInfoHeaderItem) {
+  // Contact Info section header item should be of type PaymentsTextItem.
+  id item = [GetPaymentRequestMediator() contactInfoSectionHeaderItem];
+  ASSERT_TRUE([item isMemberOfClass:[PaymentsTextItem class]]);
+  PaymentsTextItem* contact_info_section_header_item =
+      base::mac::ObjCCastStrict<PaymentsTextItem>(item);
+  EXPECT_TRUE([contact_info_section_header_item.text
+      isEqualToString:l10n_util::GetNSString(
+                          IDS_PAYMENTS_CONTACT_DETAILS_LABEL)]);
+  EXPECT_EQ(nil, contact_info_section_header_item.detailText);
+}
+
+// Tests that the Contact Info item is created as expected.
+TEST_F(PaymentRequestMediatorTest, TestContactInfoItem) {
+  // Contact Info item should be of type AutofillProfileItem.
+  id item = [GetPaymentRequestMediator() contactInfoItem];
+  ASSERT_TRUE([item isMemberOfClass:[AutofillProfileItem class]]);
+  AutofillProfileItem* contact_info_item =
+      base::mac::ObjCCastStrict<AutofillProfileItem>(item);
+  EXPECT_TRUE([contact_info_item.name
+      isEqualToString:GetNameLabelFromAutofillProfile(
+                          *payment_request_->selected_contact_profile())]);
+  EXPECT_TRUE([contact_info_item.phoneNumber
+      isEqualToString:GetPhoneNumberLabelFromAutofillProfile(
+                          *payment_request_->selected_contact_profile())]);
+  EXPECT_TRUE([contact_info_item.email
+      isEqualToString:GetEmailLabelFromAutofillProfile(
+                          *payment_request_->selected_contact_profile())]);
+  EXPECT_EQ(MDCCollectionViewCellAccessoryDisclosureIndicator,
+            contact_info_item.accessoryType);
+
+  // Reset the selected contact profile.
+  payment_request_->set_selected_contact_profile(nullptr);
+
+  // When there is no selected contact profile, the Payment Method item should
+  // be of type CollectionViewDetailItem.
+  item = [GetPaymentRequestMediator() contactInfoItem];
+  ASSERT_TRUE([item isMemberOfClass:[CollectionViewDetailItem class]]);
+  CollectionViewDetailItem* add_contact_info_item =
+      base::mac::ObjCCastStrict<CollectionViewDetailItem>(item);
+  EXPECT_TRUE([add_contact_info_item.text
+      isEqualToString:l10n_util::GetNSString(
+                          IDS_PAYMENTS_CONTACT_DETAILS_LABEL)]);
+  EXPECT_EQ(nil, add_contact_info_item.detailText);
+  EXPECT_EQ(MDCCollectionViewCellAccessoryDisclosureIndicator,
+            add_contact_info_item.accessoryType);
+
+  // Remove the contact profiles.
+  payment_request_->ClearContactProfiles();
+
+  // No accessory view indicates there are no contact profiles to choose from.
+  item = [GetPaymentRequestMediator() contactInfoItem];
+  add_contact_info_item =
+      base::mac::ObjCCastStrict<CollectionViewDetailItem>(item);
+  EXPECT_TRUE([add_contact_info_item.detailText
+      isEqualToString:[l10n_util::GetNSString(IDS_ADD)
+                          uppercaseStringWithLocale:[NSLocale currentLocale]]]);
+  EXPECT_EQ(MDCCollectionViewCellAccessoryNone,
+            add_contact_info_item.accessoryType);
+}
+
+// Tests that the Footer item is created as expected.
+TEST_F(PaymentRequestMediatorTest, TestFooterItem) {
+  // Make sure the user is signed out.
+  SigninManager* signin_manager = ios::SigninManagerFactory::GetForBrowserState(
+      chrome_browser_state_.get());
+  if (signin_manager->IsAuthenticated()) {
+    signin_manager->SignOut(signin_metrics::SIGNOUT_TEST,
+                            signin_metrics::SignoutDelete::IGNORE_METRIC);
+  }
+
+  // Footer item should be of type CollectionViewFooterItem.
+  id item = [GetPaymentRequestMediator() footerItem];
+  ASSERT_TRUE([item isMemberOfClass:[CollectionViewFooterItem class]]);
+  CollectionViewFooterItem* footer_item =
+      base::mac::ObjCCastStrict<CollectionViewFooterItem>(item);
+  EXPECT_TRUE([footer_item.text
+      isEqualToString:l10n_util::GetNSString(
+                          IDS_PAYMENTS_CARD_AND_ADDRESS_SETTINGS_SIGNED_OUT)]);
+
+  // Fake a signed in user.
+  signin_manager->SetAuthenticatedAccountInfo("12345", "username@example.com");
+
+  item = [GetPaymentRequestMediator() footerItem];
+  footer_item = base::mac::ObjCCastStrict<CollectionViewFooterItem>(item);
+  EXPECT_TRUE([footer_item.text
+      isEqualToString:l10n_util::GetNSStringF(
+                          IDS_PAYMENTS_CARD_AND_ADDRESS_SETTINGS_SIGNED_IN,
+                          base::ASCIIToUTF16("username@example.com"))]);
+}
diff --git a/ios/chrome/browser/ui/payments/payment_request_view_controller.h b/ios/chrome/browser/ui/payments/payment_request_view_controller.h
index a7cf175..9aae684 100644
--- a/ios/chrome/browser/ui/payments/payment_request_view_controller.h
+++ b/ios/chrome/browser/ui/payments/payment_request_view_controller.h
@@ -7,25 +7,13 @@
 
 #import <UIKit/UIKit.h>
 
-#include "ios/chrome/browser/payments/payment_request.h"
-#import "ios/chrome/browser/ui/collection_view/cells/collection_view_footer_item.h"
 #import "ios/chrome/browser/ui/collection_view/collection_view_controller.h"
+#import "ios/chrome/browser/ui/payments/payment_request_view_controller_data_source.h"
 
 extern NSString* const kPaymentRequestCollectionViewID;
 
-class PaymentRequest;
-
 @class PaymentRequestViewController;
 
-// Data source protocol for PaymentRequestViewController.
-@protocol PaymentRequestViewControllerDataSource<NSObject>
-
-// Returns the authenticated account name, if a user is authenticated.
-// Otherwise, returns nil.
-- (NSString*)authenticatedAccountName;
-
-@end
-
 // Delegate protocol for PaymentRequestViewController.
 @protocol PaymentRequestViewControllerDelegate<NSObject>
 
@@ -66,8 +54,7 @@
 
 // View controller responsible for presenting the details of a PaymentRequest to
 // the user and communicating their choices to the supplied delegate.
-@interface PaymentRequestViewController
-    : CollectionViewController<CollectionViewFooterLinkDelegate>
+@interface PaymentRequestViewController : CollectionViewController
 
 // The favicon of the page invoking the Payment Request API.
 @property(nonatomic, strong) UIImage* pageFavicon;
@@ -87,37 +74,23 @@
 // The delegate to be notified when the user confirms or cancels the request.
 @property(nonatomic, weak) id<PaymentRequestViewControllerDelegate> delegate;
 
-// Whether the data source should be shown (usually until the first payment
-// has been completed) or not.
-@property(nonatomic, assign) BOOL showPaymentDataSource;
-
+// The data source for this view controller.
 @property(nonatomic, weak) id<PaymentRequestViewControllerDataSource>
     dataSource;
 
-// Updates the payment summary section UI. If |totalValueChanged| is YES,
-// adds a label to the total amount item indicating that the total amount was
-// updated.
-- (void)updatePaymentSummaryWithTotalValueChanged:(BOOL)totalValueChanged;
+// Updates the payment summary item in the summary section.
+- (void)updatePaymentSummaryItem;
 
-// Updates the selected shipping address.
-- (void)updateSelectedShippingAddressUI;
+// Updates the shipping section.
+- (void)updateShippingSection;
 
-// Updates the selected shipping option.
-- (void)updateSelectedShippingOptionUI;
+// Updates the payment method section.
+- (void)updatePaymentMethodSection;
 
-// Updates the selected payment method.
-- (void)updateSelectedPaymentMethodUI;
+// Updates the contact info section.
+- (void)updateContactInfoSection;
 
-// Updates the selected contact info.
-- (void)updateSelectedContactInfoUI;
-
-// Initializes this object with an instance of PaymentRequest which has a copy
-// of web::PaymentRequest as provided by the page invoking the Payment Request
-// API. This object will not take ownership of |paymentRequest|.
-- (instancetype)initWithPaymentRequest:(PaymentRequest*)paymentRequest
-    NS_DESIGNATED_INITIALIZER;
-
-- (instancetype)init NS_UNAVAILABLE;
+- (instancetype)init NS_DESIGNATED_INITIALIZER;
 
 - (instancetype)initWithStyle:(CollectionViewControllerStyle)style
     NS_UNAVAILABLE;
diff --git a/ios/chrome/browser/ui/payments/payment_request_view_controller.mm b/ios/chrome/browser/ui/payments/payment_request_view_controller.mm
index 83d3a09..3bccff4 100644
--- a/ios/chrome/browser/ui/payments/payment_request_view_controller.mm
+++ b/ios/chrome/browser/ui/payments/payment_request_view_controller.mm
@@ -6,67 +6,34 @@
 
 #include "base/mac/foundation_util.h"
 
-#include "base/strings/sys_string_conversions.h"
-#include "base/strings/utf_string_conversions.h"
-#include "components/autofill/core/browser/autofill_data_util.h"
-#include "components/autofill/core/browser/autofill_profile.h"
-#include "components/autofill/core/browser/credit_card.h"
-#include "components/autofill/core/browser/field_types.h"
-#include "components/autofill/core/browser/personal_data_manager.h"
-#include "components/payments/core/currency_formatter.h"
-#include "components/payments/core/strings_util.h"
 #include "components/strings/grit/components_strings.h"
-#include "ios/chrome/browser/payments/payment_request.h"
-#import "ios/chrome/browser/payments/payment_request_util.h"
 #import "ios/chrome/browser/ui/autofill/cells/status_item.h"
 #import "ios/chrome/browser/ui/collection_view/cells/MDCCollectionViewCell+Chrome.h"
 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_detail_item.h"
 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_footer_item.h"
+#import "ios/chrome/browser/ui/collection_view/cells/collection_view_item+collection_view_controller.h"
 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_item.h"
 #import "ios/chrome/browser/ui/collection_view/collection_view_model.h"
 #import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h"
-#import "ios/chrome/browser/ui/payments/cells/autofill_profile_item.h"
 #import "ios/chrome/browser/ui/payments/cells/page_info_item.h"
-#import "ios/chrome/browser/ui/payments/cells/payment_method_item.h"
-#import "ios/chrome/browser/ui/payments/cells/payments_text_item.h"
 #import "ios/chrome/browser/ui/payments/cells/price_item.h"
 #import "ios/chrome/browser/ui/payments/payment_request_view_controller_actions.h"
 #include "ios/chrome/browser/ui/rtl_geometry.h"
 #include "ios/chrome/browser/ui/uikit_ui_util.h"
 #include "ios/chrome/grit/ios_strings.h"
 #import "ios/third_party/material_components_ios/src/components/Buttons/src/MaterialButtons.h"
-#import "ios/third_party/material_components_ios/src/components/CollectionCells/src/MaterialCollectionCells.h"
 #import "ios/third_party/material_components_ios/src/components/Typography/src/MaterialTypography.h"
-#include "ios/web/public/payments/payment_request.h"
 #include "ui/base/l10n/l10n_util.h"
-#include "ui/base/resource/resource_bundle.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
-namespace {
-using ::payment_request_util::GetNameLabelFromAutofillProfile;
-using ::payment_request_util::GetShippingAddressLabelFromAutofillProfile;
-using ::payment_request_util::GetPhoneNumberLabelFromAutofillProfile;
-using ::payment_request_util::GetEmailLabelFromAutofillProfile;
-using ::payment_request_util::GetShippingSectionTitle;
-using ::payments::GetShippingOptionSectionString;
-using ::payments::GetShippingAddressSectionString;
-
-// String used as the "URL" to take the user to the settings page for card and
-// address options. Needs to be URL-like; otherwise, the link will not appear
-// as a link in the UI (see setLabelLinkURL: in CollectionViewFooterCell).
-const char kSettingsURL[] = "settings://card-and-address";
-
-const CGFloat kFooterCellHorizontalPadding = 16;
-
-}  // namespace
-
 NSString* const kPaymentRequestCollectionViewID =
     @"kPaymentRequestCollectionViewID";
 
 namespace {
+const CGFloat kFooterCellHorizontalPadding = 16;
 
 const CGFloat kButtonEdgeInset = 9;
 const CGFloat kSeparatorEdgeInset = 14;
@@ -85,35 +52,21 @@
   ItemTypeSummaryTotal,
   ItemTypeShippingTitle,
   ItemTypeShippingAddress,
-  ItemTypeAddShippingAddress,
   ItemTypeShippingOption,
-  ItemTypeSelectShippingOption,
-  ItemTypePaymentTitle,
+  ItemTypePaymentHeader,
   ItemTypePaymentMethod,
-  ItemTypeAddPaymentMethod,
-  ItemTypeContactInfoTitle,
+  ItemTypeContactInfoHeader,
   ItemTypeContactInfo,
-  ItemTypeAddContactInfo,
   ItemTypeFooterText,
 };
 
 }  // namespace
 
 @interface PaymentRequestViewController ()<
+    CollectionViewFooterLinkDelegate,
     PaymentRequestViewControllerActions> {
   UIBarButtonItem* _cancelButton;
   MDCButton* _payButton;
-
-  // The PaymentRequest object having a copy of web::PaymentRequest as provided
-  // by the page invoking the Payment Request API. This is a weak pointer and
-  // should outlive this class.
-  PaymentRequest* _paymentRequest;
-
-  __weak PriceItem* _paymentSummaryItem;
-  __weak AutofillProfileItem* _selectedShippingAddressItem;
-  __weak PaymentsTextItem* _selectedShippingOptionItem;
-  __weak PaymentMethodItem* _selectedPaymentMethodItem;
-  __weak AutofillProfileItem* _selectedContactInfoItem;
 }
 
 @end
@@ -126,11 +79,9 @@
 @synthesize connectionSecure = _connectionSecure;
 @synthesize pending = _pending;
 @synthesize delegate = _delegate;
-@synthesize showPaymentDataSource = _showPaymentDataSource;
 @synthesize dataSource = _dataSource;
 
-- (instancetype)initWithPaymentRequest:(PaymentRequest*)paymentRequest {
-  DCHECK(paymentRequest);
+- (instancetype)init {
   if ((self = [super initWithStyle:CollectionViewControllerStyleAppBar])) {
     [self setTitle:l10n_util::GetNSString(IDS_PAYMENTS_TITLE)];
 
@@ -158,7 +109,6 @@
                    action:@selector(onConfirm)
          forControlEvents:UIControlEventTouchUpInside];
     [_payButton sizeToFit];
-    [_payButton setEnabled:(paymentRequest->selected_credit_card() != nil)];
     [_payButton setAutoresizingMask:UIViewAutoresizingFlexibleTrailingMargin() |
                                     UIViewAutoresizingFlexibleTopMargin |
                                     UIViewAutoresizingFlexibleBottomMargin];
@@ -181,11 +131,6 @@
     UIBarButtonItem* payButtonItem =
         [[UIBarButtonItem alloc] initWithCustomView:buttonView];
     [self navigationItem].rightBarButtonItem = payButtonItem;
-
-    _paymentRequest = paymentRequest;
-
-    // By default, data source is shown.
-    _showPaymentDataSource = TRUE;
   }
   return self;
 }
@@ -198,6 +143,13 @@
   [_delegate paymentRequestViewControllerDidConfirm:self];
 }
 
+#pragma mark - Setters
+
+- (void)setDataSource:(id<PaymentRequestViewControllerDataSource>)dataSource {
+  _dataSource = dataSource;
+  [_payButton setEnabled:[_dataSource canPay]];
+}
+
 #pragma mark - CollectionViewController methods
 
 - (void)loadModel {
@@ -225,170 +177,38 @@
     return;
   }
 
-  PriceItem* paymentSummaryItem =
-      [[PriceItem alloc] initWithType:ItemTypeSummaryTotal];
-  _paymentSummaryItem = paymentSummaryItem;
-  [self fillPaymentSummaryItem:paymentSummaryItem
-               withPaymentItem:_paymentRequest->payment_details().total
-         withTotalValueChanged:NO];
-  if (!_paymentRequest->payment_details().display_items.empty()) {
-    paymentSummaryItem.accessoryType =
-        MDCCollectionViewCellAccessoryDisclosureIndicator;
-    paymentSummaryItem.accessibilityTraits |= UIAccessibilityTraitButton;
-  }
-  [model addItem:paymentSummaryItem
-      toSectionWithIdentifier:SectionIdentifierSummary];
+  [self addPaymentSummaryItem];
 
   // Shipping section.
-  [model addSectionWithIdentifier:SectionIdentifierShipping];
+  if ([_dataSource requestShipping]) {
+    [model addSectionWithIdentifier:SectionIdentifierShipping];
 
-  PaymentsTextItem* shippingTitle =
-      [[PaymentsTextItem alloc] initWithType:ItemTypeShippingTitle];
-  shippingTitle.text =
-      GetShippingSectionTitle(_paymentRequest->shipping_type());
-  [model setHeader:shippingTitle
-      forSectionWithIdentifier:SectionIdentifierShipping];
+    CollectionViewItem* shippingSectionHeaderItem =
+        [_dataSource shippingSectionHeaderItem];
+    [shippingSectionHeaderItem setType:ItemTypeShippingTitle];
+    [model setHeader:shippingSectionHeaderItem
+        forSectionWithIdentifier:SectionIdentifierShipping];
 
-  CollectionViewItem* shippingAddressItem = nil;
-  if (_paymentRequest->selected_shipping_profile()) {
-    AutofillProfileItem* selectedShippingAddressItem =
-        [[AutofillProfileItem alloc] initWithType:ItemTypeShippingAddress];
-    shippingAddressItem = selectedShippingAddressItem;
-    _selectedShippingAddressItem = selectedShippingAddressItem;
-    [self fillShippingAddressItem:selectedShippingAddressItem
-              withAutofillProfile:_paymentRequest->selected_shipping_profile()];
-    selectedShippingAddressItem.accessoryType =
-        MDCCollectionViewCellAccessoryDisclosureIndicator;
-    selectedShippingAddressItem.accessibilityTraits |=
-        UIAccessibilityTraitButton;
-  } else {
-    CollectionViewDetailItem* addAddressItem = [[CollectionViewDetailItem alloc]
-        initWithType:ItemTypeAddShippingAddress];
-    shippingAddressItem = addAddressItem;
-    addAddressItem.text = SysUTF16ToNSString(
-        GetShippingAddressSectionString(_paymentRequest->shipping_type()));
-    addAddressItem.detailText = [l10n_util::GetNSString(IDS_ADD)
-        uppercaseStringWithLocale:[NSLocale currentLocale]];
-    addAddressItem.accessibilityTraits |= UIAccessibilityTraitButton;
+    [self populateShippingSection];
   }
-  [model addItem:shippingAddressItem
-      toSectionWithIdentifier:SectionIdentifierShipping];
-
-  CollectionViewItem* shippingOptionItem = nil;
-  if (_paymentRequest->selected_shipping_option()) {
-    PaymentsTextItem* selectedShippingOptionItem =
-        [[PaymentsTextItem alloc] initWithType:ItemTypeShippingOption];
-    shippingOptionItem = selectedShippingOptionItem;
-
-    _selectedShippingOptionItem = selectedShippingOptionItem;
-    [self fillShippingOptionItem:selectedShippingOptionItem
-                      withOption:_paymentRequest->selected_shipping_option()];
-    selectedShippingOptionItem.accessoryType =
-        MDCCollectionViewCellAccessoryDisclosureIndicator;
-    selectedShippingOptionItem.accessibilityTraits |=
-        UIAccessibilityTraitButton;
-  } else {
-    CollectionViewDetailItem* selectShippingOptionItem =
-        [[CollectionViewDetailItem alloc]
-            initWithType:ItemTypeSelectShippingOption];
-    shippingOptionItem = selectShippingOptionItem;
-    selectShippingOptionItem.text = base::SysUTF16ToNSString(
-        GetShippingOptionSectionString(_paymentRequest->shipping_type()));
-    selectShippingOptionItem.accessoryType =
-        MDCCollectionViewCellAccessoryDisclosureIndicator;
-    selectShippingOptionItem.accessibilityTraits |= UIAccessibilityTraitButton;
-  }
-  [model addItem:shippingOptionItem
-      toSectionWithIdentifier:SectionIdentifierShipping];
 
   // Payment method section.
   [model addSectionWithIdentifier:SectionIdentifierPayment];
-
-  CollectionViewItem* paymentMethodItem = nil;
-  if (_paymentRequest->selected_credit_card()) {
-    PaymentsTextItem* paymentTitle =
-        [[PaymentsTextItem alloc] initWithType:ItemTypePaymentTitle];
-    paymentTitle.text =
-        l10n_util::GetNSString(IDS_PAYMENT_REQUEST_PAYMENT_METHOD_SECTION_NAME);
-    [model setHeader:paymentTitle
-        forSectionWithIdentifier:SectionIdentifierPayment];
-
-    PaymentMethodItem* selectedPaymentMethodItem =
-        [[PaymentMethodItem alloc] initWithType:ItemTypePaymentMethod];
-    paymentMethodItem = selectedPaymentMethodItem;
-    _selectedPaymentMethodItem = selectedPaymentMethodItem;
-    [self fillPaymentMethodItem:selectedPaymentMethodItem
-                 withCreditCard:_paymentRequest->selected_credit_card()];
-    selectedPaymentMethodItem.accessoryType =
-        MDCCollectionViewCellAccessoryDisclosureIndicator;
-    selectedPaymentMethodItem.accessibilityTraits |= UIAccessibilityTraitButton;
-  } else {
-    CollectionViewDetailItem* addPaymentMethodItem = [
-        [CollectionViewDetailItem alloc] initWithType:ItemTypeAddPaymentMethod];
-    paymentMethodItem = addPaymentMethodItem;
-    addPaymentMethodItem.text =
-        l10n_util::GetNSString(IDS_PAYMENT_REQUEST_PAYMENT_METHOD_SECTION_NAME);
-    addPaymentMethodItem.detailText = [l10n_util::GetNSString(IDS_ADD)
-        uppercaseStringWithLocale:[NSLocale currentLocale]];
-    addPaymentMethodItem.accessibilityTraits |= UIAccessibilityTraitButton;
-  }
-  [model addItem:paymentMethodItem
-      toSectionWithIdentifier:SectionIdentifierPayment];
+  [self populatePaymentMethodSection];
 
   // Contact Info section.
-  [model addSectionWithIdentifier:SectionIdentifierContactInfo];
-
-  CollectionViewItem* contactInfoItem = nil;
-  if (_paymentRequest->selected_contact_profile()) {
-    PaymentsTextItem* contactInfoTitle =
-        [[PaymentsTextItem alloc] initWithType:ItemTypeContactInfoTitle];
-    contactInfoTitle.text =
-        l10n_util::GetNSString(IDS_PAYMENTS_CONTACT_DETAILS_LABEL);
-    [model setHeader:contactInfoTitle
-        forSectionWithIdentifier:SectionIdentifierContactInfo];
-
-    AutofillProfileItem* selectedContactInfoItem =
-        [[AutofillProfileItem alloc] initWithType:ItemTypeContactInfo];
-    contactInfoItem = selectedContactInfoItem;
-    _selectedContactInfoItem = selectedContactInfoItem;
-    [self fillContactInfoItem:selectedContactInfoItem
-          withAutofillProfile:_paymentRequest->selected_contact_profile()];
-    selectedContactInfoItem.accessoryType =
-        MDCCollectionViewCellAccessoryDisclosureIndicator;
-
-  } else {
-    CollectionViewDetailItem* addContactInfoItem =
-        [[CollectionViewDetailItem alloc] initWithType:ItemTypeAddContactInfo];
-    contactInfoItem = addContactInfoItem;
-    addContactInfoItem.text =
-        l10n_util::GetNSString(IDS_PAYMENTS_CONTACT_DETAILS_LABEL);
-    addContactInfoItem.detailText = [l10n_util::GetNSString(IDS_ADD)
-        uppercaseStringWithLocale:[NSLocale currentLocale]];
-    addContactInfoItem.accessibilityTraits |= UIAccessibilityTraitButton;
+  if ([_dataSource requestContactInfo]) {
+    [model addSectionWithIdentifier:SectionIdentifierContactInfo];
+    [self populateContactInfoSection];
   }
-  [model addItem:contactInfoItem
-      toSectionWithIdentifier:SectionIdentifierContactInfo];
 
   // Footer Text section.
   [model addSectionWithIdentifier:SectionIdentifierFooter];
-  CollectionViewFooterItem* footer =
-      [[CollectionViewFooterItem alloc] initWithType:ItemTypeFooterText];
-  if (!_showPaymentDataSource) {
-    footer.text =
-        l10n_util::GetNSString(IDS_PAYMENTS_CARD_AND_ADDRESS_SETTINGS);
-  } else if ([[_dataSource authenticatedAccountName] length]) {
-    const base::string16 accountName =
-        base::SysNSStringToUTF16([_dataSource authenticatedAccountName]);
-    const std::string formattedString = l10n_util::GetStringFUTF8(
-        IDS_PAYMENTS_CARD_AND_ADDRESS_SETTINGS_SIGNED_IN, accountName);
-    footer.text = base::SysUTF8ToNSString(formattedString);
-  } else {
-    footer.text = l10n_util::GetNSString(
-        IDS_PAYMENTS_CARD_AND_ADDRESS_SETTINGS_SIGNED_OUT);
-  }
-  footer.linkURL = GURL(kSettingsURL);
-  footer.linkDelegate = self;
-  [model addItem:footer toSectionWithIdentifier:SectionIdentifierFooter];
+
+  CollectionViewFooterItem* footerItem = [_dataSource footerItem];
+  [footerItem setType:ItemTypeFooterText];
+  footerItem.linkDelegate = self;
+  [model addItem:footerItem toSectionWithIdentifier:SectionIdentifierFooter];
 }
 
 - (void)viewDidLoad {
@@ -401,110 +221,88 @@
       UIEdgeInsetsMake(0, kSeparatorEdgeInset, 0, kSeparatorEdgeInset);
 }
 
-- (void)updatePaymentSummaryWithTotalValueChanged:(BOOL)totalValueChanged {
-  [self fillPaymentSummaryItem:_paymentSummaryItem
-               withPaymentItem:_paymentRequest->payment_details().total
-         withTotalValueChanged:totalValueChanged];
+- (void)updatePaymentSummaryItem {
+  CollectionViewModel* model = self.collectionViewModel;
+
+  [model removeItemWithType:ItemTypeSummaryTotal
+      fromSectionWithIdentifier:SectionIdentifierSummary];
+
+  [self addPaymentSummaryItem];
+
+  // Reload the item.
   NSIndexPath* indexPath =
-      [self.collectionViewModel indexPathForItem:_paymentSummaryItem];
+      [model indexPathForItemType:ItemTypeSummaryTotal
+                sectionIdentifier:SectionIdentifierSummary];
   [self.collectionView reloadItemsAtIndexPaths:@[ indexPath ]];
 }
 
-- (void)updateSelectedShippingAddressUI {
-  [self fillShippingAddressItem:_selectedShippingAddressItem
-            withAutofillProfile:_paymentRequest->selected_shipping_profile()];
-  NSIndexPath* indexPath =
-      [self.collectionViewModel indexPathForItem:_selectedShippingAddressItem];
-  [self.collectionView reloadItemsAtIndexPaths:@[ indexPath ]];
+- (void)updateShippingSection {
+  CollectionViewModel* model = self.collectionViewModel;
+
+  [model removeItemWithType:ItemTypeShippingAddress
+      fromSectionWithIdentifier:SectionIdentifierShipping];
+
+  if ([model hasItemForItemType:ItemTypeShippingOption
+              sectionIdentifier:SectionIdentifierShipping]) {
+    [model removeItemWithType:ItemTypeShippingOption
+        fromSectionWithIdentifier:SectionIdentifierShipping];
+  }
+
+  [self populateShippingSection];
+
+  // Reload the section.
+  NSInteger sectionIndex =
+      [model sectionForSectionIdentifier:SectionIdentifierShipping];
+  [self.collectionView
+      reloadSections:[NSIndexSet indexSetWithIndex:sectionIndex]];
+
+  // Update the pay button.
+  [_payButton setEnabled:[_dataSource canPay]];
 }
 
-- (void)updateSelectedShippingOptionUI {
-  [self fillShippingOptionItem:_selectedShippingOptionItem
-                    withOption:_paymentRequest->selected_shipping_option()];
-  NSIndexPath* indexPath =
-      [self.collectionViewModel indexPathForItem:_selectedShippingOptionItem];
-  [self.collectionView reloadItemsAtIndexPaths:@[ indexPath ]];
+- (void)updatePaymentMethodSection {
+  CollectionViewModel* model = self.collectionViewModel;
+
+  [model removeItemWithType:ItemTypePaymentMethod
+      fromSectionWithIdentifier:SectionIdentifierPayment];
+
+  [self populatePaymentMethodSection];
+
+  // Reload the section.
+  NSInteger sectionIndex =
+      [model sectionForSectionIdentifier:SectionIdentifierPayment];
+  [self.collectionView
+      reloadSections:[NSIndexSet indexSetWithIndex:sectionIndex]];
+
+  // Update the pay button.
+  [_payButton setEnabled:[_dataSource canPay]];
 }
 
-- (void)updateSelectedPaymentMethodUI {
-  [self fillPaymentMethodItem:_selectedPaymentMethodItem
-               withCreditCard:_paymentRequest->selected_credit_card()];
-  NSIndexPath* indexPath =
-      [self.collectionViewModel indexPathForItem:_selectedPaymentMethodItem];
-  [self.collectionView reloadItemsAtIndexPaths:@[ indexPath ]];
-}
+- (void)updateContactInfoSection {
+  CollectionViewModel* model = self.collectionViewModel;
 
-- (void)updateSelectedContactInfoUI {
-  [self fillContactInfoItem:_selectedContactInfoItem
-        withAutofillProfile:_paymentRequest->selected_contact_profile()];
-  NSIndexPath* indexPath =
-      [self.collectionViewModel indexPathForItem:_selectedContactInfoItem];
-  [self.collectionView reloadItemsAtIndexPaths:@[ indexPath ]];
-}
+  [model removeItemWithType:ItemTypeContactInfo
+      fromSectionWithIdentifier:SectionIdentifierContactInfo];
 
-#pragma mark - Helper methods
+  [self populateContactInfoSection];
 
-- (void)fillPaymentSummaryItem:(PriceItem*)item
-               withPaymentItem:(web::PaymentItem)paymentItem
-         withTotalValueChanged:(BOOL)totalValueChanged {
-  item.item =
-      base::SysUTF16ToNSString(_paymentRequest->payment_details().total.label);
-  payments::CurrencyFormatter* currencyFormatter =
-      _paymentRequest->GetOrCreateCurrencyFormatter();
-  item.price = SysUTF16ToNSString(l10n_util::GetStringFUTF16(
-      IDS_PAYMENT_REQUEST_ORDER_SUMMARY_SHEET_TOTAL_FORMAT,
-      base::UTF8ToUTF16(currencyFormatter->formatted_currency_code()),
-      currencyFormatter->Format(base::UTF16ToASCII(paymentItem.amount.value))));
-  item.notification = totalValueChanged
-                          ? l10n_util::GetNSString(IDS_PAYMENTS_UPDATED_LABEL)
-                          : nil;
-}
+  // Reload the section.
+  NSInteger sectionIndex =
+      [model sectionForSectionIdentifier:SectionIdentifierContactInfo];
+  [self.collectionView
+      reloadSections:[NSIndexSet indexSetWithIndex:sectionIndex]];
 
-- (void)fillShippingAddressItem:(AutofillProfileItem*)item
-            withAutofillProfile:(autofill::AutofillProfile*)profile {
-  DCHECK(profile);
-  item.name = GetNameLabelFromAutofillProfile(*profile);
-  item.address = GetShippingAddressLabelFromAutofillProfile(*profile);
-  item.phoneNumber = GetPhoneNumberLabelFromAutofillProfile(*profile);
-}
-
-- (void)fillShippingOptionItem:(PaymentsTextItem*)item
-                    withOption:(web::PaymentShippingOption*)option {
-  item.text = base::SysUTF16ToNSString(option->label);
-  payments::CurrencyFormatter* currencyFormatter =
-      _paymentRequest->GetOrCreateCurrencyFormatter();
-  item.detailText = SysUTF16ToNSString(
-      currencyFormatter->Format(base::UTF16ToASCII(option->amount.value)));
-}
-
-- (void)fillPaymentMethodItem:(PaymentMethodItem*)item
-               withCreditCard:(autofill::CreditCard*)creditCard {
-  item.methodID =
-      base::SysUTF16ToNSString(creditCard->NetworkAndLastFourDigits());
-  item.methodDetail = base::SysUTF16ToNSString(
-      creditCard->GetRawInfo(autofill::CREDIT_CARD_NAME_FULL));
-  int issuerNetworkIconID =
-      autofill::data_util::GetPaymentRequestData(creditCard->network())
-          .icon_resource_id;
-  item.methodTypeIcon = NativeImage(issuerNetworkIconID);
-}
-
-- (void)fillContactInfoItem:(AutofillProfileItem*)item
-        withAutofillProfile:(autofill::AutofillProfile*)profile {
-  DCHECK(profile);
-  item.name = GetNameLabelFromAutofillProfile(*profile);
-  item.phoneNumber = GetPhoneNumberLabelFromAutofillProfile(*profile);
-  item.email = GetEmailLabelFromAutofillProfile(*profile);
+  // Update the pay button.
+  [_payButton setEnabled:[_dataSource canPay]];
 }
 
 #pragma mark - CollectionViewFooterLinkDelegate
 
 - (void)cell:(CollectionViewFooterCell*)cell didTapLinkURL:(GURL)url {
-  DCHECK_EQ(url, GURL(kSettingsURL)) << "Unknown URL tapped";
   [_delegate paymentRequestViewControllerDidSelectSettings:self];
 }
 
-#pragma mark UICollectionViewDataSource
+#pragma mark - UICollectionViewDataSource
 
 - (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView
                  cellForItemAtIndexPath:(nonnull NSIndexPath*)indexPath {
@@ -514,12 +312,17 @@
   NSInteger itemType =
       [self.collectionViewModel itemTypeForIndexPath:indexPath];
   switch (itemType) {
-    case ItemTypeAddShippingAddress: {
-      CollectionViewDetailCell* detailCell =
-          base::mac::ObjCCastStrict<CollectionViewDetailCell>(cell);
-      detailCell.detailTextLabel.font = [MDCTypography body2Font];
-      detailCell.detailTextLabel.textColor =
-          [[MDCPalette cr_bluePalette] tint700];
+    case ItemTypeShippingAddress:
+    case ItemTypePaymentMethod:
+    case ItemTypeShippingOption:
+    case ItemTypeContactInfo: {
+      if ([cell isKindOfClass:[CollectionViewDetailCell class]]) {
+        CollectionViewDetailCell* detailCell =
+            base::mac::ObjCCastStrict<CollectionViewDetailCell>(cell);
+        detailCell.detailTextLabel.font = [MDCTypography body2Font];
+        detailCell.detailTextLabel.textColor =
+            [[MDCPalette cr_bluePalette] tint700];
+      }
       break;
     }
     case ItemTypeFooterText: {
@@ -537,7 +340,7 @@
   return cell;
 }
 
-#pragma mark UICollectionViewDelegate
+#pragma mark - UICollectionViewDelegate
 
 - (void)collectionView:(UICollectionView*)collectionView
     didSelectItemAtIndexPath:(NSIndexPath*)indexPath {
@@ -547,24 +350,19 @@
       [self.collectionViewModel itemTypeForIndexPath:indexPath];
   switch (itemType) {
     case ItemTypeSummaryTotal:
-      if (!_paymentRequest->payment_details().display_items.empty())
         [_delegate
             paymentRequestViewControllerDidSelectPaymentSummaryItem:self];
       break;
     case ItemTypeShippingAddress:
-    case ItemTypeAddShippingAddress:
       [_delegate paymentRequestViewControllerDidSelectShippingAddressItem:self];
       break;
     case ItemTypeShippingOption:
-    case ItemTypeSelectShippingOption:
       [_delegate paymentRequestViewControllerDidSelectShippingOptionItem:self];
       break;
     case ItemTypePaymentMethod:
-    case ItemTypeAddPaymentMethod:
       [_delegate paymentRequestViewControllerDidSelectPaymentMethodItem:self];
       break;
     case ItemTypeContactInfo:
-    case ItemTypeAddContactInfo:
       [_delegate paymentRequestViewControllerDidSelectContactInfoItem:self];
       break;
     case ItemTypeFooterText:
@@ -577,7 +375,7 @@
   }
 }
 
-#pragma mark MDCCollectionViewStylingDelegate
+#pragma mark - MDCCollectionViewStylingDelegate
 
 - (CGFloat)collectionView:(UICollectionView*)collectionView
     cellHeightAtIndexPath:(NSIndexPath*)indexPath {
@@ -586,23 +384,15 @@
   switch (item.type) {
     case ItemTypeSpinner:
     case ItemTypeShippingAddress:
+    case ItemTypeShippingOption:
     case ItemTypePaymentMethod:
     case ItemTypeContactInfo:
     case ItemTypeFooterText:
       return [MDCCollectionViewCell
           cr_preferredHeightForWidth:CGRectGetWidth(collectionView.bounds)
                              forItem:item];
-    case ItemTypeShippingOption:
-      return MDCCellDefaultTwoLineHeight;
     case ItemTypeSummaryPageInfo:
     case ItemTypeSummaryTotal:
-    case ItemTypeShippingTitle:
-    case ItemTypeAddShippingAddress:
-    case ItemTypeSelectShippingOption:
-    case ItemTypePaymentTitle:
-    case ItemTypeAddPaymentMethod:
-    case ItemTypeContactInfoTitle:
-    case ItemTypeAddContactInfo:
       return MDCCellDefaultOneLineHeight;
     default:
       NOTREACHED();
@@ -616,8 +406,7 @@
   // If there are no payment items to display, there is no effect from touching
   // the total so there should not be an ink ripple. The footer should also not
   // have a ripple.
-  if ((type == ItemTypeSummaryTotal &&
-       _paymentRequest->payment_details().display_items.empty()) ||
+  if ((type == ItemTypeSummaryTotal && ![_dataSource hasPaymentItems]) ||
       (type == ItemTypeFooterText)) {
     return YES;
   } else {
@@ -633,4 +422,69 @@
   return sectionIdentifier == SectionIdentifierFooter ? YES : NO;
 }
 
+#pragma mark - Helper methods
+
+- (void)addPaymentSummaryItem {
+  CollectionViewItem* item = [_dataSource paymentSummaryItem];
+  [item setType:ItemTypeSummaryTotal];
+  if ([_dataSource hasPaymentItems])
+    item.accessibilityTraits |= UIAccessibilityTraitButton;
+  [self.collectionViewModel addItem:item
+            toSectionWithIdentifier:SectionIdentifierSummary];
+}
+
+- (void)populateShippingSection {
+  CollectionViewModel* model = self.collectionViewModel;
+
+  CollectionViewItem* shippingAddressItem = [_dataSource shippingAddressItem];
+  [shippingAddressItem setType:ItemTypeShippingAddress];
+  shippingAddressItem.accessibilityTraits |= UIAccessibilityTraitButton;
+  [model addItem:shippingAddressItem
+      toSectionWithIdentifier:SectionIdentifierShipping];
+
+  if ([_dataSource canShip]) {
+    CollectionViewItem* shippingOptionItem = [_dataSource shippingOptionItem];
+    [shippingOptionItem setType:ItemTypeShippingOption];
+    shippingOptionItem.accessibilityTraits |= UIAccessibilityTraitButton;
+    [model addItem:shippingOptionItem
+        toSectionWithIdentifier:SectionIdentifierShipping];
+  }
+}
+
+- (void)populatePaymentMethodSection {
+  CollectionViewModel* model = self.collectionViewModel;
+
+  CollectionViewItem* paymentMethodSectionHeaderItem =
+      [_dataSource paymentMethodSectionHeaderItem];
+  if (paymentMethodSectionHeaderItem) {
+    [paymentMethodSectionHeaderItem setType:ItemTypePaymentHeader];
+    [model setHeader:paymentMethodSectionHeaderItem
+        forSectionWithIdentifier:SectionIdentifierPayment];
+  }
+
+  CollectionViewItem* paymentMethodItem = [_dataSource paymentMethodItem];
+  [paymentMethodItem setType:ItemTypePaymentMethod];
+  paymentMethodItem.accessibilityTraits |= UIAccessibilityTraitButton;
+  [model addItem:paymentMethodItem
+      toSectionWithIdentifier:SectionIdentifierPayment];
+}
+
+- (void)populateContactInfoSection {
+  CollectionViewModel* model = self.collectionViewModel;
+
+  CollectionViewItem* contactInfoSectionHeaderItem =
+      [_dataSource contactInfoSectionHeaderItem];
+  if (contactInfoSectionHeaderItem) {
+    [contactInfoSectionHeaderItem setType:ItemTypeContactInfoHeader];
+    [model setHeader:contactInfoSectionHeaderItem
+        forSectionWithIdentifier:SectionIdentifierContactInfo];
+  }
+
+  CollectionViewItem* contactInfoItem = [_dataSource contactInfoItem];
+  [contactInfoItem setType:ItemTypeContactInfo];
+  contactInfoItem.accessibilityTraits |= UIAccessibilityTraitButton;
+  [model addItem:contactInfoItem
+      toSectionWithIdentifier:SectionIdentifierContactInfo];
+}
+
 @end
diff --git a/ios/chrome/browser/ui/payments/payment_request_view_controller_data_source.h b/ios/chrome/browser/ui/payments/payment_request_view_controller_data_source.h
new file mode 100644
index 0000000..1f130b3
--- /dev/null
+++ b/ios/chrome/browser/ui/payments/payment_request_view_controller_data_source.h
@@ -0,0 +1,64 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_PAYMENTS_PAYMENT_REQUEST_VIEW_CONTROLLER_DATA_SOURCE_H_
+#define IOS_CHROME_BROWSER_UI_PAYMENTS_PAYMENT_REQUEST_VIEW_CONTROLLER_DATA_SOURCE_H_
+
+#import <Foundation/Foundation.h>
+
+@class CollectionViewFooterItem;
+@class CollectionViewItem;
+
+// Data source protocol for PaymentRequestViewController.
+@protocol PaymentRequestViewControllerDataSource
+
+// Returns whether the payment can be made and therefore the pay button should
+// be enabled.
+- (BOOL)canPay;
+
+// Returns whether shipment can be done and therefore the shipping options
+// should be presented.
+- (BOOL)canShip;
+
+// Returns whether the total price is itemized.
+- (BOOL)hasPaymentItems;
+
+// Returns whether shipping is requested and therefore the Shipping section
+// should be presented.
+- (BOOL)requestShipping;
+
+// Returns whether contact information is requested and therefore the Contact
+// Info section should be presented.
+- (BOOL)requestContactInfo;
+
+// Returns the Payment Summary item displayed in the Summary section.
+- (CollectionViewItem*)paymentSummaryItem;
+
+// Returns the header item for the Shipping section.
+- (CollectionViewItem*)shippingSectionHeaderItem;
+
+// Returns the Shipping Address item displayed in the Shipping section.
+- (CollectionViewItem*)shippingAddressItem;
+
+// Returns the Shipping Option item displayed in the Shipping section.
+- (CollectionViewItem*)shippingOptionItem;
+
+// Returns the header item for the Payment Method section.
+- (CollectionViewItem*)paymentMethodSectionHeaderItem;
+
+// Returns the item displayed in the Payment Method section.
+- (CollectionViewItem*)paymentMethodItem;
+
+// Returns the header item for the Contact Info section.
+- (CollectionViewItem*)contactInfoSectionHeaderItem;
+
+// Returns the item displayed in the Contact Info section.
+- (CollectionViewItem*)contactInfoItem;
+
+// Returns the item displayed at the bottom of the view.
+- (CollectionViewFooterItem*)footerItem;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_PAYMENTS_PAYMENT_REQUEST_VIEW_CONTROLLER_DATA_SOURCE_H_
diff --git a/ios/chrome/browser/ui/payments/payment_request_view_controller_unittest.mm b/ios/chrome/browser/ui/payments/payment_request_view_controller_unittest.mm
index f42b497a..ad35d58 100644
--- a/ios/chrome/browser/ui/payments/payment_request_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/payments/payment_request_view_controller_unittest.mm
@@ -4,6 +4,8 @@
 
 #import "ios/chrome/browser/ui/payments/payment_request_view_controller.h"
 
+#import <Foundation/Foundation.h>
+
 #include "base/mac/foundation_util.h"
 #include "base/memory/ptr_util.h"
 #include "base/strings/utf_string_conversions.h"
@@ -12,16 +14,18 @@
 #include "components/autofill/core/browser/credit_card.h"
 #include "components/autofill/core/browser/test_personal_data_manager.h"
 #include "components/strings/grit/components_strings.h"
-#include "ios/chrome/browser/payments/payment_request.h"
 #include "ios/chrome/browser/payments/payment_request_test_util.h"
+#include "ios/chrome/browser/payments/test_payment_request.h"
 #import "ios/chrome/browser/ui/autofill/cells/status_item.h"
 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_detail_item.h"
+#import "ios/chrome/browser/ui/collection_view/cells/collection_view_footer_item.h"
 #import "ios/chrome/browser/ui/collection_view/collection_view_controller_test.h"
 #import "ios/chrome/browser/ui/payments/cells/autofill_profile_item.h"
 #import "ios/chrome/browser/ui/payments/cells/page_info_item.h"
 #import "ios/chrome/browser/ui/payments/cells/payment_method_item.h"
 #import "ios/chrome/browser/ui/payments/cells/payments_text_item.h"
 #import "ios/chrome/browser/ui/payments/cells/price_item.h"
+#import "ios/chrome/browser/ui/payments/payment_request_view_controller_data_source.h"
 #include "ios/chrome/grit/ios_strings.h"
 #include "ios/web/public/payments/payment_request.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -31,6 +35,107 @@
 #error "This file requires ARC support."
 #endif
 
+@interface TestPaymentRequestMediator
+    : NSObject<PaymentRequestViewControllerDataSource>
+
+@end
+
+@implementation TestPaymentRequestMediator
+
+- (BOOL)canPay {
+  return YES;
+}
+
+- (BOOL)canShip {
+  return YES;
+}
+
+- (BOOL)hasPaymentItems {
+  return YES;
+}
+
+- (BOOL)requestShipping {
+  return YES;
+}
+
+- (BOOL)requestContactInfo {
+  return YES;
+}
+
+- (CollectionViewItem*)paymentSummaryItem {
+  return [[PriceItem alloc] init];
+}
+
+- (CollectionViewItem*)shippingSectionHeaderItem {
+  return [[PaymentsTextItem alloc] init];
+}
+
+- (CollectionViewItem*)shippingAddressItem {
+  return [[AutofillProfileItem alloc] init];
+}
+
+- (CollectionViewItem*)shippingOptionItem {
+  return [[PaymentsTextItem alloc] init];
+}
+
+- (CollectionViewItem*)paymentMethodSectionHeaderItem {
+  return [[PaymentsTextItem alloc] init];
+}
+
+- (CollectionViewItem*)paymentMethodItem {
+  return [[PaymentMethodItem alloc] init];
+}
+
+- (CollectionViewItem*)contactInfoSectionHeaderItem {
+  return [[PaymentsTextItem alloc] init];
+}
+
+- (CollectionViewItem*)contactInfoItem {
+  return [[AutofillProfileItem alloc] init];
+}
+
+- (CollectionViewFooterItem*)footerItem {
+  return [[CollectionViewFooterItem alloc] init];
+}
+
+@end
+
+@interface TestPaymentRequestMediatorNoShipping : TestPaymentRequestMediator
+
+@end
+
+@implementation TestPaymentRequestMediatorNoShipping
+
+- (BOOL)requestShipping {
+  return NO;
+}
+
+@end
+
+@interface TestPaymentRequestMediatorNoContactInfo : TestPaymentRequestMediator
+
+@end
+
+@implementation TestPaymentRequestMediatorNoContactInfo
+
+- (BOOL)requestContactInfo {
+  return NO;
+}
+
+@end
+
+@interface TestPaymentRequestMediatorCantShip : TestPaymentRequestMediator
+
+@end
+
+@implementation TestPaymentRequestMediatorCantShip
+
+- (BOOL)canShip {
+  return NO;
+}
+
+@end
+
 class PaymentRequestViewControllerTest : public CollectionViewControllerTest {
  protected:
   PaymentRequestViewControllerTest()
@@ -39,15 +144,19 @@
     // Add testing profile and credit card to autofill::TestPersonalDataManager.
     personal_data_manager_.AddTestingProfile(&autofill_profile_);
     personal_data_manager_.AddTestingCreditCard(&credit_card_);
-  }
 
-  CollectionViewController* InstantiateController() override {
-    payment_request_ = base::MakeUnique<PaymentRequest>(
+    payment_request_ = base::MakeUnique<TestPaymentRequest>(
         payment_request_test_util::CreateTestWebPaymentRequest(),
         &personal_data_manager_);
 
-    return [[PaymentRequestViewController alloc]
-        initWithPaymentRequest:payment_request_.get()];
+    mediator_ = [[TestPaymentRequestMediator alloc] init];
+  }
+
+  CollectionViewController* InstantiateController() override {
+    PaymentRequestViewController* viewController =
+        [[PaymentRequestViewController alloc] init];
+    [viewController setDataSource:mediator_];
+    return viewController;
   }
 
   PaymentRequestViewController* GetPaymentRequestViewController() {
@@ -58,7 +167,8 @@
   autofill::AutofillProfile autofill_profile_;
   autofill::CreditCard credit_card_;
   autofill::TestPersonalDataManager personal_data_manager_;
-  std::unique_ptr<PaymentRequest> payment_request_;
+  std::unique_ptr<TestPaymentRequest> payment_request_;
+  TestPaymentRequestMediator* mediator_;
 };
 
 // Tests that the correct items are displayed after loading the model.
@@ -69,17 +179,14 @@
 
   [GetPaymentRequestViewController() loadModel];
 
-  // There should be five sections in total. Summary, Shipping, Payment,
-  // Contact info and a footer.
+  // There should be five sections in total. Summary, Shipping, Payment Method,
+  // Contact Info and the Footer.
   ASSERT_EQ(5, NumberOfSections());
 
   // The only item in the Summary section should be of type PriceItem.
   ASSERT_EQ(1U, static_cast<unsigned int>(NumberOfItemsInSection(0)));
   id item = GetCollectionViewItem(0, 0);
   EXPECT_TRUE([item isMemberOfClass:[PriceItem class]]);
-  PriceItem* price_item = item;
-  EXPECT_EQ(MDCCollectionViewCellAccessoryDisclosureIndicator,
-            price_item.accessoryType);
 
   // There should be two items in the Shipping section.
   ASSERT_EQ(2U, static_cast<unsigned int>(NumberOfItemsInSection(1)));
@@ -87,103 +194,129 @@
   // The first one should be of type AutofillProfileItem.
   item = GetCollectionViewItem(1, 0);
   EXPECT_TRUE([item isMemberOfClass:[AutofillProfileItem class]]);
-  AutofillProfileItem* shipping_address_item = item;
-  EXPECT_EQ(MDCCollectionViewCellAccessoryDisclosureIndicator,
-            shipping_address_item.accessoryType);
 
   // The next item should be of type PaymentsTextItem.
   item = GetCollectionViewItem(1, 1);
   EXPECT_TRUE([item isMemberOfClass:[PaymentsTextItem class]]);
-  PaymentsTextItem* shipping_option_item = item;
-  EXPECT_EQ(MDCCollectionViewCellAccessoryDisclosureIndicator,
-            shipping_option_item.accessoryType);
 
-  // The only item in the Payment section should be of type PaymentMethodItem.
+  // The only item in the Payment Method section should be of type
+  // PaymentMethodItem.
   ASSERT_EQ(1U, static_cast<unsigned int>(NumberOfItemsInSection(2)));
   item = GetCollectionViewItem(2, 0);
   EXPECT_TRUE([item isMemberOfClass:[PaymentMethodItem class]]);
 
-  // The only item in the Contact info section should be of type
+  // The only item in the Contact Info section should be of type
   // AutofillProfileItem.
   ASSERT_EQ(1U, static_cast<unsigned int>(NumberOfItemsInSection(3)));
   item = GetCollectionViewItem(3, 0);
   EXPECT_TRUE([item isMemberOfClass:[AutofillProfileItem class]]);
+
+  // The only item in the Footer section should be of type
+  // CollectionViewFooterItem.
+  ASSERT_EQ(1U, static_cast<unsigned int>(NumberOfItemsInSection(4)));
+  item = GetCollectionViewItem(4, 0);
+  EXPECT_TRUE([item isMemberOfClass:[CollectionViewFooterItem class]]);
 }
 
-// Tests that the correct items are displayed after loading the model, when
-// there are no display items.
-TEST_F(PaymentRequestViewControllerTest, TestModelNoDisplayItem) {
+// Tests that the correct items are displayed after loading the model, when no
+// shipping information is requested.
+TEST_F(PaymentRequestViewControllerTest, TestModelNoShipping) {
+  mediator_ = [[TestPaymentRequestMediatorNoShipping alloc] init];
+
   CreateController();
   CheckController();
 
-  payment_request_->UpdatePaymentDetails(web::PaymentDetails());
-  [GetPaymentRequestViewController() loadModel];
+  // There should be four sections in total now.
+  ASSERT_EQ(4, NumberOfSections());
 
-  // The only item in the Summary section should stil be of type PriceItem, but
-  // without an accessory view.
-  ASSERT_EQ(1U, static_cast<unsigned int>(NumberOfItemsInSection(0)));
-  id item = GetCollectionViewItem(0, 0);
-  EXPECT_TRUE([item isMemberOfClass:[PriceItem class]]);
-  PriceItem* price_item = item;
-  EXPECT_EQ(MDCCollectionViewCellAccessoryNone, price_item.accessoryType);
+  // The second section is the Payment Method section isntead of the Shipping
+  // section.
+  ASSERT_EQ(1U, static_cast<unsigned int>(NumberOfItemsInSection(1)));
+  CollectionViewItem* item = GetCollectionViewItem(1, 0);
+  EXPECT_TRUE([item isMemberOfClass:[PaymentMethodItem class]]);
 }
 
-// Tests that the correct items are displayed after loading the model, when
-// there is no selected shipping addresse.
-TEST_F(PaymentRequestViewControllerTest, TestModelNoSelectedShippingAddress) {
+// Tests that the correct items are displayed after loading the model, when no
+// contact information is requested.
+TEST_F(PaymentRequestViewControllerTest, TestModelNoContactInfo) {
+  mediator_ = [[TestPaymentRequestMediatorNoContactInfo alloc] init];
+
   CreateController();
   CheckController();
 
-  payment_request_->set_selected_shipping_profile(nullptr);
-  [GetPaymentRequestViewController() loadModel];
+  // There should be four sections in total now.
+  ASSERT_EQ(4, NumberOfSections());
 
-  // There should still be two items in the Shipping section.
-  ASSERT_EQ(2U, static_cast<unsigned int>(NumberOfItemsInSection(1)));
+  // The fourth section is the Footer section instead of the Contact Info
+  // section.
+  ASSERT_EQ(1U, static_cast<unsigned int>(NumberOfItemsInSection(3)));
+  CollectionViewItem* item = GetCollectionViewItem(3, 0);
+  EXPECT_TRUE([item isMemberOfClass:[CollectionViewFooterItem class]]);
+}
 
-  // The first one should be of type CollectionViewDetailItem.
+// Tests that the correct items are displayed after loading the model, when
+// shipping can't be made.
+TEST_F(PaymentRequestViewControllerTest, TestModelCantShip) {
+  mediator_ = [[TestPaymentRequestMediatorCantShip alloc] init];
+
+  CreateController();
+  CheckController();
+
+  // There should only be one item in the Shipping section and it should be of
+  // type AutofillProfileItem.
+  ASSERT_EQ(1U, static_cast<unsigned int>(NumberOfItemsInSection(1)));
   id item = GetCollectionViewItem(1, 0);
-  EXPECT_TRUE([item isMemberOfClass:[CollectionViewDetailItem class]]);
-  CollectionViewDetailItem* detail_item = item;
-  EXPECT_EQ(MDCCollectionViewCellAccessoryNone, detail_item.accessoryType);
+  EXPECT_TRUE([item isMemberOfClass:[AutofillProfileItem class]]);
 }
 
-// Tests that the correct items are displayed after loading the model, when
-// there is no selected shipping option.
-TEST_F(PaymentRequestViewControllerTest, TestModelNoSelectedShippingOption) {
+// Tests that the correct items are displayed after updating the Shipping
+// section.
+TEST_F(PaymentRequestViewControllerTest, TestUpdateShippingSection) {
   CreateController();
   CheckController();
 
-  // Resetting the payment details should reset the selected shipping option.
-  payment_request_->UpdatePaymentDetails(web::PaymentDetails());
-  [GetPaymentRequestViewController() loadModel];
+  [GetPaymentRequestViewController() updateShippingSection];
 
-  // There should still be two items in the Shipping section.
+  // There should be two items in the Shipping section.
   ASSERT_EQ(2U, static_cast<unsigned int>(NumberOfItemsInSection(1)));
 
-  // The second one should be of type CollectionViewDetailItem.
-  id item = GetCollectionViewItem(1, 1);
-  EXPECT_TRUE([item isMemberOfClass:[CollectionViewDetailItem class]]);
-  CollectionViewDetailItem* detail_item = item;
-  EXPECT_EQ(MDCCollectionViewCellAccessoryDisclosureIndicator,
-            detail_item.accessoryType);
+  // The first one should be of type AutofillProfileItem.
+  id item = GetCollectionViewItem(1, 0);
+  EXPECT_TRUE([item isMemberOfClass:[AutofillProfileItem class]]);
+
+  // The next item should be of type PaymentsTextItem.
+  item = GetCollectionViewItem(1, 1);
+  EXPECT_TRUE([item isMemberOfClass:[PaymentsTextItem class]]);
 }
 
-// Tests that the correct items are displayed after loading the model, when
-// there is no selected payment method.
-TEST_F(PaymentRequestViewControllerTest, TestModelNoSelectedPaymentMethod) {
+// Tests that the correct items are displayed after updating the Payment Method
+// section.
+TEST_F(PaymentRequestViewControllerTest, TestUpdatePaymentMethodSection) {
   CreateController();
   CheckController();
 
-  payment_request_->set_selected_credit_card(nullptr);
-  [GetPaymentRequestViewController() loadModel];
+  [GetPaymentRequestViewController() updatePaymentMethodSection];
 
-  // The only item in the Payment section should be of type
-  // CollectionViewDetailItem.
+  // The only item in the Payment Method section should be of type
+  // PaymentMethodItem.
   ASSERT_EQ(1U, static_cast<unsigned int>(NumberOfItemsInSection(2)));
   id item = GetCollectionViewItem(2, 0);
-  EXPECT_TRUE([item isMemberOfClass:[CollectionViewDetailItem class]]);
-  CollectionViewDetailItem* detail_item = item;
-  EXPECT_EQ(MDCCollectionViewCellAccessoryNone, detail_item.accessoryType);
+  EXPECT_TRUE([item isMemberOfClass:[PaymentMethodItem class]]);
+}
+
+// Tests that the correct items are displayed after updating the Contact Info
+// section.
+TEST_F(PaymentRequestViewControllerTest, TestUpdateContactInfoSection) {
+  CreateController();
+  CheckController();
+
+  [GetPaymentRequestViewController() updatePaymentMethodSection];
+
+  // The only item in the Contact Info section should be of type
+  // AutofillProfileItem.
+  ASSERT_EQ(1U, static_cast<unsigned int>(NumberOfItemsInSection(3)));
+  id item = GetCollectionViewItem(3, 0);
+  EXPECT_TRUE([item isMemberOfClass:[AutofillProfileItem class]]);
 }
 
 // Tests that the correct items are displayed after loading the model, when
@@ -203,13 +336,3 @@
   id item = GetCollectionViewItem(0, 0);
   EXPECT_TRUE([item isMemberOfClass:[StatusItem class]]);
 }
-
-TEST_F(PaymentRequestViewControllerTest, TestSignedInStringFormatting) {
-  const std::string unformattedString = l10n_util::GetStringUTF8(
-      IDS_PAYMENTS_CARD_AND_ADDRESS_SETTINGS_SIGNED_IN);
-  const std::string formattedString = l10n_util::GetStringFUTF8(
-      IDS_PAYMENTS_CARD_AND_ADDRESS_SETTINGS_SIGNED_IN,
-      base::ASCIIToUTF16("example@gmail.com"));
-
-  EXPECT_NE(unformattedString, formattedString);
-}
diff --git a/ios/chrome/test/earl_grey/BUILD.gn b/ios/chrome/test/earl_grey/BUILD.gn
index 2606082..601576e 100644
--- a/ios/chrome/test/earl_grey/BUILD.gn
+++ b/ios/chrome/test/earl_grey/BUILD.gn
@@ -77,6 +77,7 @@
 }
 
 chrome_ios_eg_test("ios_chrome_multitasking_egtests") {
+  configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
     "//ios/chrome/app/multitasking_test_application_delegate.h",
     "//ios/chrome/app/multitasking_test_application_delegate.mm",
diff --git a/media/base/watch_time_keys.cc b/media/base/watch_time_keys.cc
index f473039b..243d580 100644
--- a/media/base/watch_time_keys.cc
+++ b/media/base/watch_time_keys.cc
@@ -69,6 +69,16 @@
 const char kMeanTimeBetweenRebuffersAudioVideoEme[] =
     "Media.MeanTimeBetweenRebuffers.AudioVideo.EME";
 
+const char kRebuffersCountAudioSrc[] = "Media.RebuffersCount.Audio.SRC";
+const char kRebuffersCountAudioMse[] = "Media.RebuffersCount.Audio.MSE";
+const char kRebuffersCountAudioEme[] = "Media.RebuffersCount.Audio.EME";
+const char kRebuffersCountAudioVideoSrc[] =
+    "Media.RebuffersCount.AudioVideo.SRC";
+const char kRebuffersCountAudioVideoMse[] =
+    "Media.RebuffersCount.AudioVideo.MSE";
+const char kRebuffersCountAudioVideoEme[] =
+    "Media.RebuffersCount.AudioVideo.EME";
+
 base::flat_set<base::StringPiece> GetWatchTimeKeys() {
   return base::flat_set<base::StringPiece>(
       {
diff --git a/media/base/watch_time_keys.h b/media/base/watch_time_keys.h
index 5b948f1..e7dcdfa5 100644
--- a/media/base/watch_time_keys.h
+++ b/media/base/watch_time_keys.h
@@ -58,6 +58,14 @@
 MEDIA_EXPORT extern const char kMeanTimeBetweenRebuffersAudioVideoMse[];
 MEDIA_EXPORT extern const char kMeanTimeBetweenRebuffersAudioVideoEme[];
 
+// Whether there were any rebuffers within a given watch time report.
+MEDIA_EXPORT extern const char kRebuffersCountAudioSrc[];
+MEDIA_EXPORT extern const char kRebuffersCountAudioMse[];
+MEDIA_EXPORT extern const char kRebuffersCountAudioEme[];
+MEDIA_EXPORT extern const char kRebuffersCountAudioVideoSrc[];
+MEDIA_EXPORT extern const char kRebuffersCountAudioVideoMse[];
+MEDIA_EXPORT extern const char kRebuffersCountAudioVideoEme[];
+
 MEDIA_EXPORT base::flat_set<base::StringPiece> GetWatchTimeKeys();
 MEDIA_EXPORT base::flat_set<base::StringPiece> GetWatchTimePowerKeys();
 MEDIA_EXPORT base::flat_set<base::StringPiece> GetWatchTimeControlsKeys();
diff --git a/media/capture/video/android/java/src/org/chromium/media/VideoCaptureCamera2.java b/media/capture/video/android/java/src/org/chromium/media/VideoCaptureCamera2.java
index 1a25383..5919ed1 100644
--- a/media/capture/video/android/java/src/org/chromium/media/VideoCaptureCamera2.java
+++ b/media/capture/video/android/java/src/org/chromium/media/VideoCaptureCamera2.java
@@ -8,6 +8,7 @@
 import android.content.Context;
 import android.graphics.ImageFormat;
 import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
@@ -24,6 +25,7 @@
 import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.SystemClock;
 import android.util.Range;
 import android.util.Size;
 import android.util.SparseIntArray;
@@ -55,7 +57,7 @@
         public void onOpened(CameraDevice cameraDevice) {
             mCameraDevice = cameraDevice;
             changeCameraStateAndNotify(CameraState.CONFIGURING);
-            if (createPreviewObjectsAndStartPreview()) return;
+            if (createPreviewObjectsAndCaptureSession()) return;
 
             changeCameraStateAndNotify(CameraState.STOPPED);
             nativeOnError(mNativeVideoCaptureDeviceAndroid, "Error configuring camera");
@@ -80,35 +82,12 @@
 
     // Inner class to extend a Capture Session state change listener.
     private class CrPreviewSessionListener extends CameraCaptureSession.StateCallback {
-        private final CaptureRequest mPreviewRequest;
-        CrPreviewSessionListener(CaptureRequest previewRequest) {
-            mPreviewRequest = previewRequest;
-        }
-
         @Override
         public void onConfigured(CameraCaptureSession cameraCaptureSession) {
             Log.d(TAG, "CrPreviewSessionListener.onConfigured");
-            mPreviewSession = cameraCaptureSession;
-            try {
-                // This line triggers the preview. A |listener| is registered to receive the actual
-                // capture result details. A CrImageReaderListener will be triggered every time a
-                // downloaded image is ready. Since |handler| is null, we'll work on the current
-                // Thread Looper.
-                mPreviewSession.setRepeatingRequest(
-                        mPreviewRequest, new CameraCaptureSession.CaptureCallback() {
-                            @Override
-                            public void onCaptureCompleted(CameraCaptureSession session,
-                                    CaptureRequest request, TotalCaptureResult result) {
-                                mLastExposureTimeNs =
-                                        result.get(CaptureResult.SENSOR_EXPOSURE_TIME);
-                            }
-                        }, null);
+            mCaptureSession = cameraCaptureSession;
+            restartPreview(null);
 
-            } catch (CameraAccessException | SecurityException | IllegalStateException
-                    | IllegalArgumentException ex) {
-                Log.e(TAG, "setRepeatingRequest: ", ex);
-                return;
-            }
             // Now wait for trigger on CrPreviewReaderListener.onImageAvailable();
             nativeOnStarted(mNativeVideoCaptureDeviceAndroid);
             changeCameraStateAndNotify(CameraState.STARTED);
@@ -116,8 +95,7 @@
 
         @Override
         public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
-            // TODO(mcasas): When signalling error, C++ will tear us down. Is there need for
-            // cleanup?
+            // TODO(mcasas): When signalling error, C++ will tear us down. Do we need to clean up?
             changeCameraStateAndNotify(CameraState.STOPPED);
             nativeOnError(mNativeVideoCaptureDeviceAndroid, "Camera session configuration error");
         }
@@ -158,41 +136,8 @@
         }
     };
 
-    // Inner class to extend a Photo Session state change listener.
-    // Error paths must signal notifyTakePhotoError().
-    private class CrPhotoSessionListener extends CameraCaptureSession.StateCallback {
-        private final CaptureRequest mPhotoRequest;
-        private final long mCallbackId;
-        CrPhotoSessionListener(CaptureRequest photoRequest, long callbackId) {
-            mPhotoRequest = photoRequest;
-            mCallbackId = callbackId;
-        }
-
-        @Override
-        public void onConfigured(CameraCaptureSession session) {
-            Log.d(TAG, "CrPhotoSessionListener.onConfigured");
-            try {
-                // This line triggers a single photo capture. No |listener| is registered, so we
-                // will get notified via a CrPhotoSessionListener. Since |handler| is null, we'll
-                // work on the current Thread Looper.
-                session.capture(mPhotoRequest, null, null);
-            } catch (CameraAccessException e) {
-                Log.e(TAG, "capture() error");
-                notifyTakePhotoError(mCallbackId);
-                return;
-            }
-        }
-
-        @Override
-        public void onConfigureFailed(CameraCaptureSession session) {
-            Log.e(TAG, "failed configuring capture session");
-            notifyTakePhotoError(mCallbackId);
-            return;
-        }
-    };
-
     // Internal class implementing an ImageReader listener for encoded Photos.
-    // Gets pinged when a new Image is been captured.
+    // Gets pinged when a new Image has been captured.
     private class CrPhotoReaderListener implements ImageReader.OnImageAvailableListener {
         private final long mCallbackId;
         CrPhotoReaderListener(long callbackId) {
@@ -215,6 +160,7 @@
 
         @Override
         public void onImageAvailable(ImageReader reader) {
+            Log.d(TAG, "onImageAvailable()");
             try (Image image = reader.acquireLatestImage()) {
                 if (image == null) {
                     throw new IllegalStateException();
@@ -233,35 +179,15 @@
                 return;
             }
 
-            if (createPreviewObjectsAndStartPreview()) return;
+            if (restartPreview(null)) return;
 
             nativeOnError(mNativeVideoCaptureDeviceAndroid, "Error restarting preview");
         }
     };
 
-    // Inner Runnable to reconfigure the preview session, must be run on application context looper.
-    private final Runnable mReconfigureCaptureTask = new Runnable() {
-        @Override
-        public void run() {
-            ThreadUtils.assertOnUiThread();
-            assert mPreviewRequestBuilder != null : "preview request builder";
-            assert mPreviewSession != null : "preview session";
-
-            // Reuse most of |mPreviewRequestBuilder| since it has expensive items inside that have
-            // to do with preview, e.g. the ImageReader and its associated Surface.
-            configureCommonCaptureSettings(mPreviewRequestBuilder);
-
-            try {
-                mPreviewSession.setRepeatingRequest(mPreviewRequestBuilder.build(), null, null);
-            } catch (CameraAccessException | SecurityException | IllegalStateException
-                    | IllegalArgumentException ex) {
-                Log.e(TAG, "setRepeatingRequest: ", ex);
-            }
-        }
-    };
-
     private static final double kNanoSecondsToFps = 1.0E-9;
     private static final String TAG = "VideoCapture";
+    private static final long PRECAPTURE_TIMEOUT_MS = 1000;
 
     // Map of the equivalent color temperature in Kelvin for the White Balance setting. The
     // values are a mixture of educated guesses and data from Android's Camera2 API. The
@@ -283,11 +209,15 @@
     private final Object mCameraStateLock = new Object();
 
     private CameraDevice mCameraDevice;
-    private CameraCaptureSession mPreviewSession;
-    private CaptureRequest mPreviewRequest;
+    private CameraCaptureSession mCaptureSession;
     private CaptureRequest.Builder mPreviewRequestBuilder;
+
     private Handler mMainHandler;
-    private ImageReader mImageReader = null;
+    private Handler mBackgroundHandler;
+
+    private ImageReader mPreviewReader;
+    private ImageReader mPhotoReader;
+    private Surface mPrecaptureSurface;
 
     private Range<Integer> mAeFpsRange;
     private CameraState mCameraState = CameraState.STOPPED;
@@ -326,18 +256,33 @@
         nativeOnPhotoTaken(mNativeVideoCaptureDeviceAndroid, callbackId, new byte[0]);
     }
 
-    private boolean createPreviewObjectsAndStartPreview() {
-        if (mCameraDevice == null) return false;
+    // Convenience method to call setRepeatingRequest() and catch its potential Exceptions.
+    private boolean restartPreview(Handler handler) {
+        Log.d(TAG, "restartPreview()");
+        try {
+            // (Re)trigger the preview. The registered CaptureCallback will receive the actual
+            // capture result parameters. If |handler| is null we'll work on the current Thread
+            // Looper.
+            mCaptureSession.setRepeatingRequest(
+                    mPreviewRequestBuilder.build(), new CameraCaptureSession.CaptureCallback() {
+                        @Override
+                        public void onCaptureCompleted(CameraCaptureSession session,
+                                CaptureRequest request, TotalCaptureResult result) {
+                            mLastExposureTimeNs = result.get(CaptureResult.SENSOR_EXPOSURE_TIME);
+                        }
+                    }, handler);
 
-        // Create an ImageReader and plug a thread looper into it to have
-        // readback take place on its own thread.
-        mImageReader = ImageReader.newInstance(mCaptureFormat.getWidth(),
-                mCaptureFormat.getHeight(), mCaptureFormat.getPixelFormat(), 2 /* maxImages */);
-        HandlerThread thread = new HandlerThread("CameraPreview");
-        thread.start();
-        final Handler backgroundHandler = new Handler(thread.getLooper());
-        final CrPreviewReaderListener imageReaderListener = new CrPreviewReaderListener();
-        mImageReader.setOnImageAvailableListener(imageReaderListener, backgroundHandler);
+        } catch (CameraAccessException | SecurityException | IllegalStateException
+                | IllegalArgumentException ex) {
+            Log.e(TAG, "mCaptureSession.setRepeatingRequest: ", ex);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean createPreviewObjectsAndCaptureSession() {
+        Log.d(TAG, "createPreviewObjectsAndCaptureSession()");
+        if (mCameraDevice == null) return false;
 
         try {
             // TEMPLATE_PREVIEW specifically means "high frame rate is given
@@ -354,11 +299,15 @@
             return false;
         }
 
-        // Construct an ImageReader Surface and plug it into our CaptureRequest.Builder.
-        mPreviewRequestBuilder.addTarget(mImageReader.getSurface());
+        // Create an ImageReader with a background thread looper in it to have readback take place
+        // on its own thread, and use its Surface for the preview.
+        mPreviewReader = ImageReader.newInstance(mCaptureFormat.getWidth(),
+                mCaptureFormat.getHeight(), mCaptureFormat.getPixelFormat(), 2 /* maxImages */);
+        final CrPreviewReaderListener imageReaderListener = new CrPreviewReaderListener();
+        mPreviewReader.setOnImageAvailableListener(imageReaderListener, mBackgroundHandler);
+        mPreviewRequestBuilder.addTarget(mPreviewReader.getSurface());
 
-        // A series of configuration options in the PreviewBuilder
-        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
+        configureCommonCaptureSettings(mPreviewRequestBuilder);
         mPreviewRequestBuilder.set(
                 CaptureRequest.NOISE_REDUCTION_MODE, CameraMetadata.NOISE_REDUCTION_MODE_FAST);
         mPreviewRequestBuilder.set(CaptureRequest.EDGE_MODE, CameraMetadata.EDGE_MODE_FAST);
@@ -377,19 +326,31 @@
             }
         }
 
-        configureCommonCaptureSettings(mPreviewRequestBuilder);
+        // Create another ImageReader on the same background thread to retrieve photos, but do not
+        // connect its Surface to the preview request yet.
+        final StreamConfigurationMap streamMap =
+                cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+        final Size[] supportedSizes = streamMap.getOutputSizes(ImageFormat.JPEG);
+        // |supportedSizes[0]| is the largest.
+        mPhotoReader = ImageReader.newInstance(supportedSizes[0].getWidth(),
+                supportedSizes[0].getHeight(), ImageFormat.JPEG, 1 /* maxImages */);
 
-        List<Surface> surfaceList = new ArrayList<Surface>(1);
-        // TODO(mcasas): release this Surface when not needed, https://crbug.com/643884.
-        surfaceList.add(mImageReader.getSurface());
+        // A dummy Surface for precapture operations.  We could also have another ImageReader and
+        // make sure to flush it, but it's not worth it.
+        SurfaceTexture preview = new SurfaceTexture(/*arbitrary value*/ 1);
+        mPrecaptureSurface = new Surface(preview);
 
-        mPreviewRequest = mPreviewRequestBuilder.build();
+        List<Surface> surfaceList = new ArrayList<Surface>(3);
+        // Make sure both ImageReader's Surfaces are registered with the session.
+        // TODO(mcasas): release these Surfaces when not needed, https://crbug.com/643884.
+        surfaceList.add(mPreviewReader.getSurface());
+        surfaceList.add(mPhotoReader.getSurface());
+        surfaceList.add(mPrecaptureSurface);
 
         try {
-            mCameraDevice.createCaptureSession(
-                    surfaceList, new CrPreviewSessionListener(mPreviewRequest), null);
+            mCameraDevice.createCaptureSession(surfaceList, new CrPreviewSessionListener(), null);
         } catch (CameraAccessException | IllegalArgumentException | SecurityException ex) {
-            Log.e(TAG, "createCaptureSession: ", ex);
+            Log.e(TAG, "mCameraDevice.createCaptureSession(): ", ex);
             return false;
         }
         // Wait for trigger on CrPreviewSessionListener.onConfigured();
@@ -416,10 +377,9 @@
 
         // |mExposureMode|, |mFillLightMode| and |mTorch| interact to configure the AE and Flash
         // modes. In a nutshell, FLASH_MODE is only effective if the auto-exposure is ON/OFF,
-        // otherwise the auto-exposure related flash control (ON_{AUTO,ALWAYS}_FLASH{_REDEYE) takes
-        // priority.  |mTorch| mode overrides any previous |mFillLightMode| flash control.
-        if (mExposureMode == AndroidMeteringMode.NONE
-                || mExposureMode == AndroidMeteringMode.FIXED) {
+        // otherwise the auto-exposure related flash control overrides it. |mTorch| mode overrides
+        // any previous |mFillLightMode| flash control.
+        if (mExposureMode == AndroidMeteringMode.FIXED) {
             requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CameraMetadata.CONTROL_AE_MODE_OFF);
 
             // We need to configure by hand the exposure time when AE mode is off.  Set it to the
@@ -443,33 +403,28 @@
         }
 
         if (mTorch) {
-            requestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
-                    mExposureMode == AndroidMeteringMode.CONTINUOUS
-                            ? CameraMetadata.CONTROL_AE_MODE_ON
-                            : CameraMetadata.CONTROL_AE_MODE_OFF);
             requestBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH);
         } else {
-            switch (mFillLightMode) {
-                case AndroidFillLightMode.OFF:
-                    requestBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_OFF);
-                    break;
-                case AndroidFillLightMode.AUTO:
-                    // Setting the AE to CONTROL_AE_MODE_ON_AUTO_FLASH[_REDEYE] overrides
-                    // FLASH_MODE.
-                    requestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
-                            mRedEyeReduction ? CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE
-                                             : CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH);
-                    break;
-                case AndroidFillLightMode.FLASH:
-                    // Setting the AE to CONTROL_AE_MODE_ON_ALWAYS_FLASH overrides FLASH_MODE.
-                    requestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
-                            CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
-                    requestBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_SINGLE);
-                    break;
-                default:
+            requestBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_OFF);
+
+            // Do not set FLASH_MODE (see https://stackoverflow.com/a/36069908); anyway either of
+            // CONTROL_AE_MODE_ON_{ALWAYS_FLASH,AUTO_FLASH,AUTO_FLASH_REDEYE} overrides FLASH_MODE.
+            if (mFillLightMode == AndroidFillLightMode.AUTO) {
+                Log.d(TAG, "configureCommonCaptureSettings() Flash set to Auto");
+                requestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
+                        mRedEyeReduction ? CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE
+                                         : CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH);
+            } else if (mFillLightMode == AndroidFillLightMode.FLASH) {
+                Log.d(TAG, "configureCommonCaptureSettings() Flash set to Always");
+                requestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
+                        CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
+            } else {
+                // The case |mFillLightMode| == AndroidFillLightMode.OFF is already covered by
+                // setting CONTROL_AE_MODE to simply either ON/OFF while handling |mExposureMode|.
+                final int aeMode = requestBuilder.get(CaptureRequest.CONTROL_AE_MODE);
+                assert aeMode == CameraMetadata.CONTROL_AE_MODE_ON
+                        || aeMode == CameraMetadata.CONTROL_AE_MODE_OFF;
             }
-            requestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
-                    CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE);
         }
 
         requestBuilder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, mExposureCompensation);
@@ -743,6 +698,10 @@
             mMainHandler = new Handler(thread.getLooper());
         }
 
+        HandlerThread thread = new HandlerThread("CameraPreview");
+        thread.start();
+        mBackgroundHandler = new Handler(thread.getLooper());
+
         final CrStateListener stateListener = new CrStateListener();
         try {
             manager.openCamera(Integer.toString(mId), stateListener, mMainHandler);
@@ -773,7 +732,7 @@
         }
 
         try {
-            mPreviewSession.abortCaptures();
+            mCaptureSession.abortCaptures();
         } catch (CameraAccessException | IllegalStateException ex) {
             // Stopping a device whose CameraCaptureSession is closed is not an error: ignore this.
             Log.w(TAG, "abortCaptures: ", ex);
@@ -803,7 +762,7 @@
             maxIso = iso_range.getUpper();
         }
         builder.setMinIso(minIso).setMaxIso(maxIso).setStepIso(1);
-        builder.setCurrentIso(mPreviewRequest.get(CaptureRequest.SENSOR_SENSITIVITY));
+        builder.setCurrentIso(mPreviewRequestBuilder.get(CaptureRequest.SENSOR_SENSITIVITY));
 
         final StreamConfigurationMap streamMap =
                 cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
@@ -826,7 +785,7 @@
         final float currentZoom =
                 cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE)
                         .width()
-                / (float) mPreviewRequest.get(CaptureRequest.SCALER_CROP_REGION).width();
+                / (float) mPreviewRequestBuilder.get(CaptureRequest.SCALER_CROP_REGION).width();
         // There is no min-zoom per se, so clamp it to always 1.
         builder.setMinZoom(1.0).setMaxZoom(mMaxZoom);
         builder.setCurrentZoom(currentZoom).setStepZoom(0.1);
@@ -855,7 +814,7 @@
         }
         builder.setFocusModes(integerArrayListToArray(focusModes));
 
-        final int focusMode = mPreviewRequest.get(CaptureRequest.CONTROL_AF_MODE);
+        final int focusMode = mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AF_MODE);
         int jniFocusMode = AndroidMeteringMode.NONE;
         if (focusMode == CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_VIDEO
                 || focusMode == CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE) {
@@ -895,11 +854,11 @@
         builder.setExposureModes(integerArrayListToArray(exposureModes));
 
         int jniExposureMode = AndroidMeteringMode.CONTINUOUS;
-        if (mPreviewRequest.get(CaptureRequest.CONTROL_AE_MODE)
+        if (mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AE_MODE)
                 == CameraMetadata.CONTROL_AE_MODE_OFF) {
             jniExposureMode = AndroidMeteringMode.NONE;
         }
-        if (mPreviewRequest.get(CaptureRequest.CONTROL_AE_LOCK)) {
+        if (mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AE_LOCK)) {
             jniExposureMode = AndroidMeteringMode.FIXED;
         }
         builder.setExposureMode(jniExposureMode);
@@ -913,7 +872,7 @@
         builder.setMinExposureCompensation(exposureCompensationRange.getLower() * step);
         builder.setMaxExposureCompensation(exposureCompensationRange.getUpper() * step);
         builder.setCurrentExposureCompensation(
-                mPreviewRequest.get(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION) * step);
+                mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION) * step);
 
         final int[] jniWhiteBalanceMode =
                 cameraCharacteristics.get(CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES);
@@ -933,7 +892,7 @@
         }
         builder.setWhiteBalanceModes(integerArrayListToArray(whiteBalanceModes));
 
-        final int whiteBalanceMode = mPreviewRequest.get(CaptureRequest.CONTROL_AWB_MODE);
+        final int whiteBalanceMode = mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AWB_MODE);
         if (whiteBalanceMode == CameraMetadata.CONTROL_AWB_MODE_OFF) {
             builder.setWhiteBalanceMode(AndroidMeteringMode.NONE);
         } else if (whiteBalanceMode == CameraMetadata.CONTROL_AWB_MODE_AUTO) {
@@ -957,7 +916,7 @@
             // There's no way to query if torch and/or red eye reduction modes are available using
             // Camera2 API but since there's a Flash unit, we assume so.
             builder.setSupportsTorch(true);
-            builder.setTorch(mPreviewRequest.get(CaptureRequest.FLASH_MODE)
+            builder.setTorch(mPreviewRequestBuilder.get(CaptureRequest.FLASH_MODE)
                     == CameraMetadata.FLASH_MODE_TORCH);
 
             builder.setRedEyeReduction(true);
@@ -986,6 +945,7 @@
             double exposureCompensation, int whiteBalanceMode, double iso,
             boolean hasRedEyeReduction, boolean redEyeReduction, int fillLightMode,
             boolean hasTorch, boolean torch, double colorTemperature) {
+        Log.d(TAG, "setPhotoOptions()");
         ThreadUtils.assertOnUiThread();
         final CameraCharacteristics cameraCharacteristics = getCameraCharacteristics(mId);
         final Rect canvas =
@@ -1058,42 +1018,19 @@
         if (fillLightMode != AndroidFillLightMode.NOT_SET) mFillLightMode = fillLightMode;
         if (hasTorch) mTorch = torch;
 
-        final Handler mainHandler =
-                new Handler(ContextUtils.getApplicationContext().getMainLooper());
-        mainHandler.removeCallbacks(mReconfigureCaptureTask);
-        mainHandler.post(mReconfigureCaptureTask);
+        // Reuse most of |mPreviewRequestBuilder| since it has expensive items inside that have
+        // to do with preview, e.g. the ImageReader and its associated Surface.
+        configureCommonCaptureSettings(mPreviewRequestBuilder);
+        restartPreview(mBackgroundHandler);
     }
 
     @Override
     public boolean takePhoto(final long callbackId) {
+        Log.d(TAG, "takePhoto()");
         ThreadUtils.assertOnUiThread();
-        if (mCameraDevice == null || mCameraState != CameraState.STARTED) return false;
-
-        final CameraCharacteristics cameraCharacteristics = getCameraCharacteristics(mId);
-        final StreamConfigurationMap streamMap =
-                cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
-        final Size[] supportedSizes = streamMap.getOutputSizes(ImageFormat.JPEG);
-        final Size closestSize = findClosestSizeInArray(supportedSizes, mPhotoWidth, mPhotoHeight);
-
-        Log.d(TAG, "requested resolution: (%dx%d)", mPhotoWidth, mPhotoHeight);
-        if (closestSize != null) {
-            Log.d(TAG, " matched (%dx%d)", closestSize.getWidth(), closestSize.getHeight());
-        }
-        final ImageReader imageReader = ImageReader.newInstance(
-                (closestSize != null) ? closestSize.getWidth() : mCaptureFormat.getWidth(),
-                (closestSize != null) ? closestSize.getHeight() : mCaptureFormat.getHeight(),
-                ImageFormat.JPEG, 1 /* maxImages */);
-
-        HandlerThread thread = new HandlerThread("CameraPicture");
-        thread.start();
-        final Handler backgroundHandler = new Handler(thread.getLooper());
 
         final CrPhotoReaderListener photoReaderListener = new CrPhotoReaderListener(callbackId);
-        imageReader.setOnImageAvailableListener(photoReaderListener, backgroundHandler);
-
-        final List<Surface> surfaceList = new ArrayList<Surface>(1);
-        // TODO(mcasas): release this Surface when not needed, https://crbug.com/643884.
-        surfaceList.add(imageReader.getSurface());
+        mPhotoReader.setOnImageAvailableListener(photoReaderListener, mBackgroundHandler);
 
         CaptureRequest.Builder photoRequestBuilder = null;
         try {
@@ -1107,18 +1044,95 @@
             Log.e(TAG, "photoRequestBuilder error");
             return false;
         }
-        photoRequestBuilder.addTarget(imageReader.getSurface());
+        photoRequestBuilder.addTarget(mPhotoReader.getSurface());
         photoRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, getCameraRotation());
 
         configureCommonCaptureSettings(photoRequestBuilder);
 
-        final CaptureRequest photoRequest = photoRequestBuilder.build();
-        final CrPhotoSessionListener sessionListener =
-                new CrPhotoSessionListener(photoRequest, callbackId);
+        // If there is no flash configured, we can just capture using |photoRequestBuilder| and let
+        // the results be collected in |photoReaderListener|.
+        if (mFillLightMode == AndroidFillLightMode.OFF || mTorch) {
+            try {
+                mCaptureSession.capture(photoRequestBuilder.build(), null, mBackgroundHandler);
+            } catch (CameraAccessException | IllegalArgumentException | SecurityException ex) {
+                Log.e(TAG, "mCaptureSession.capture() " + ex);
+                return false;
+            }
+            return true;
+        }
+
+        // To use the flash we need to trigger a precapture sequence on a still capture session
+        // described by |precaptureRequestBuilder|.
+        CaptureRequest.Builder precaptureRequestBuilder = null;
         try {
-            mCameraDevice.createCaptureSession(surfaceList, sessionListener, backgroundHandler);
+            precaptureRequestBuilder =
+                    mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+        } catch (CameraAccessException ex) {
+            Log.e(TAG, "createCaptureRequest() error ", ex);
+            return false;
+        }
+        if (precaptureRequestBuilder == null) {
+            Log.e(TAG, "precaptureRequestBuilder is null");
+            return false;
+        }
+
+        precaptureRequestBuilder.addTarget(mPrecaptureSurface);
+
+        precaptureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
+                photoRequestBuilder.get(CaptureRequest.CONTROL_AE_MODE));
+        precaptureRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+                CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
+
+        final long precaptureTimestamp = SystemClock.elapsedRealtime();
+        final CaptureRequest photoRequest = photoRequestBuilder.build();
+        final CameraCaptureSession.CaptureCallback precaptureStateObserver =
+                new CameraCaptureSession.CaptureCallback() {
+                    private boolean mPhotoCaptureTriggered = false;
+
+                    @Override
+                    public void onCaptureCompleted(CameraCaptureSession session,
+                            CaptureRequest request, TotalCaptureResult result) {
+                        process(result);
+                    }
+
+                    @Override
+                    public void onCaptureProgressed(CameraCaptureSession session,
+                            CaptureRequest request, CaptureResult partialResult) {
+                        process(partialResult);
+                    }
+
+                    void process(CaptureResult result) {
+                        Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
+                        if (aeState == null) return;
+
+                        // After the first convergence, discard eventual subsequent fragments.
+                        if (mPhotoCaptureTriggered) return;
+
+                        boolean timedOut = (SystemClock.elapsedRealtime() - precaptureTimestamp)
+                                > PRECAPTURE_TIMEOUT_MS;
+
+                        // Wait until the AE routine converges or we timeout.
+                        // https://developer.android.com/reference/android/hardware/camera2/CaptureResult.html#CONTROL_AE_STATE
+                        if (aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED
+                                || aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED
+                                || timedOut) {
+                            mPhotoCaptureTriggered = true;
+
+                            try {
+                                mCaptureSession.capture(photoRequest, null, null);
+                            } catch (CameraAccessException | IllegalArgumentException
+                                    | SecurityException ex) {
+                                Log.e(TAG, "mCaptureSession.capture(): " + ex);
+                            }
+                        }
+                    }
+                };
+
+        try {
+            mCaptureSession.capture(
+                    precaptureRequestBuilder.build(), precaptureStateObserver, mBackgroundHandler);
         } catch (CameraAccessException | IllegalArgumentException | SecurityException ex) {
-            Log.e(TAG, "createCaptureSession: " + ex);
+            Log.e(TAG, "takePhoto() - mCaptureSession.setRepeatingRequest()" + ex);
             return false;
         }
         return true;
diff --git a/media/filters/vpx_video_decoder.cc b/media/filters/vpx_video_decoder.cc
index 5fb9439..ca70546 100644
--- a/media/filters/vpx_video_decoder.cc
+++ b/media/filters/vpx_video_decoder.cc
@@ -22,6 +22,7 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/single_thread_task_runner.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/synchronization/atomic_flag.h"
 #include "base/sys_byteorder.h"
 #include "base/sys_info.h"
 #include "base/threading/thread.h"
@@ -380,6 +381,12 @@
   DCHECK_NE(state_, kUninitialized)
       << "Called Decode() before successful Initialize()";
 
+  // If abort has been triggered, do nothing.
+  if (should_abort_decodes_ && should_abort_decodes_->IsSet()) {
+    bound_decode_cb.Run(DecodeStatus::ABORTED);
+    return;
+  }
+
   if (state_ == kError) {
     bound_decode_cb.Run(DecodeStatus::DECODE_ERROR);
     return;
@@ -442,12 +449,36 @@
 
 void VpxVideoDecoder::Reset(const base::Closure& closure) {
   DCHECK(thread_checker_.CalledOnValidThread());
-  if (offload_task_runner_)
-    GetOffloadThread()->WaitForOutstandingTasks();
+  if (offload_task_runner_) {
+    should_abort_decodes_->Set();
+    offload_task_runner_->PostTask(
+        FROM_HERE,
+        BindToCurrentLoop(base::Bind(&VpxVideoDecoder::ResetHelper,
+                                     base::Unretained(this), closure)));
+    return;
+  }
 
+  // BindToCurrentLoop() to avoid calling |closure| inmediately.
+  ResetHelper(BindToCurrentLoop(closure));
+}
+
+void VpxVideoDecoder::ResetHelper(const base::Closure& closure) {
+  DCHECK(thread_checker_.CalledOnValidThread());
   state_ = kNormal;
-  // PostTask() to avoid calling |closure| inmediately.
-  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, closure);
+  if (offload_task_runner_)
+    should_abort_decodes_.reset(new base::AtomicFlag());
+  closure.Run();
+}
+
+int VpxVideoDecoder::GetMaxDecodeRequests() const {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  // Maximum number of concurrent Decode() operations allowed. Since this
+  // requires queuing of the decode requests, it can only be enabled when we
+  // have the offload thread. Higher values allow better pipelining, but also
+  // require more resources. Arbitrarily, we've chosen the same value as used by
+  // GpuVideoDecoder.
+  return offload_task_runner_ ? 4 : 1;
 }
 
 bool VpxVideoDecoder::ConfigureDecoder(const VideoDecoderConfig& config) {
@@ -485,7 +516,9 @@
     // Move high resolution vp9 decodes off of the main media thread (otherwise
     // decode may block audio decoding, demuxing, and other control activities).
     if (config.coded_size().width() >= 1024) {
+      DCHECK(!offload_task_runner_);
       offload_task_runner_ = GetOffloadThread()->RequestOffloadThread();
+      should_abort_decodes_.reset(new base::AtomicFlag());
     }
 
     DCHECK(!memory_pool_);
@@ -513,6 +546,7 @@
 
 void VpxVideoDecoder::CloseDecoder() {
   if (offload_task_runner_) {
+    should_abort_decodes_->Set();
     GetOffloadThread()->WaitForOutstandingTasksAndReleaseOffloadThread();
     offload_task_runner_ = nullptr;
   }
diff --git a/media/filters/vpx_video_decoder.h b/media/filters/vpx_video_decoder.h
index f3e341e..b307ab0 100644
--- a/media/filters/vpx_video_decoder.h
+++ b/media/filters/vpx_video_decoder.h
@@ -18,6 +18,7 @@
 struct vpx_image;
 
 namespace base {
+class AtomicFlag;
 class SingleThreadTaskRunner;
 }
 
@@ -45,6 +46,7 @@
   void Decode(const scoped_refptr<DecoderBuffer>& buffer,
               const DecodeCB& decode_cb) override;
   void Reset(const base::Closure& closure) override;
+  int GetMaxDecodeRequests() const override;
 
  private:
   enum DecoderState {
@@ -63,6 +65,9 @@
     kAlphaPlaneError  // Fatal error occured when trying to decode alpha plane.
   };
 
+  // Helper function for trampolining through the |offload_task_runner_|.
+  void ResetHelper(const base::Closure& closure);
+
   // Handles (re-)initializing the decoder with a (new) config.
   // Returns true when initialization was successful.
   bool ConfigureDecoder(const VideoDecoderConfig& config);
@@ -111,6 +116,7 @@
   // High resolution vp9 may block the media thread for too long, in such cases
   // we share a per-process thread to avoid overly long blocks.
   scoped_refptr<base::SingleThreadTaskRunner> offload_task_runner_;
+  std::unique_ptr<base::AtomicFlag> should_abort_decodes_;
 
   VideoFramePool frame_pool_;
 
diff --git a/mojo/edk/system/node_controller.cc b/mojo/edk/system/node_controller.cc
index c895379..66d4f52f 100644
--- a/mojo/edk/system/node_controller.cc
+++ b/mojo/edk/system/node_controller.cc
@@ -774,10 +774,6 @@
     delete this;
 }
 
-void NodeController::GenerateRandomPortName(ports::PortName* port_name) {
-  GenerateRandomName(port_name);
-}
-
 void NodeController::ForwardEvent(const ports::NodeName& node,
                                   ports::ScopedEvent event) {
   DCHECK(event);
diff --git a/mojo/edk/system/node_controller.h b/mojo/edk/system/node_controller.h
index 9d1c816..d48389e9 100644
--- a/mojo/edk/system/node_controller.h
+++ b/mojo/edk/system/node_controller.h
@@ -182,7 +182,6 @@
   void DropAllPeers();
 
   // ports::NodeDelegate:
-  void GenerateRandomPortName(ports::PortName* port_name) override;
   void ForwardEvent(const ports::NodeName& node,
                     ports::ScopedEvent event) override;
   void BroadcastEvent(ports::ScopedEvent event) override;
diff --git a/mojo/edk/system/ports/BUILD.gn b/mojo/edk/system/ports/BUILD.gn
index b5562d0..e500ebaa 100644
--- a/mojo/edk/system/ports/BUILD.gn
+++ b/mojo/edk/system/ports/BUILD.gn
@@ -18,6 +18,8 @@
     "node_delegate.h",
     "port.cc",
     "port.h",
+    "port_locker.cc",
+    "port_locker.h",
     "port_ref.cc",
     "port_ref.h",
     "user_data.h",
@@ -27,6 +29,12 @@
   public_deps = [
     "//base",
   ]
+
+  if (!is_nacl) {
+    deps = [
+      "//crypto",
+    ]
+  }
 }
 
 source_set("tests") {
diff --git a/mojo/edk/system/ports/message_queue.cc b/mojo/edk/system/ports/message_queue.cc
index f1a9719..08a7a10 100644
--- a/mojo/edk/system/ports/message_queue.cc
+++ b/mojo/edk/system/ports/message_queue.cc
@@ -69,7 +69,7 @@
   }
 }
 
-void MessageQueue::GetReferencedPorts(std::deque<PortName>* port_names) {
+void MessageQueue::GetReferencedPorts(std::vector<PortName>* port_names) {
   for (const auto& message : heap_) {
     for (size_t i = 0; i < message->num_ports(); ++i)
       port_names->push_back(message->ports()[i]);
diff --git a/mojo/edk/system/ports/message_queue.h b/mojo/edk/system/ports/message_queue.h
index 8ff3ab9..278c0da4 100644
--- a/mojo/edk/system/ports/message_queue.h
+++ b/mojo/edk/system/ports/message_queue.h
@@ -7,7 +7,6 @@
 
 #include <stdint.h>
 
-#include <deque>
 #include <limits>
 #include <memory>
 #include <vector>
@@ -58,7 +57,7 @@
                      bool* has_next_message);
 
   // Returns all of the ports referenced by messages in this message queue.
-  void GetReferencedPorts(std::deque<PortName>* ports);
+  void GetReferencedPorts(std::vector<PortName>* ports);
 
  private:
   std::vector<std::unique_ptr<UserMessageEvent>> heap_;
diff --git a/mojo/edk/system/ports/node.cc b/mojo/edk/system/ports/node.cc
index ec9b441..a57b9045 100644
--- a/mojo/edk/system/ports/node.cc
+++ b/mojo/edk/system/ports/node.cc
@@ -6,14 +6,24 @@
 
 #include <string.h>
 
+#include <algorithm>
 #include <utility>
 
 #include "base/atomicops.h"
+#include "base/containers/stack_container.h"
 #include "base/logging.h"
 #include "base/memory/ref_counted.h"
+#include "base/optional.h"
 #include "base/synchronization/lock.h"
 #include "mojo/edk/system/ports/event.h"
 #include "mojo/edk/system/ports/node_delegate.h"
+#include "mojo/edk/system/ports/port_locker.h"
+
+#if !defined(OS_NACL)
+#include "crypto/random.h"
+#else
+#include "base/rand_util.h"
+#endif
 
 namespace mojo {
 namespace edk {
@@ -22,7 +32,7 @@
 namespace {
 
 int DebugError(const char* message, int error_code) {
-  CHECK(false) << "Oops: " << message;
+  NOTREACHED() << "Oops: " << message;
   return error_code;
 }
 
@@ -41,25 +51,18 @@
   return true;
 }
 
+void GenerateRandomPortName(PortName* name) {
+#if defined(OS_NACL)
+  base::RandBytes(name, sizeof(PortName));
+#else
+  crypto::RandBytes(name, sizeof(PortName));
+#endif
+}
+
 }  // namespace
 
-class Node::LockedPort {
- public:
-  explicit LockedPort(Port* port) : port_(port) {
-    port_->lock.AssertAcquired();
-  }
-
-  Port* get() const { return port_; }
-  Port* operator->() const { return port_; }
-
- private:
-  Port* const port_;
-};
-
 Node::Node(const NodeName& name, NodeDelegate* delegate)
-    : name_(name),
-      delegate_(delegate) {
-}
+    : name_(name), delegate_(this, delegate) {}
 
 Node::~Node() {
   if (!ports_.empty())
@@ -67,6 +70,7 @@
 }
 
 bool Node::CanShutdownCleanly(ShutdownPolicy policy) {
+  PortLocker::AssertNoPortsLockedOnCurrentThread();
   base::AutoLock ports_lock(ports_lock_);
 
   if (policy == ShutdownPolicy::DONT_ALLOW_LOCAL_PORTS) {
@@ -87,14 +91,15 @@
   // need to be blazingly fast.
   bool can_shutdown = true;
   for (auto entry : ports_) {
-    base::AutoLock lock(entry.second->lock);
-    if (entry.second->peer_node_name != name_ &&
-        entry.second->state != Port::kReceiving) {
+    PortRef port_ref(entry.first, entry.second);
+    SinglePortLocker locker(&port_ref);
+    auto* port = locker.port();
+    if (port->peer_node_name != name_ && port->state != Port::kReceiving) {
       can_shutdown = false;
 #if DCHECK_IS_ON()
       DVLOG(2) << "Port " << entry.first << " referencing node "
-               << entry.second->peer_node_name << " is blocking shutdown of "
-               << "node " << name_ << " (state=" << entry.second->state << ")";
+               << port->peer_node_name << " is blocking shutdown of "
+               << "node " << name_ << " (state=" << port->state << ")";
 #else
       // Exit early when not debugging.
       break;
@@ -106,17 +111,24 @@
 }
 
 int Node::GetPort(const PortName& port_name, PortRef* port_ref) {
-  scoped_refptr<Port> port = GetPort(port_name);
-  if (!port)
+  PortLocker::AssertNoPortsLockedOnCurrentThread();
+  base::AutoLock lock(ports_lock_);
+  auto iter = ports_.find(port_name);
+  if (iter == ports_.end())
     return ERROR_PORT_UNKNOWN;
 
-  *port_ref = PortRef(port_name, std::move(port));
+#if defined(OS_ANDROID) && defined(ARCH_CPU_ARM64)
+  // Workaround for https://crbug.com/665869.
+  base::subtle::MemoryBarrier();
+#endif
+
+  *port_ref = PortRef(port_name, iter->second);
   return OK;
 }
 
 int Node::CreateUninitializedPort(PortRef* port_ref) {
   PortName port_name;
-  delegate_->GenerateRandomPortName(&port_name);
+  GenerateRandomPortName(&port_name);
 
   scoped_refptr<Port> port(new Port(kInitialSequenceNum, kInitialSequenceNum));
   int rv = AddPortWithName(port_name, port);
@@ -130,10 +142,9 @@
 int Node::InitializePort(const PortRef& port_ref,
                          const NodeName& peer_node_name,
                          const PortName& peer_port_name) {
-  Port* port = port_ref.port();
-
   {
-    base::AutoLock lock(port->lock);
+    SinglePortLocker locker(&port_ref);
+    auto* port = locker.port();
     if (port->state != Port::kUninitialized)
       return ERROR_PORT_STATE_UNEXPECTED;
 
@@ -171,9 +182,8 @@
 
 int Node::SetUserData(const PortRef& port_ref,
                       scoped_refptr<UserData> user_data) {
-  Port* port = port_ref.port();
-
-  base::AutoLock lock(port->lock);
+  SinglePortLocker locker(&port_ref);
+  auto* port = locker.port();
   if (port->state == Port::kClosed)
     return ERROR_PORT_STATE_UNEXPECTED;
 
@@ -184,9 +194,8 @@
 
 int Node::GetUserData(const PortRef& port_ref,
                       scoped_refptr<UserData>* user_data) {
-  Port* port = port_ref.port();
-
-  base::AutoLock lock(port->lock);
+  SinglePortLocker locker(&port_ref);
+  auto* port = locker.port();
   if (port->state == Port::kClosed)
     return ERROR_PORT_STATE_UNEXPECTED;
 
@@ -196,64 +205,60 @@
 }
 
 int Node::ClosePort(const PortRef& port_ref) {
-  std::deque<PortName> referenced_port_names;
-
+  std::vector<PortName> referenced_port_names;
   NodeName peer_node_name;
   PortName peer_port_name;
   uint64_t last_sequence_num = 0;
-  Port* port = port_ref.port();
+  bool was_initialized = false;
   {
-    // We may need to erase the port, which requires ports_lock_ to be held,
-    // but ports_lock_ must be acquired before any individual port locks.
-    base::AutoLock ports_lock(ports_lock_);
+    SinglePortLocker locker(&port_ref);
+    auto* port = locker.port();
+    switch (port->state) {
+      case Port::kUninitialized:
+        break;
 
-    base::AutoLock lock(port->lock);
-    if (port->state == Port::kUninitialized) {
-      // If the port was not yet initialized, there's nothing interesting to do.
-      ErasePort_Locked(port_ref.name());
-      return OK;
+      case Port::kReceiving:
+        was_initialized = true;
+        port->state = Port::kClosed;
+
+        // We pass along the sequence number of the last message sent from this
+        // port to allow the peer to have the opportunity to consume all inbound
+        // messages before notifying the embedder that this port is closed.
+        last_sequence_num = port->next_sequence_num_to_send - 1;
+
+        peer_node_name = port->peer_node_name;
+        peer_port_name = port->peer_port_name;
+
+        // If the port being closed still has unread messages, then we need to
+        // take care to close those ports so as to avoid leaking memory.
+        port->message_queue.GetReferencedPorts(&referenced_port_names);
+        break;
+
+      default:
+        return ERROR_PORT_STATE_UNEXPECTED;
     }
-
-    if (port->state != Port::kReceiving)
-      return ERROR_PORT_STATE_UNEXPECTED;
-
-    port->state = Port::kClosed;
-
-    // We pass along the sequence number of the last message sent from this
-    // port to allow the peer to have the opportunity to consume all inbound
-    // messages before notifying the embedder that this port is closed.
-    last_sequence_num = port->next_sequence_num_to_send - 1;
-
-    peer_node_name = port->peer_node_name;
-    peer_port_name = port->peer_port_name;
-
-    // If the port being closed still has unread messages, then we need to take
-    // care to close those ports so as to avoid leaking memory.
-    port->message_queue.GetReferencedPorts(&referenced_port_names);
-
-    ErasePort_Locked(port_ref.name());
   }
 
-  DVLOG(2) << "Sending ObserveClosure from " << port_ref.name() << "@" << name_
-           << " to " << peer_port_name << "@" << peer_node_name;
+  ErasePort(port_ref.name());
 
-  delegate_->ForwardEvent(
-      peer_node_name,
-      base::MakeUnique<ObserveClosureEvent>(peer_port_name, last_sequence_num));
-
-  for (const auto& name : referenced_port_names) {
-    PortRef ref;
-    if (GetPort(name, &ref) == OK)
-      ClosePort(ref);
+  if (was_initialized) {
+    DVLOG(2) << "Sending ObserveClosure from " << port_ref.name() << "@"
+             << name_ << " to " << peer_port_name << "@" << peer_node_name;
+    delegate_->ForwardEvent(peer_node_name,
+                            base::MakeUnique<ObserveClosureEvent>(
+                                peer_port_name, last_sequence_num));
+    for (const auto& name : referenced_port_names) {
+      PortRef ref;
+      if (GetPort(name, &ref) == OK)
+        ClosePort(ref);
+    }
   }
   return OK;
 }
 
 int Node::GetStatus(const PortRef& port_ref, PortStatus* port_status) {
-  Port* port = port_ref.port();
-
-  base::AutoLock lock(port->lock);
-
+  SinglePortLocker locker(&port_ref);
+  auto* port = locker.port();
   if (port->state != Port::kReceiving)
     return ERROR_PORT_STATE_UNEXPECTED;
 
@@ -270,9 +275,9 @@
 
   DVLOG(4) << "GetMessage for " << port_ref.name() << "@" << name_;
 
-  Port* port = port_ref.port();
   {
-    base::AutoLock lock(port->lock);
+    SinglePortLocker locker(&port_ref);
+    auto* port = locker.port();
 
     // This could also be treated like the port being unknown since the
     // embedder should no longer be referring to a port that has been sent.
@@ -290,16 +295,15 @@
   // Allow referenced ports to trigger PortStatusChanged calls.
   if (*message) {
     for (size_t i = 0; i < (*message)->num_ports(); ++i) {
-      const PortName& new_port_name = (*message)->ports()[i];
-      scoped_refptr<Port> new_port = GetPort(new_port_name);
+      PortRef new_port_ref;
+      int rv = GetPort((*message)->ports()[i], &new_port_ref);
 
-      DCHECK(new_port) << "Port " << new_port_name << "@" << name_
-                       << " does not exist!";
+      DCHECK_EQ(OK, rv) << "Port " << new_port_ref.name() << "@" << name_
+                        << " does not exist!";
 
-      base::AutoLock lock(new_port->lock);
-
-      DCHECK(new_port->state == Port::kReceiving);
-      new_port->message_queue.set_signalable(true);
+      SinglePortLocker locker(&new_port_ref);
+      DCHECK(locker.port()->state == Port::kReceiving);
+      locker.port()->message_queue.set_signalable(true);
     }
   }
 
@@ -346,11 +350,10 @@
 int Node::MergePorts(const PortRef& port_ref,
                      const NodeName& destination_node_name,
                      const PortName& destination_port_name) {
-  Port* port = port_ref.port();
   PortName new_port_name;
   Event::PortDescriptor new_port_descriptor;
   {
-    base::AutoLock lock(port->lock);
+    SinglePortLocker locker(&port_ref);
 
     DVLOG(1) << "Sending MergePort from " << port_ref.name() << "@" << name_
              << " to " << destination_port_name << "@" << destination_node_name;
@@ -358,8 +361,8 @@
     // Send the port-to-merge over to the destination node so it can be merged
     // into the port cycle atomically there.
     new_port_name = port_ref.name();
-    WillSendPort(LockedPort(port), destination_node_name, &new_port_name,
-                 &new_port_descriptor);
+    ConvertToProxy(locker.port(), destination_node_name, &new_port_name,
+                   &new_port_descriptor);
   }
   delegate_->ForwardEvent(
       destination_node_name,
@@ -369,30 +372,10 @@
 }
 
 int Node::MergeLocalPorts(const PortRef& port0_ref, const PortRef& port1_ref) {
-  Port* port0 = port0_ref.port();
-  Port* port1 = port1_ref.port();
-  int rv;
-  {
-    // |ports_lock_| must be held when acquiring overlapping port locks.
-    base::AutoLock ports_lock(ports_lock_);
-    base::AutoLock port0_lock(port0->lock);
-    base::AutoLock port1_lock(port1->lock);
-
-    DVLOG(1) << "Merging local ports " << port0_ref.name() << "@" << name_
-             << " and " << port1_ref.name() << "@" << name_;
-
-    if (port0->state != Port::kReceiving || port1->state != Port::kReceiving)
-      rv = ERROR_PORT_STATE_UNEXPECTED;
-    else
-      rv = MergePorts_Locked(port0_ref, port1_ref);
-  }
-
-  if (rv != OK) {
-    ClosePort(port0_ref);
-    ClosePort(port1_ref);
-  }
-
-  return rv;
+  DVLOG(1) << "Merging local ports " << port0_ref.name() << "@" << name_
+           << " and " << port1_ref.name() << "@" << name_;
+  return MergePortsInternal(port0_ref, port1_ref,
+                            true /* allow_close_on_bad_state */);
 }
 
 int Node::LostConnectionToNode(const NodeName& node_name) {
@@ -422,8 +405,6 @@
            << name_;
 #endif
 
-  scoped_refptr<Port> port = GetPort(port_name);
-
   // Even if this port does not exist, cannot receive anymore messages or is
   // buffering or proxying messages, we still need these ports to be bound to
   // this node. When the message is forwarded, these ports will get transferred
@@ -434,7 +415,8 @@
     if (descriptor.referring_node_name == kInvalidNodeName) {
       // If the referring node name is invalid, this descriptor can be ignored
       // and the port should already exist locally.
-      if (!GetPort(message->ports()[i]))
+      PortRef port_ref;
+      if (GetPort(message->ports()[i], &port_ref) != OK)
         return ERROR_PORT_UNKNOWN;
     } else {
       int rv = AcceptPort(message->ports()[i], descriptor);
@@ -448,18 +430,18 @@
     }
   }
 
+  PortRef port_ref;
+  GetPort(port_name, &port_ref);
   bool has_next_message = false;
   bool message_accepted = false;
-
-  if (port) {
-    // We may want to forward messages once the port lock is held, so we must
-    // acquire |ports_lock_| first.
-    base::AutoLock ports_lock(ports_lock_);
-    base::AutoLock lock(port->lock);
+  bool should_forward_messages = false;
+  if (port_ref.is_valid()) {
+    SinglePortLocker locker(&port_ref);
+    auto* port = locker.port();
 
     // Reject spurious messages if we've already received the last expected
     // message.
-    if (CanAcceptMoreMessages(port.get())) {
+    if (CanAcceptMoreMessages(port)) {
       message_accepted = true;
       port->message_queue.AcceptMessage(std::move(message), &has_next_message);
 
@@ -467,33 +449,30 @@
         has_next_message = false;
       } else if (port->state == Port::kProxying) {
         has_next_message = false;
-
-        // Forward messages. We forward messages in sequential order here so
-        // that we maintain the message queue's notion of next sequence number.
-        // That's useful for the proxy removal process as we can tell when this
-        // port has seen all of the messages it is expected to see.
-        int rv = ForwardMessages_Locked(LockedPort(port.get()), port_name);
-        if (rv != OK)
-          return rv;
-
-        MaybeRemoveProxy_Locked(LockedPort(port.get()), port_name);
+        should_forward_messages = true;
       }
     }
   }
 
+  if (should_forward_messages) {
+    int rv = ForwardUserMessagesFromProxy(port_ref);
+    if (rv != OK)
+      return rv;
+    TryRemoveProxy(port_ref);
+  }
+
   if (!message_accepted) {
     DVLOG(2) << "Message not accepted!\n";
     // Close all newly accepted ports as they are effectively orphaned.
     for (size_t i = 0; i < message->num_ports(); ++i) {
-      PortRef port_ref;
-      if (GetPort(message->ports()[i], &port_ref) == OK) {
-        ClosePort(port_ref);
+      PortRef attached_port_ref;
+      if (GetPort(message->ports()[i], &attached_port_ref) == OK) {
+        ClosePort(attached_port_ref);
       } else {
         DLOG(WARNING) << "Cannot close non-existent port!\n";
       }
     }
   } else if (has_next_message) {
-    PortRef port_ref(port_name, port);
     delegate_->PortStatusChanged(port_ref);
   }
 
@@ -501,20 +480,24 @@
 }
 
 int Node::OnPortAccepted(std::unique_ptr<PortAcceptedEvent> event) {
-  const PortName& port_name = event->port_name();
-  scoped_refptr<Port> port = GetPort(port_name);
-  if (!port)
+  PortRef port_ref;
+  if (GetPort(event->port_name(), &port_ref) != OK)
     return ERROR_PORT_UNKNOWN;
 
-  DVLOG(2) << "PortAccepted at " << port_name << "@" << name_ << " pointing to "
-           << port->peer_port_name << "@" << port->peer_node_name;
+#if DCHECK_IS_ON()
+  {
+    SinglePortLocker locker(&port_ref);
+    DVLOG(2) << "PortAccepted at " << port_ref.name() << "@" << name_
+             << " pointing to " << locker.port()->peer_port_name << "@"
+             << locker.port()->peer_node_name;
+  }
+#endif
 
-  return BeginProxying(PortRef(port_name, std::move(port)));
+  return BeginProxying(port_ref);
 }
 
 int Node::OnObserveProxy(std::unique_ptr<ObserveProxyEvent> event) {
-  const PortName& port_name = event->port_name();
-  if (port_name == kInvalidPortName) {
+  if (event->port_name() == kInvalidPortName) {
     // An ObserveProxy with an invalid target port name is a broadcast used to
     // inform ports when their peer (which was itself a proxy) has become
     // defunct due to unexpected node disconnection.
@@ -531,29 +514,33 @@
   // The port may have already been closed locally, in which case the
   // ObserveClosure message will contain the last_sequence_num field.
   // We can then silently ignore this message.
-  scoped_refptr<Port> port = GetPort(port_name);
-  if (!port) {
-    DVLOG(1) << "ObserveProxy: " << port_name << "@" << name_ << " not found";
+  PortRef port_ref;
+  if (GetPort(event->port_name(), &port_ref) != OK) {
+    DVLOG(1) << "ObserveProxy: " << event->port_name() << "@" << name_
+             << " not found";
     return OK;
   }
 
-  DVLOG(2) << "ObserveProxy at " << port_name << "@" << name_ << ", proxy at "
-           << event->proxy_port_name() << "@" << event->proxy_node_name()
-           << " pointing to " << event->proxy_target_port_name() << "@"
+  DVLOG(2) << "ObserveProxy at " << port_ref.name() << "@" << name_
+           << ", proxy at " << event->proxy_port_name() << "@"
+           << event->proxy_node_name() << " pointing to "
+           << event->proxy_target_port_name() << "@"
            << event->proxy_target_node_name();
 
+  ScopedEvent event_to_forward;
+  NodeName event_target_node;
   {
-    base::AutoLock lock(port->lock);
+    SinglePortLocker locker(&port_ref);
+    auto* port = locker.port();
 
     if (port->peer_node_name == event->proxy_node_name() &&
         port->peer_port_name == event->proxy_port_name()) {
       if (port->state == Port::kReceiving) {
         port->peer_node_name = event->proxy_target_node_name();
         port->peer_port_name = event->proxy_target_port_name();
-        delegate_->ForwardEvent(
-            event->proxy_node_name(),
-            base::MakeUnique<ObserveProxyAckEvent>(
-                event->proxy_port_name(), port->next_sequence_num_to_send - 1));
+        event_target_node = event->proxy_node_name();
+        event_to_forward = base::MakeUnique<ObserveProxyAckEvent>(
+            event->proxy_port_name(), port->next_sequence_num_to_send - 1);
       } else {
         // As a proxy ourselves, we don't know how to honor the ObserveProxy
         // event or to populate the last_sequence_num field of ObserveProxyAck.
@@ -576,51 +563,58 @@
     } else {
       // Forward this event along to our peer. Eventually, it should find the
       // port referring to the proxy.
+      event_target_node = port->peer_node_name;
       event->set_port_name(port->peer_port_name);
-      delegate_->ForwardEvent(port->peer_node_name, std::move(event));
+      event_to_forward = std::move(event);
     }
   }
+
+  if (event_to_forward)
+    delegate_->ForwardEvent(event_target_node, std::move(event_to_forward));
+
   return OK;
 }
 
 int Node::OnObserveProxyAck(std::unique_ptr<ObserveProxyAckEvent> event) {
-  const PortName& port_name = event->port_name();
-
-  DVLOG(2) << "ObserveProxyAck at " << port_name << "@" << name_
+  DVLOG(2) << "ObserveProxyAck at " << event->port_name() << "@" << name_
            << " (last_sequence_num=" << event->last_sequence_num() << ")";
 
-  scoped_refptr<Port> port = GetPort(port_name);
-  if (!port)
-    return ERROR_PORT_UNKNOWN;  // The port may have observed closure first, so
-                                // this is not an "Oops".
+  PortRef port_ref;
+  if (GetPort(event->port_name(), &port_ref) != OK)
+    return ERROR_PORT_UNKNOWN;  // The port may have observed closure first.
 
+  bool try_remove_proxy_immediately;
   {
-    base::AutoLock lock(port->lock);
-
+    SinglePortLocker locker(&port_ref);
+    auto* port = locker.port();
     if (port->state != Port::kProxying)
       return OOPS(ERROR_PORT_STATE_UNEXPECTED);
 
-    if (event->last_sequence_num() == kInvalidSequenceNum) {
-      // Send again.
-      InitiateProxyRemoval(LockedPort(port.get()), port_name);
-      return OK;
+    // If the last sequence number is invalid, this is a signal that we need to
+    // retransmit the ObserveProxy event for this port rather than flagging the
+    // the proxy for removal ASAP.
+    try_remove_proxy_immediately =
+        event->last_sequence_num() != kInvalidSequenceNum;
+    if (try_remove_proxy_immediately) {
+      // We can now remove this port once we have received and forwarded the
+      // last message addressed to this port.
+      port->remove_proxy_on_last_message = true;
+      port->last_sequence_num_to_receive = event->last_sequence_num();
     }
-
-    // We can now remove this port once we have received and forwarded the last
-    // message addressed to this port.
-    port->remove_proxy_on_last_message = true;
-    port->last_sequence_num_to_receive = event->last_sequence_num();
   }
-  TryRemoveProxy(PortRef(port_name, std::move(port)));
+
+  if (try_remove_proxy_immediately)
+    TryRemoveProxy(port_ref);
+  else
+    InitiateProxyRemoval(port_ref);
+
   return OK;
 }
 
 int Node::OnObserveClosure(std::unique_ptr<ObserveClosureEvent> event) {
-  PortName port_name = event->port_name();
-
   // OK if the port doesn't exist, as it may have been closed already.
-  scoped_refptr<Port> port = GetPort(port_name);
-  if (!port)
+  PortRef port_ref;
+  if (GetPort(event->port_name(), &port_ref) != OK)
     return OK;
 
   // This message tells the port that it should no longer expect more messages
@@ -633,12 +627,13 @@
   PortName peer_port_name;
   bool try_remove_proxy = false;
   {
-    base::AutoLock lock(port->lock);
+    SinglePortLocker locker(&port_ref);
+    auto* port = locker.port();
 
     port->peer_closed = true;
     port->last_sequence_num_to_receive = event->last_sequence_num();
 
-    DVLOG(2) << "ObserveClosure at " << port_name << "@" << name_
+    DVLOG(2) << "ObserveClosure at " << port_ref.name() << "@" << name_
              << " (state=" << port->state << ") pointing to "
              << port->peer_port_name << "@" << port->peer_node_name
              << " (last_sequence_num=" << event->last_sequence_num() << ")";
@@ -668,135 +663,77 @@
         try_remove_proxy = true;
     }
 
-    DVLOG(2) << "Forwarding ObserveClosure from " << port_name << "@" << name_
-             << " to peer " << port->peer_port_name << "@"
+    DVLOG(2) << "Forwarding ObserveClosure from " << port_ref.name() << "@"
+             << name_ << " to peer " << port->peer_port_name << "@"
              << port->peer_node_name
              << " (last_sequence_num=" << event->last_sequence_num() << ")";
 
     peer_node_name = port->peer_node_name;
     peer_port_name = port->peer_port_name;
   }
+
   if (try_remove_proxy)
-    TryRemoveProxy(PortRef(port_name, port));
+    TryRemoveProxy(port_ref);
 
   event->set_port_name(peer_port_name);
   delegate_->ForwardEvent(peer_node_name, std::move(event));
 
-  if (notify_delegate) {
-    PortRef port_ref(port_name, std::move(port));
+  if (notify_delegate)
     delegate_->PortStatusChanged(port_ref);
-  }
+
   return OK;
 }
 
 int Node::OnMergePort(std::unique_ptr<MergePortEvent> event) {
-  const PortName& port_name = event->port_name();
-  scoped_refptr<Port> port = GetPort(port_name);
+  PortRef port_ref;
+  GetPort(event->port_name(), &port_ref);
 
-  DVLOG(1) << "MergePort at " << port_name << "@" << name_
-           << " (state=" << (port ? port->state : -1) << ") merging with proxy "
-           << event->new_port_name() << "@" << name_ << " pointing to "
-           << event->new_port_descriptor().peer_port_name << "@"
-           << event->new_port_descriptor().peer_node_name << " referred by "
+  DVLOG(1) << "MergePort at " << port_ref.name() << "@" << name_
+           << " merging with proxy " << event->new_port_name() << "@" << name_
+           << " pointing to " << event->new_port_descriptor().peer_port_name
+           << "@" << event->new_port_descriptor().peer_node_name
+           << " referred by "
            << event->new_port_descriptor().referring_port_name << "@"
            << event->new_port_descriptor().referring_node_name;
 
-  bool close_target_port = false;
-  bool close_new_port = false;
-
   // Accept the new port. This is now the receiving end of the other port cycle
-  // to be merged with ours.
-  int rv = AcceptPort(event->new_port_name(), event->new_port_descriptor());
-  if (rv != OK) {
-    close_target_port = true;
-  } else if (port) {
-    // BeginProxying_Locked may call MaybeRemoveProxy_Locked, which in turn
-    // needs to hold |ports_lock_|. We also acquire multiple port locks within.
-    base::AutoLock ports_lock(ports_lock_);
-    base::AutoLock lock(port->lock);
-
-    if (port->state != Port::kReceiving) {
-      close_new_port = true;
-    } else {
-      scoped_refptr<Port> new_port = GetPort_Locked(event->new_port_name());
-      base::AutoLock new_port_lock(new_port->lock);
-      DCHECK(new_port->state == Port::kReceiving);
-
-      // Both ports are locked. Now all we have to do is swap their peer
-      // information and set them up as proxies.
-
-      PortRef port0_ref(port_name, port);
-      PortRef port1_ref(event->new_port_name(), new_port);
-      int rv = MergePorts_Locked(port0_ref, port1_ref);
-      if (rv == OK)
-        return rv;
-
-      close_new_port = true;
-      close_target_port = true;
-    }
-  } else {
-    close_new_port = true;
+  // to be merged with ours. Note that we always attempt to accept the new port
+  // first as otherwise its peer receiving port could be left stranded
+  // indefinitely.
+  if (AcceptPort(event->new_port_name(), event->new_port_descriptor()) != OK) {
+    ClosePort(port_ref);
+    return ERROR_PORT_STATE_UNEXPECTED;
   }
 
-  if (close_target_port) {
-    PortRef target_port;
-    rv = GetPort(port_name, &target_port);
-    DCHECK(rv == OK);
+  PortRef new_port_ref;
+  GetPort(event->new_port_name(), &new_port_ref);
+  DCHECK(new_port_ref.is_valid());
 
-    ClosePort(target_port);
+  if (!port_ref.is_valid()) {
+    ClosePort(port_ref);
+    ClosePort(new_port_ref);
   }
 
-  if (close_new_port) {
-    PortRef new_port;
-    rv = GetPort(event->new_port_name(), &new_port);
-    DCHECK(rv == OK);
-
-    ClosePort(new_port);
-  }
-
-  return ERROR_PORT_STATE_UNEXPECTED;
+  return MergePortsInternal(port_ref, new_port_ref,
+                            false /* allow_close_on_bad_state */);
 }
 
 int Node::AddPortWithName(const PortName& port_name, scoped_refptr<Port> port) {
+  PortLocker::AssertNoPortsLockedOnCurrentThread();
   base::AutoLock lock(ports_lock_);
-
   if (!ports_.insert(std::make_pair(port_name, std::move(port))).second)
     return OOPS(ERROR_PORT_EXISTS);  // Suggests a bad UUID generator.
-
   DVLOG(2) << "Created port " << port_name << "@" << name_;
   return OK;
 }
 
 void Node::ErasePort(const PortName& port_name) {
+  PortLocker::AssertNoPortsLockedOnCurrentThread();
   base::AutoLock lock(ports_lock_);
-  ErasePort_Locked(port_name);
-}
-
-void Node::ErasePort_Locked(const PortName& port_name) {
-  ports_lock_.AssertAcquired();
   ports_.erase(port_name);
   DVLOG(2) << "Deleted port " << port_name << "@" << name_;
 }
 
-scoped_refptr<Port> Node::GetPort(const PortName& port_name) {
-  base::AutoLock lock(ports_lock_);
-  return GetPort_Locked(port_name);
-}
-
-scoped_refptr<Port> Node::GetPort_Locked(const PortName& port_name) {
-  ports_lock_.AssertAcquired();
-  auto iter = ports_.find(port_name);
-  if (iter == ports_.end())
-    return nullptr;
-
-#if defined(OS_ANDROID) && defined(ARCH_CPU_ARM64)
-  // Workaround for https://crbug.com/665869.
-  base::subtle::MemoryBarrier();
-#endif
-
-  return iter->second;
-}
-
 int Node::SendUserMessageInternal(const PortRef& port_ref,
                                   std::unique_ptr<UserMessageEvent>* message) {
   std::unique_ptr<UserMessageEvent>& m = *message;
@@ -805,133 +742,150 @@
       return ERROR_PORT_CANNOT_SEND_SELF;
   }
 
-  Port* port = port_ref.port();
-  NodeName peer_node_name;
-  {
-    // We must acquire |ports_lock_| before grabbing any port locks, because
-    // WillForwardUserMessage_Locked may need to lock multiple ports out of
-    // order.
-    base::AutoLock ports_lock(ports_lock_);
-    base::AutoLock lock(port->lock);
+  NodeName target_node;
+  int rv = PrepareToForwardUserMessage(port_ref, Port::kReceiving,
+                                       false /* ignore_closed_peer */, m.get(),
+                                       &target_node);
+  if (rv != OK)
+    return rv;
 
-    if (port->state != Port::kReceiving)
-      return ERROR_PORT_STATE_UNEXPECTED;
+  // Beyond this point there's no sense in returning anything but OK. Even if
+  // message forwarding or acceptance fails, there's nothing the embedder can
+  // do to recover. Assume that failure beyond this point must be treated as a
+  // transport failure.
 
-    if (port->peer_closed)
-      return ERROR_PORT_PEER_CLOSED;
-
-    int rv = WillForwardUserMessage_Locked(LockedPort(port), port_ref.name(),
-                                           m.get());
-    if (rv != OK)
-      return rv;
-
-    // Beyond this point there's no sense in returning anything but OK. Even if
-    // message forwarding or acceptance fails, there's nothing the embedder can
-    // do to recover. Assume that failure beyond this point must be treated as a
-    // transport failure.
-
-    peer_node_name = port->peer_node_name;
-  }
-
-  if (peer_node_name != name_) {
-    delegate_->ForwardEvent(peer_node_name, std::move(m));
+  DCHECK_NE(kInvalidNodeName, target_node);
+  if (target_node != name_) {
+    delegate_->ForwardEvent(target_node, std::move(m));
     return OK;
   }
 
-  int rv = AcceptEvent(std::move(m));
-  if (rv != OK) {
+  int accept_result = AcceptEvent(std::move(m));
+  if (accept_result != OK) {
     // See comment above for why we don't return an error in this case.
-    DVLOG(2) << "AcceptEvent failed: " << rv;
+    DVLOG(2) << "AcceptEvent failed: " << accept_result;
   }
 
   return OK;
 }
 
-int Node::MergePorts_Locked(const PortRef& port0_ref,
-                            const PortRef& port1_ref) {
-  Port* port0 = port0_ref.port();
-  Port* port1 = port1_ref.port();
+int Node::MergePortsInternal(const PortRef& port0_ref,
+                             const PortRef& port1_ref,
+                             bool allow_close_on_bad_state) {
+  const PortRef* port_refs[2] = {&port0_ref, &port1_ref};
+  {
+    base::Optional<PortLocker> locker(base::in_place, port_refs, 2);
+    auto* port0 = locker->GetPort(port0_ref);
+    auto* port1 = locker->GetPort(port1_ref);
 
-  ports_lock_.AssertAcquired();
-  port0->lock.AssertAcquired();
-  port1->lock.AssertAcquired();
-
-  CHECK(port0->state == Port::kReceiving);
-  CHECK(port1->state == Port::kReceiving);
-
-  // Ports cannot be merged with their own receiving peer!
-  if (port0->peer_node_name == name_ &&
-      port0->peer_port_name == port1_ref.name())
-    return ERROR_PORT_STATE_UNEXPECTED;
-
-  if (port1->peer_node_name == name_ &&
-      port1->peer_port_name == port0_ref.name())
-    return ERROR_PORT_STATE_UNEXPECTED;
-
-  // Only merge if both ports have never sent a message.
-  if (port0->next_sequence_num_to_send == kInitialSequenceNum &&
-      port1->next_sequence_num_to_send == kInitialSequenceNum) {
-    // Swap the ports' peer information and switch them both into buffering
-    // (eventually proxying) mode.
-
-    std::swap(port0->peer_node_name, port1->peer_node_name);
-    std::swap(port0->peer_port_name, port1->peer_port_name);
-
-    port0->state = Port::kBuffering;
-    if (port0->peer_closed)
-      port0->remove_proxy_on_last_message = true;
-
-    port1->state = Port::kBuffering;
-    if (port1->peer_closed)
-      port1->remove_proxy_on_last_message = true;
-
-    int rv1 = BeginProxying_Locked(LockedPort(port0), port0_ref.name());
-    int rv2 = BeginProxying_Locked(LockedPort(port1), port1_ref.name());
-
-    if (rv1 == OK && rv2 == OK) {
-      // If either merged port had a closed peer, its new peer needs to be
-      // informed of this.
-      if (port1->peer_closed) {
-        delegate_->ForwardEvent(
-            port0->peer_node_name,
-            base::MakeUnique<ObserveClosureEvent>(
-                port0->peer_port_name, port0->last_sequence_num_to_receive));
-      }
-
-      if (port0->peer_closed) {
-        delegate_->ForwardEvent(
-            port1->peer_node_name,
-            base::MakeUnique<ObserveClosureEvent>(
-                port1->peer_port_name, port1->last_sequence_num_to_receive));
-      }
-
-      return OK;
+    // There are several conditions which must be met before we'll consider
+    // merging two ports:
+    //
+    // - They must both be in the kReceiving state
+    // - They must not be each other's peer
+    // - They must have never sent a user message
+    //
+    // If any of these criteria are not met, we fail early.
+    if (port0->state != Port::kReceiving || port1->state != Port::kReceiving ||
+        (port0->peer_node_name == name_ &&
+         port0->peer_port_name == port1_ref.name()) ||
+        (port1->peer_node_name == name_ &&
+         port1->peer_port_name == port0_ref.name()) ||
+        port0->next_sequence_num_to_send != kInitialSequenceNum ||
+        port1->next_sequence_num_to_send != kInitialSequenceNum) {
+      // On failure, we only close a port if it was at least properly in the
+      // |kReceiving| state. This avoids getting the system in an inconsistent
+      // state by e.g. closing a proxy abruptly.
+      //
+      // Note that we must release the port locks before closing ports.
+      const bool close_port0 =
+          port0->state == Port::kReceiving || allow_close_on_bad_state;
+      const bool close_port1 =
+          port1->state == Port::kReceiving || allow_close_on_bad_state;
+      locker.reset();
+      if (close_port0)
+        ClosePort(port0_ref);
+      if (close_port1)
+        ClosePort(port1_ref);
+      return ERROR_PORT_STATE_UNEXPECTED;
     }
 
-    // If either proxy failed to initialize (e.g. had undeliverable messages
-    // or ended up in a bad state somehow), we keep the system in a consistent
-    // state by undoing the peer swap.
+    // Swap the ports' peer information and switch them both to proxying mode.
+    std::swap(port0->peer_node_name, port1->peer_node_name);
+    std::swap(port0->peer_port_name, port1->peer_port_name);
+    port0->state = Port::kProxying;
+    port1->state = Port::kProxying;
+    if (port0->peer_closed)
+      port0->remove_proxy_on_last_message = true;
+    if (port1->peer_closed)
+      port1->remove_proxy_on_last_message = true;
+  }
+
+  // Flush any queued messages from the new proxies and, if successful, complete
+  // the merge by initiating proxy removals.
+  if (ForwardUserMessagesFromProxy(port0_ref) == OK &&
+      ForwardUserMessagesFromProxy(port1_ref) == OK) {
+    for (size_t i = 0; i < 2; ++i) {
+      bool try_remove_proxy_immediately = false;
+      ScopedEvent closure_event;
+      NodeName closure_event_target_node;
+      {
+        SinglePortLocker locker(port_refs[i]);
+        auto* port = locker.port();
+        DCHECK(port->state == Port::kProxying);
+        try_remove_proxy_immediately = port->remove_proxy_on_last_message;
+        if (try_remove_proxy_immediately || port->peer_closed) {
+          // If either end of the port cycle is closed, we propagate an
+          // ObserveClosure event.
+          closure_event_target_node = port->peer_node_name;
+          closure_event = base::MakeUnique<ObserveClosureEvent>(
+              port->peer_port_name, port->last_sequence_num_to_receive);
+        }
+      }
+      if (try_remove_proxy_immediately)
+        TryRemoveProxy(*port_refs[i]);
+      else
+        InitiateProxyRemoval(*port_refs[i]);
+
+      if (closure_event) {
+        delegate_->ForwardEvent(closure_event_target_node,
+                                std::move(closure_event));
+      }
+    }
+
+    return OK;
+  }
+
+  // If we failed to forward proxied messages, we keep the system in a
+  // consistent state by undoing the peer swap and closing the ports.
+  {
+    PortLocker locker(port_refs, 2);
+    auto* port0 = locker.GetPort(port0_ref);
+    auto* port1 = locker.GetPort(port1_ref);
     std::swap(port0->peer_node_name, port1->peer_node_name);
     std::swap(port0->peer_port_name, port1->peer_port_name);
     port0->remove_proxy_on_last_message = false;
     port1->remove_proxy_on_last_message = false;
+    DCHECK_EQ(Port::kProxying, port0->state);
+    DCHECK_EQ(Port::kProxying, port1->state);
     port0->state = Port::kReceiving;
     port1->state = Port::kReceiving;
   }
 
+  ClosePort(port0_ref);
+  ClosePort(port1_ref);
   return ERROR_PORT_STATE_UNEXPECTED;
 }
 
-void Node::WillSendPort(const LockedPort& port,
-                        const NodeName& to_node_name,
-                        PortName* port_name,
-                        Event::PortDescriptor* port_descriptor) {
-  port->lock.AssertAcquired();
-
+void Node::ConvertToProxy(Port* port,
+                          const NodeName& to_node_name,
+                          PortName* port_name,
+                          Event::PortDescriptor* port_descriptor) {
+  port->AssertLockAcquired();
   PortName local_port_name = *port_name;
 
   PortName new_port_name;
-  delegate_->GenerateRandomPortName(&new_port_name);
+  GenerateRandomPortName(&new_port_name);
 
   // Make sure we don't send messages to the new peer until after we know it
   // exists. In the meantime, just buffer messages locally.
@@ -993,18 +947,35 @@
   return OK;
 }
 
-int Node::WillForwardUserMessage_Locked(const LockedPort& port,
-                                        const PortName& port_name,
-                                        UserMessageEvent* message) {
-  ports_lock_.AssertAcquired();
-  port->lock.AssertAcquired();
+int Node::PrepareToForwardUserMessage(const PortRef& forwarding_port_ref,
+                                      Port::State expected_port_state,
+                                      bool ignore_closed_peer,
+                                      UserMessageEvent* message,
+                                      NodeName* forward_to_node) {
+  // We must lock the forwarding port as well as all attached ports.
+  base::StackVector<PortRef, 4> attached_port_refs;
+  base::StackVector<const PortRef*, 5> ports_to_lock;
+  attached_port_refs.container().resize(message->num_ports());
+  ports_to_lock.container().resize(message->num_ports() + 1);
+  ports_to_lock[0] = &forwarding_port_ref;
+  for (size_t i = 0; i < message->num_ports(); ++i) {
+    GetPort(message->ports()[i], &attached_port_refs[i]);
+    DCHECK(attached_port_refs[i].is_valid());
+    ports_to_lock[i + 1] = &attached_port_refs[i];
+  }
+  PortLocker locker(ports_to_lock.container().data(),
+                    ports_to_lock.container().size());
 
-  DCHECK(message);
+  auto* forwarding_port = locker.GetPort(forwarding_port_ref);
+  if (forwarding_port->state != expected_port_state)
+    return ERROR_PORT_STATE_UNEXPECTED;
+  if (forwarding_port->peer_closed && !ignore_closed_peer)
+    return ERROR_PORT_PEER_CLOSED;
 
   // Messages may already have a sequence number if they're being forwarded
   // by a proxy. Otherwise, use the next outgoing sequence number.
   if (message->sequence_num() == 0)
-    message->set_sequence_num(port->next_sequence_num_to_send++);
+    message->set_sequence_num(forwarding_port->next_sequence_num_to_send++);
 #if DCHECK_IS_ON()
   std::ostringstream ports_buf;
   for (size_t i = 0; i < message->num_ports(); ++i) {
@@ -1015,215 +986,147 @@
 #endif
 
   if (message->num_ports() > 0) {
-    // Note: Another thread could be trying to send the same ports, so we need
-    // to ensure that they are ours to send before we mutate their state.
+    // Sanity check to make sure we can actually send all the attached ports.
+    // They must all be in the |kReceiving| state and must not be the sender's
+    // own peer.
+    DCHECK_EQ(message->num_ports(), attached_port_refs.container().size());
+    for (size_t i = 0; i < message->num_ports(); ++i) {
+      auto* attached_port = locker.GetPort(attached_port_refs[i]);
+      int error = OK;
+      if (attached_port->state != Port::kReceiving)
+        error = ERROR_PORT_STATE_UNEXPECTED;
+      else if (attached_port_refs[i].name() == forwarding_port->peer_port_name)
+        error = ERROR_PORT_CANNOT_SEND_PEER;
 
-    std::vector<scoped_refptr<Port>> ports;
-    ports.resize(message->num_ports());
-
-    {
-      for (size_t i = 0; i < message->num_ports(); ++i) {
-        ports[i] = GetPort_Locked(message->ports()[i]);
-        DCHECK(ports[i]);
-
-        ports[i]->lock.Acquire();
-        int error = OK;
-        if (ports[i]->state != Port::kReceiving)
-          error = ERROR_PORT_STATE_UNEXPECTED;
-        else if (message->ports()[i] == port->peer_port_name)
-          error = ERROR_PORT_CANNOT_SEND_PEER;
-
-        if (error != OK) {
-          // Oops, we cannot send this port.
-          for (size_t j = 0; j <= i; ++j)
-            ports[i]->lock.Release();
-          // Backpedal on the sequence number.
-          port->next_sequence_num_to_send--;
-          return error;
-        }
+      if (error != OK) {
+        // Not going to send. Backpedal on the sequence number.
+        forwarding_port->next_sequence_num_to_send--;
+        return error;
       }
     }
 
-    if (port->peer_node_name != name_) {
+    if (forwarding_port->peer_node_name != name_) {
       // We only bother to proxy and rewrite ports in the event if it's going to
       // be routed to an external node. This substantially reduces the amount of
       // port churn in the system, as many port-carrying events are routed at
       // least 1 or 2 intra-node hops before (if ever) being routed externally.
       Event::PortDescriptor* port_descriptors = message->port_descriptors();
       for (size_t i = 0; i < message->num_ports(); ++i) {
-        WillSendPort(LockedPort(ports[i].get()), port->peer_node_name,
-                     message->ports() + i, port_descriptors + i);
+        ConvertToProxy(locker.GetPort(attached_port_refs[i]),
+                       forwarding_port->peer_node_name, message->ports() + i,
+                       port_descriptors + i);
       }
     }
-
-    for (size_t i = 0; i < message->num_ports(); ++i)
-      ports[i]->lock.Release();
   }
 
 #if DCHECK_IS_ON()
   DVLOG(4) << "Sending message " << message->sequence_num()
            << " [ports=" << ports_buf.str() << "]"
-           << " from " << port_name << "@" << name_ << " to "
-           << port->peer_port_name << "@" << port->peer_node_name;
+           << " from " << forwarding_port_ref.name() << "@" << name_ << " to "
+           << forwarding_port->peer_port_name << "@"
+           << forwarding_port->peer_node_name;
 #endif
 
-  message->set_port_name(port->peer_port_name);
+  *forward_to_node = forwarding_port->peer_node_name;
+  message->set_port_name(forwarding_port->peer_port_name);
   return OK;
 }
 
-int Node::BeginProxying_Locked(const LockedPort& port,
-                               const PortName& port_name) {
-  ports_lock_.AssertAcquired();
-  port->lock.AssertAcquired();
+int Node::BeginProxying(const PortRef& port_ref) {
+  {
+    SinglePortLocker locker(&port_ref);
+    auto* port = locker.port();
+    if (port->state != Port::kBuffering)
+      return OOPS(ERROR_PORT_STATE_UNEXPECTED);
+    port->state = Port::kProxying;
+  }
 
-  if (port->state != Port::kBuffering)
-    return OOPS(ERROR_PORT_STATE_UNEXPECTED);
-
-  port->state = Port::kProxying;
-
-  int rv = ForwardMessages_Locked(LockedPort(port), port_name);
+  int rv = ForwardUserMessagesFromProxy(port_ref);
   if (rv != OK)
     return rv;
 
-  // We may have observed closure while buffering. In that case, we can advance
-  // to removing the proxy without sending out an ObserveProxy message. We
-  // already know the last expected message, etc.
-
-  if (port->remove_proxy_on_last_message) {
-    MaybeRemoveProxy_Locked(LockedPort(port), port_name);
-
-    // Make sure we propagate closure to our current peer.
-    delegate_->ForwardEvent(
-        port->peer_node_name,
-        base::MakeUnique<ObserveClosureEvent>(
-            port->peer_port_name, port->last_sequence_num_to_receive));
-  } else {
-    InitiateProxyRemoval(LockedPort(port), port_name);
-  }
-
-  return OK;
-}
-
-int Node::BeginProxying(PortRef port_ref) {
-  Port* port = port_ref.port();
-  {
-    base::AutoLock ports_lock(ports_lock_);
-    base::AutoLock lock(port->lock);
-
-    if (port->state != Port::kBuffering)
-      return OOPS(ERROR_PORT_STATE_UNEXPECTED);
-
-    port->state = Port::kProxying;
-
-    int rv = ForwardMessages_Locked(LockedPort(port), port_ref.name());
-    if (rv != OK)
-      return rv;
-  }
-
-  bool should_remove;
-  NodeName peer_node_name;
+  bool try_remove_proxy_immediately;
   ScopedEvent closure_event;
+  NodeName closure_target_node;
   {
-    base::AutoLock lock(port->lock);
+    SinglePortLocker locker(&port_ref);
+    auto* port = locker.port();
     if (port->state != Port::kProxying)
       return OOPS(ERROR_PORT_STATE_UNEXPECTED);
 
-    should_remove = port->remove_proxy_on_last_message;
-    if (should_remove) {
+    try_remove_proxy_immediately = port->remove_proxy_on_last_message;
+    if (try_remove_proxy_immediately) {
       // Make sure we propagate closure to our current peer.
-      peer_node_name = port->peer_node_name;
+      closure_target_node = port->peer_node_name;
       closure_event = base::MakeUnique<ObserveClosureEvent>(
           port->peer_port_name, port->last_sequence_num_to_receive);
-    } else {
-      InitiateProxyRemoval(LockedPort(port), port_ref.name());
     }
   }
 
-  if (should_remove) {
+  if (try_remove_proxy_immediately) {
     TryRemoveProxy(port_ref);
-    delegate_->ForwardEvent(peer_node_name, std::move(closure_event));
+    delegate_->ForwardEvent(closure_target_node, std::move(closure_event));
+  } else {
+    InitiateProxyRemoval(port_ref);
   }
 
   return OK;
 }
 
-int Node::ForwardMessages_Locked(const LockedPort& port,
-                                 const PortName &port_name) {
-  ports_lock_.AssertAcquired();
-  port->lock.AssertAcquired();
-
+int Node::ForwardUserMessagesFromProxy(const PortRef& port_ref) {
   for (;;) {
+    // NOTE: We forward messages in sequential order here so that we maintain
+    // the message queue's notion of next sequence number. That's useful for the
+    // proxy removal process as we can tell when this port has seen all of the
+    // messages it is expected to see.
     std::unique_ptr<UserMessageEvent> message;
-    port->message_queue.GetNextMessage(&message, nullptr);
-    if (!message)
-      break;
+    {
+      SinglePortLocker locker(&port_ref);
+      locker.port()->message_queue.GetNextMessage(&message, nullptr);
+      if (!message)
+        break;
+    }
 
-    int rv = WillForwardUserMessage_Locked(LockedPort(port), port_name,
-                                           message.get());
+    NodeName target_node;
+    int rv = PrepareToForwardUserMessage(port_ref, Port::kProxying,
+                                         true /* ignore_closed_peer */,
+                                         message.get(), &target_node);
     if (rv != OK)
       return rv;
 
-    delegate_->ForwardEvent(port->peer_node_name, std::move(message));
+    delegate_->ForwardEvent(target_node, std::move(message));
   }
   return OK;
 }
 
-void Node::InitiateProxyRemoval(const LockedPort& port,
-                                const PortName& port_name) {
-  port->lock.AssertAcquired();
+void Node::InitiateProxyRemoval(const PortRef& port_ref) {
+  NodeName peer_node_name;
+  PortName peer_port_name;
+  {
+    SinglePortLocker locker(&port_ref);
+    auto* port = locker.port();
+    peer_node_name = port->peer_node_name;
+    peer_port_name = port->peer_port_name;
+  }
 
   // To remove this node, we start by notifying the connected graph that we are
   // a proxy. This allows whatever port is referencing this node to skip it.
   // Eventually, this node will receive ObserveProxyAck (or ObserveClosure if
   // the peer was closed in the meantime).
-
-  delegate_->ForwardEvent(port->peer_node_name,
+  delegate_->ForwardEvent(peer_node_name,
                           base::MakeUnique<ObserveProxyEvent>(
-                              port->peer_port_name, name_, port_name,
-                              port->peer_node_name, port->peer_port_name));
+                              peer_port_name, name_, port_ref.name(),
+                              peer_node_name, peer_port_name));
 }
 
-void Node::MaybeRemoveProxy_Locked(const LockedPort& port,
-                                   const PortName& port_name) {
-  // |ports_lock_| must be held so we can potentilaly ErasePort_Locked().
-  ports_lock_.AssertAcquired();
-  port->lock.AssertAcquired();
-
-  DCHECK(port->state == Port::kProxying);
-
-  // Make sure we have seen ObserveProxyAck before removing the port.
-  if (!port->remove_proxy_on_last_message)
-    return;
-
-  if (!CanAcceptMoreMessages(port.get())) {
-    // This proxy port is done. We can now remove it!
-    ErasePort_Locked(port_name);
-
-    if (port->send_on_proxy_removal) {
-      NodeName to_node = port->send_on_proxy_removal->first;
-      ScopedEvent& event = port->send_on_proxy_removal->second;
-
-      delegate_->ForwardEvent(to_node, std::move(event));
-      port->send_on_proxy_removal.reset();
-    }
-  } else {
-    DVLOG(2) << "Cannot remove port " << port_name << "@" << name_
-             << " now; waiting for more messages";
-  }
-}
-
-void Node::TryRemoveProxy(PortRef port_ref) {
-  Port* port = port_ref.port();
+void Node::TryRemoveProxy(const PortRef& port_ref) {
   bool should_erase = false;
-  ScopedEvent event;
-  NodeName to_node;
+  NodeName removal_target_node;
+  ScopedEvent removal_event;
+
   {
-    base::AutoLock lock(port->lock);
-
-    // Port already removed. Nothing to do.
-    if (port->state == Port::kClosed)
-      return;
-
+    SinglePortLocker locker(&port_ref);
+    auto* port = locker.port();
     DCHECK(port->state == Port::kProxying);
 
     // Make sure we have seen ObserveProxyAck before removing the port.
@@ -1231,13 +1134,10 @@
       return;
 
     if (!CanAcceptMoreMessages(port)) {
-      // This proxy port is done. We can now remove it!
       should_erase = true;
-
       if (port->send_on_proxy_removal) {
-        to_node = port->send_on_proxy_removal->first;
-        event = std::move(port->send_on_proxy_removal->second);
-        port->send_on_proxy_removal.reset();
+        removal_target_node = port->send_on_proxy_removal->first;
+        removal_event = std::move(port->send_on_proxy_removal->second);
       }
     } else {
       DVLOG(2) << "Cannot remove port " << port_ref.name() << "@" << name_
@@ -1248,8 +1148,8 @@
   if (should_erase)
     ErasePort(port_ref.name());
 
-  if (event)
-    delegate_->ForwardEvent(to_node, std::move(event));
+  if (removal_event)
+    delegate_->ForwardEvent(removal_target_node, std::move(removal_event));
 }
 
 void Node::DestroyAllPortsWithPeer(const NodeName& node_name,
@@ -1260,15 +1160,17 @@
 
   std::vector<PortRef> ports_to_notify;
   std::vector<PortName> dead_proxies_to_broadcast;
-  std::deque<PortName> referenced_port_names;
+  std::vector<PortName> referenced_port_names;
 
   {
+    PortLocker::AssertNoPortsLockedOnCurrentThread();
     base::AutoLock ports_lock(ports_lock_);
 
     for (auto iter = ports_.begin(); iter != ports_.end(); ++iter) {
-      Port* port = iter->second.get();
+      PortRef port_ref(iter->first, iter->second);
       {
-        base::AutoLock port_lock(port->lock);
+        SinglePortLocker locker(&port_ref);
+        auto* port = locker.port();
 
         if (port->peer_node_name == node_name &&
               (port_name == kInvalidPortName ||
@@ -1332,6 +1234,20 @@
   }
 }
 
+Node::DelegateHolder::DelegateHolder(Node* node, NodeDelegate* delegate)
+    : node_(node), delegate_(delegate) {
+  DCHECK(node_);
+}
+
+Node::DelegateHolder::~DelegateHolder() {}
+
+#if DCHECK_IS_ON()
+void Node::DelegateHolder::EnsureSafeDelegateAccess() const {
+  PortLocker::AssertNoPortsLockedOnCurrentThread();
+  base::AutoLock lock(node_->ports_lock_);
+}
+#endif
+
 }  // namespace ports
 }  // namespace edk
 }  // namespace mojo
diff --git a/mojo/edk/system/ports/node.h b/mojo/edk/system/ports/node.h
index 7970279..78fdd35 100644
--- a/mojo/edk/system/ports/node.h
+++ b/mojo/edk/system/ports/node.h
@@ -149,10 +149,31 @@
   int LostConnectionToNode(const NodeName& node_name);
 
  private:
-  class LockedPort;
+  // Helper to ensure that a Node always calls into its delegate safely, i.e.
+  // without holding any internal locks.
+  class DelegateHolder {
+   public:
+    DelegateHolder(Node* node, NodeDelegate* delegate);
+    ~DelegateHolder();
 
-  // Note: Functions that end with _Locked require |ports_lock_| to be held
-  // before calling.
+    NodeDelegate* operator->() const {
+      EnsureSafeDelegateAccess();
+      return delegate_;
+    }
+
+   private:
+#if DCHECK_IS_ON()
+    void EnsureSafeDelegateAccess() const;
+#else
+    void EnsureSafeDelegateAccess() const {}
+#endif
+
+    Node* const node_;
+    NodeDelegate* const delegate_;
+
+    DISALLOW_COPY_AND_ASSIGN(DelegateHolder);
+  };
+
   int OnUserMessage(std::unique_ptr<UserMessageEvent> message);
   int OnPortAccepted(std::unique_ptr<PortAcceptedEvent> event);
   int OnObserveProxy(std::unique_ptr<ObserveProxyEvent> event);
@@ -162,40 +183,37 @@
 
   int AddPortWithName(const PortName& port_name, scoped_refptr<Port> port);
   void ErasePort(const PortName& port_name);
-  void ErasePort_Locked(const PortName& port_name);
-  scoped_refptr<Port> GetPort(const PortName& port_name);
-  scoped_refptr<Port> GetPort_Locked(const PortName& port_name);
 
   int SendUserMessageInternal(const PortRef& port_ref,
                               std::unique_ptr<UserMessageEvent>* message);
-  int MergePorts_Locked(const PortRef& port0_ref, const PortRef& port1_ref);
-  void WillSendPort(const LockedPort& port,
-                    const NodeName& to_node_name,
-                    PortName* port_name,
-                    Event::PortDescriptor* port_descriptor);
+  int MergePortsInternal(const PortRef& port0_ref,
+                         const PortRef& port1_ref,
+                         bool allow_close_on_bad_state);
+  void ConvertToProxy(Port* port,
+                      const NodeName& to_node_name,
+                      PortName* port_name,
+                      Event::PortDescriptor* port_descriptor);
   int AcceptPort(const PortName& port_name,
                  const Event::PortDescriptor& port_descriptor);
 
-  int WillForwardUserMessage_Locked(const LockedPort& port,
-                                    const PortName& port_name,
-                                    UserMessageEvent* message);
-  int BeginProxying_Locked(const LockedPort& port, const PortName& port_name);
-  int BeginProxying(PortRef port_ref);
-  int ForwardMessages_Locked(const LockedPort& port, const PortName& port_name);
-  void InitiateProxyRemoval(const LockedPort& port, const PortName& port_name);
-  void MaybeRemoveProxy_Locked(const LockedPort& port,
-                               const PortName& port_name);
-  void TryRemoveProxy(PortRef port_ref);
+  int PrepareToForwardUserMessage(const PortRef& forwarding_port_ref,
+                                  Port::State expected_port_state,
+                                  bool ignore_closed_peer,
+                                  UserMessageEvent* message,
+                                  NodeName* forward_to_node);
+  int BeginProxying(const PortRef& port_ref);
+  int ForwardUserMessagesFromProxy(const PortRef& port_ref);
+  void InitiateProxyRemoval(const PortRef& port_ref);
+  void TryRemoveProxy(const PortRef& port_ref);
   void DestroyAllPortsWithPeer(const NodeName& node_name,
                                const PortName& port_name);
 
   const NodeName name_;
-  NodeDelegate* const delegate_;
+  const DelegateHolder delegate_;
 
-  // Guards |ports_| as well as any operation which needs to hold multiple port
-  // locks simultaneously. Usage of this is subtle: it must NEVER be acquired
-  // after a Port lock is acquired, and it must ALWAYS be acquired before
-  // calling WillForwardUserMessage_Locked or ForwardMessages_Locked.
+  // Guards |ports_|. This must never be acquired while an individual port's
+  // lock is held on the same thread. Conversely, individual port locks may be
+  // acquired while this one is held.
   base::Lock ports_lock_;
   std::unordered_map<PortName, scoped_refptr<Port>> ports_;
 
diff --git a/mojo/edk/system/ports/node_delegate.h b/mojo/edk/system/ports/node_delegate.h
index 13f264f..3c7550a 100644
--- a/mojo/edk/system/ports/node_delegate.h
+++ b/mojo/edk/system/ports/node_delegate.h
@@ -19,11 +19,7 @@
  public:
   virtual ~NodeDelegate() {}
 
-  // Port names should be difficult to guess.
-  virtual void GenerateRandomPortName(PortName* port_name) = 0;
-
-  // Forward an event asynchronously to the specified node. This method MUST NOT
-  // synchronously call any methods on Node.
+  // Forward an event (possibly asynchronously) to the specified node.
   virtual void ForwardEvent(const NodeName& node, ScopedEvent event) = 0;
 
   // Broadcast an event to all nodes.
diff --git a/mojo/edk/system/ports/port.h b/mojo/edk/system/ports/port.h
index aa16d3b1..514aa49 100644
--- a/mojo/edk/system/ports/port.h
+++ b/mojo/edk/system/ports/port.h
@@ -21,6 +21,8 @@
 namespace edk {
 namespace ports {
 
+class PortLocker;
+
 class Port : public base::RefCountedThreadSafe<Port> {
  public:
   enum State {
@@ -31,7 +33,6 @@
     kClosed
   };
 
-  base::Lock lock;
   State state;
   NodeName peer_node_name;
   PortName peer_port_name;
@@ -46,11 +47,20 @@
   Port(uint64_t next_sequence_num_to_send,
        uint64_t next_sequence_num_to_receive);
 
+  void AssertLockAcquired() {
+#if DCHECK_IS_ON()
+    lock_.AssertAcquired();
+#endif
+  }
+
  private:
   friend class base::RefCountedThreadSafe<Port>;
+  friend class PortLocker;
 
   ~Port();
 
+  base::Lock lock_;
+
   DISALLOW_COPY_AND_ASSIGN(Port);
 };
 
diff --git a/mojo/edk/system/ports/port_locker.cc b/mojo/edk/system/ports/port_locker.cc
new file mode 100644
index 0000000..f9c627c
--- /dev/null
+++ b/mojo/edk/system/ports/port_locker.cc
@@ -0,0 +1,75 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/edk/system/ports/port_locker.h"
+
+#include <algorithm>
+
+#include "mojo/edk/system/ports/port.h"
+
+#if DCHECK_IS_ON()
+#include "base/threading/thread_local.h"
+#endif
+
+namespace mojo {
+namespace edk {
+namespace ports {
+
+namespace {
+
+#if DCHECK_IS_ON()
+void UpdateTLS(PortLocker* old_locker, PortLocker* new_locker) {
+  // Sanity check when DCHECK is on to make sure there is only ever one
+  // PortLocker extant on the current thread.
+  static auto* tls = new base::ThreadLocalPointer<PortLocker>();
+  DCHECK_EQ(old_locker, tls->Get());
+  tls->Set(new_locker);
+}
+#endif
+
+}  // namespace
+
+PortLocker::PortLocker(const PortRef** port_refs, size_t num_ports)
+    : port_refs_(port_refs), num_ports_(num_ports) {
+#if DCHECK_IS_ON()
+  UpdateTLS(nullptr, this);
+#endif
+
+  // Sort the ports by address to lock them in a globally consistent order.
+  std::sort(port_refs_, port_refs_ + num_ports_, ComparePortRefsByPortAddress);
+  for (size_t i = 0; i < num_ports_; ++i)
+    port_refs_[i]->port()->lock_.Acquire();
+}
+
+PortLocker::~PortLocker() {
+  for (size_t i = 0; i < num_ports_; ++i)
+    port_refs_[i]->port()->lock_.Release();
+
+#if DCHECK_IS_ON()
+  UpdateTLS(this, nullptr);
+#endif
+}
+
+#if DCHECK_IS_ON()
+// static
+void PortLocker::AssertNoPortsLockedOnCurrentThread() {
+  // Forces a DCHECK if the TLS PortLocker is anything other than null.
+  UpdateTLS(nullptr, nullptr);
+}
+#endif
+
+// static
+bool PortLocker::ComparePortRefsByPortAddress(const PortRef* a,
+                                              const PortRef* b) {
+  return a->port() < b->port();
+}
+
+SinglePortLocker::SinglePortLocker(const PortRef* port_ref)
+    : port_ref_(port_ref), locker_(&port_ref_, 1) {}
+
+SinglePortLocker::~SinglePortLocker() = default;
+
+}  // namespace ports
+}  // namespace edk
+}  // namespace mojo
diff --git a/mojo/edk/system/ports/port_locker.h b/mojo/edk/system/ports/port_locker.h
new file mode 100644
index 0000000..0deeb87
--- /dev/null
+++ b/mojo/edk/system/ports/port_locker.h
@@ -0,0 +1,88 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_EDK_SYSTEM_PORTS_PORT_LOCKER_H_
+#define MOJO_EDK_SYSTEM_PORTS_PORT_LOCKER_H_
+
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "mojo/edk/system/ports/port_ref.h"
+
+namespace mojo {
+namespace edk {
+namespace ports {
+
+class Port;
+class PortRef;
+
+// A helper which must be used to acquire individual Port locks. Any given
+// thread may have at most one of these alive at any time. This ensures that
+// when multiple ports are locked, they're locked in globally consistent order.
+//
+// Port locks are acquired upon construction of this object and released upon
+// destruction.
+class PortLocker {
+ public:
+  // Constructs a PortLocker over a sequence of |num_ports| contiguous
+  // |PortRef*|s. The sequence may be reordered by this constructor, and upon
+  // return, all referenced ports' locks are held.
+  PortLocker(const PortRef** port_refs, size_t num_ports);
+  ~PortLocker();
+
+  // Provides safe access to a PortRef's Port. Note that in release builds this
+  // doesn't do anything other than pass through to the private accessor on
+  // |port_ref|, but it does force callers to go through a PortLocker to get to
+  // the state, thus minimizing the likelihood that they'll go and do something
+  // stupid.
+  Port* GetPort(const PortRef& port_ref) const {
+#if DCHECK_IS_ON()
+    // Sanity check when DCHECK is on to ensure this is actually a port whose
+    // lock is held by this PortLocker.
+    bool is_port_locked = false;
+    for (size_t i = 0; i < num_ports_ && !is_port_locked; ++i)
+      if (port_refs_[i]->port() == port_ref.port())
+        is_port_locked = true;
+    DCHECK(is_port_locked);
+#endif
+    return port_ref.port();
+  }
+
+// A helper which can be used to verify that no Port locks are held on the
+// current thread. In non-DCHECK builds this is a no-op.
+#if DCHECK_IS_ON()
+  static void AssertNoPortsLockedOnCurrentThread();
+#else
+  static void AssertNoPortsLockedOnCurrentThread() {}
+#endif
+
+ private:
+  static bool ComparePortRefsByPortAddress(const PortRef* a, const PortRef* b);
+
+  const PortRef** const port_refs_;
+  const size_t num_ports_;
+
+  DISALLOW_COPY_AND_ASSIGN(PortLocker);
+};
+
+// Convenience wrapper for a PortLocker that locks a single port.
+class SinglePortLocker {
+ public:
+  explicit SinglePortLocker(const PortRef* port_ref);
+  ~SinglePortLocker();
+
+  Port* port() const { return locker_.GetPort(*port_ref_); }
+
+ private:
+  const PortRef* port_ref_;
+  PortLocker locker_;
+
+  DISALLOW_COPY_AND_ASSIGN(SinglePortLocker);
+};
+
+}  // namespace ports
+}  // namespace edk
+}  // namespace mojo
+
+#endif  // MOJO_EDK_SYSTEM_PORTS_PORT_LOCKER_H_
diff --git a/mojo/edk/system/ports/port_ref.cc b/mojo/edk/system/ports/port_ref.cc
index 675754d..66e48c2 100644
--- a/mojo/edk/system/ports/port_ref.cc
+++ b/mojo/edk/system/ports/port_ref.cc
@@ -19,17 +19,13 @@
 PortRef::PortRef(const PortName& name, scoped_refptr<Port> port)
     : name_(name), port_(std::move(port)) {}
 
-PortRef::PortRef(const PortRef& other)
-    : name_(other.name_), port_(other.port_) {
-}
+PortRef::PortRef(const PortRef& other) = default;
 
-PortRef& PortRef::operator=(const PortRef& other) {
-  if (&other != this) {
-    name_ = other.name_;
-    port_ = other.port_;
-  }
-  return *this;
-}
+PortRef::PortRef(PortRef&& other) = default;
+
+PortRef& PortRef::operator=(const PortRef& other) = default;
+
+PortRef& PortRef::operator=(PortRef&& other) = default;
 
 }  // namespace ports
 }  // namespace edk
diff --git a/mojo/edk/system/ports/port_ref.h b/mojo/edk/system/ports/port_ref.h
index 59036c3..dce317e 100644
--- a/mojo/edk/system/ports/port_ref.h
+++ b/mojo/edk/system/ports/port_ref.h
@@ -5,6 +5,7 @@
 #ifndef MOJO_EDK_SYSTEM_PORTS_PORT_REF_H_
 #define MOJO_EDK_SYSTEM_PORTS_PORT_REF_H_
 
+#include "base/logging.h"
 #include "base/memory/ref_counted.h"
 #include "mojo/edk/system/ports/name.h"
 
@@ -13,7 +14,7 @@
 namespace ports {
 
 class Port;
-class Node;
+class PortLocker;
 
 class PortRef {
  public:
@@ -22,12 +23,18 @@
   PortRef(const PortName& name, scoped_refptr<Port> port);
 
   PortRef(const PortRef& other);
+  PortRef(PortRef&& other);
+
   PortRef& operator=(const PortRef& other);
+  PortRef& operator=(PortRef&& other);
 
   const PortName& name() const { return name_; }
 
+  bool is_valid() const { return !!port_; }
+
  private:
-  friend class Node;
+  friend class PortLocker;
+
   Port* port() const { return port_.get(); }
 
   PortName name_;
diff --git a/mojo/edk/system/ports/ports_unittest.cc b/mojo/edk/system/ports/ports_unittest.cc
index d4aa9db..766fb8d 100644
--- a/mojo/edk/system/ports/ports_unittest.cc
+++ b/mojo/edk/system/ports/ports_unittest.cc
@@ -16,7 +16,6 @@
 #include "base/callback.h"
 #include "base/logging.h"
 #include "base/memory/ref_counted.h"
-#include "base/rand_util.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/stringprintf.h"
 #include "base/synchronization/lock.h"
@@ -72,7 +71,6 @@
  public:
   virtual ~MessageRouter() {}
 
-  virtual void GeneratePortName(PortName* name) = 0;
   virtual void ForwardEvent(TestNode* from_node,
                             const NodeName& node_name,
                             ScopedEvent event) = 0;
@@ -190,11 +188,6 @@
     events_available_event_.Signal();
   }
 
-  void GenerateRandomPortName(PortName* port_name) override {
-    DCHECK(router_);
-    router_->GeneratePortName(port_name);
-  }
-
   void ForwardEvent(const NodeName& node_name, ScopedEvent event) override {
     {
       base::AutoLock lock(lock_);
@@ -367,12 +360,6 @@
 
  private:
   // MessageRouter:
-  void GeneratePortName(PortName* name) override {
-    base::AutoLock lock(lock_);
-    name->v1 = next_port_id_++;
-    name->v2 = 0;
-  }
-
   void ForwardEvent(TestNode* from_node,
                     const NodeName& node_name,
                     ScopedEvent event) override {
@@ -417,7 +404,6 @@
   base::Lock global_lock_;
 
   base::Lock lock_;
-  uint64_t next_port_id_ = 1;
   std::map<NodeName, TestNode*> nodes_;
 };
 
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 7e94fbcd..43ecc96 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -3786,6 +3786,10 @@
     "data/verify_certificate_chain_unittest/target-unknown-critical-extension/main.test",
     "data/verify_certificate_chain_unittest/target-wrong-signature/chain.pem",
     "data/verify_certificate_chain_unittest/target-wrong-signature/main.test",
+    "data/verify_certificate_chain_unittest/unknown-critical-policy-qualifier/chain.pem",
+    "data/verify_certificate_chain_unittest/unknown-critical-policy-qualifier/main.test",
+    "data/verify_certificate_chain_unittest/unknown-non-critical-policy-qualifier/chain.pem",
+    "data/verify_certificate_chain_unittest/unknown-non-critical-policy-qualifier/main.test",
     "data/verify_certificate_chain_unittest/violates-basic-constraints-pathlen-0/chain.pem",
     "data/verify_certificate_chain_unittest/violates-basic-constraints-pathlen-0/main.test",
     "data/verify_certificate_chain_unittest/violates-pathlen-1-from-root/chain.pem",
diff --git a/net/cert/internal/certificate_policies.cc b/net/cert/internal/certificate_policies.cc
index 58f2dc9..4ef81e4 100644
--- a/net/cert/internal/certificate_policies.cc
+++ b/net/cert/internal/certificate_policies.cc
@@ -39,7 +39,7 @@
 // Ignores the policyQualifiers, but does some minimal correctness checking.
 // TODO(mattm): parse and return the policyQualifiers, since the cert viewer
 // still needs to display them.
-bool ParsePolicyQualifiers(const der::Input& policy_oid,
+bool ParsePolicyQualifiers(bool restrict_to_known_qualifiers,
                            der::Parser* policy_qualifiers_sequence_parser) {
   // If it is present, the policyQualifiers sequence should have at least 1
   // element.
@@ -54,10 +54,7 @@
     der::Input qualifier_oid;
     if (!policy_information_parser.ReadTag(der::kOid, &qualifier_oid))
       return false;
-    // RFC 5280 section 4.2.1.4: When qualifiers are used with the special
-    // policy anyPolicy, they MUST be limited to the qualifiers identified in
-    // this section.
-    if (policy_oid == AnyPolicy() && qualifier_oid != CpsPointerId() &&
+    if (restrict_to_known_qualifiers && qualifier_oid != CpsPointerId() &&
         qualifier_oid != UserNoticeId()) {
       return false;
     }
@@ -143,6 +140,7 @@
 //      bmpString        BMPString      (SIZE (1..200)),
 //      utf8String       UTF8String     (SIZE (1..200)) }
 bool ParseCertificatePoliciesExtension(const der::Input& extension_value,
+                                       bool fail_parsing_unknown_qualifier_oids,
                                        std::vector<der::Input>* policies) {
   der::Parser extension_parser(extension_value);
   der::Parser policies_sequence_parser;
@@ -188,8 +186,15 @@
     // Should not have trailing data after policyQualifiers sequence.
     if (policy_information_parser.HasMore())
       return false;
-    if (!ParsePolicyQualifiers(policy_oid, &policy_qualifiers_sequence_parser))
+
+    // RFC 5280 section 4.2.1.4: When qualifiers are used with the special
+    // policy anyPolicy, they MUST be limited to the qualifiers identified in
+    // this section.
+    if (!ParsePolicyQualifiers(
+            fail_parsing_unknown_qualifier_oids || policy_oid == AnyPolicy(),
+            &policy_qualifiers_sequence_parser)) {
       return false;
+    }
   }
 
   return true;
diff --git a/net/cert/internal/certificate_policies.h b/net/cert/internal/certificate_policies.h
index ab9af9f..15e8bfb6 100644
--- a/net/cert/internal/certificate_policies.h
+++ b/net/cert/internal/certificate_policies.h
@@ -34,15 +34,21 @@
 NET_EXPORT der::Input PolicyMappingsOid();
 
 // Parses a certificatePolicies extension and stores the policy OIDs in
-// |*policies|, in sorted order. If policyQualifiers are present,
-// they are ignored. (RFC 5280 section 4.2.1.4 says "optional qualifiers, which
-// MAY be present, are not expected to change the definition of the policy.",
-// furthermore policyQualifiers do not affect the success or failure of the
-// section 6 Certification Path Validation algorithm.)
+// |*policies|, in sorted order.
+//
+// If policyQualifiers for User Notice or CPS are present then they are
+// ignored (RFC 5280 section 4.2.1.4 says "optional qualifiers, which MAY
+// be present, are not expected to change the definition of the policy."
+//
+// If a policy qualifier other than User Notice/CPS is present, parsing
+// will fail if |fail_parsing_unknown_qualifier_oids| was set to true,
+// otherwise the unrecognized qualifiers wil be skipped and not parsed
+// any further.
 //
 // The returned values is only valid as long as |extension_value| is.
 NET_EXPORT bool ParseCertificatePoliciesExtension(
     const der::Input& extension_value,
+    bool fail_parsing_unknown_qualifier_oids,
     std::vector<der::Input>* policies);
 
 struct ParsedPolicyConstraints {
diff --git a/net/cert/internal/certificate_policies_unittest.cc b/net/cert/internal/certificate_policies_unittest.cc
index ee67579..1a0be30 100644
--- a/net/cert/internal/certificate_policies_unittest.cc
+++ b/net/cert/internal/certificate_policies_unittest.cc
@@ -26,122 +26,156 @@
 const uint8_t policy_1_2_3_der[] = {0x2A, 0x03};
 const uint8_t policy_1_2_4_der[] = {0x2A, 0x04};
 
-}  // namespace
+class ParseCertificatePoliciesExtensionTest
+    : public testing::TestWithParam<bool> {
+ protected:
+  bool fail_parsing_unknown_qualifier_oids() const { return GetParam(); }
+};
 
-TEST(ParseCertificatePoliciesTest, InvalidEmpty) {
+// Run the tests with all possible values for
+// |fail_parsing_unknown_qualifier_oids|.
+INSTANTIATE_TEST_CASE_P(,
+                        ParseCertificatePoliciesExtensionTest,
+                        testing::Bool());
+
+TEST_P(ParseCertificatePoliciesExtensionTest, InvalidEmpty) {
   std::string der;
   ASSERT_TRUE(LoadTestData("invalid-empty.pem", &der));
   std::vector<der::Input> policies;
-  EXPECT_FALSE(ParseCertificatePoliciesExtension(der::Input(&der), &policies));
+  EXPECT_FALSE(ParseCertificatePoliciesExtension(
+      der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies));
 }
 
-TEST(ParseCertificatePoliciesTest, InvalidIdentifierNotOid) {
+TEST_P(ParseCertificatePoliciesExtensionTest, InvalidIdentifierNotOid) {
   std::string der;
   ASSERT_TRUE(LoadTestData("invalid-policy_identifier_not_oid.pem", &der));
   std::vector<der::Input> policies;
-  EXPECT_FALSE(ParseCertificatePoliciesExtension(der::Input(&der), &policies));
+  EXPECT_FALSE(ParseCertificatePoliciesExtension(
+      der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies));
 }
 
-TEST(ParseCertificatePoliciesTest, AnyPolicy) {
+TEST_P(ParseCertificatePoliciesExtensionTest, AnyPolicy) {
   std::string der;
   ASSERT_TRUE(LoadTestData("anypolicy.pem", &der));
   std::vector<der::Input> policies;
-  EXPECT_TRUE(ParseCertificatePoliciesExtension(der::Input(&der), &policies));
+  EXPECT_TRUE(ParseCertificatePoliciesExtension(
+      der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies));
   ASSERT_EQ(1U, policies.size());
   EXPECT_EQ(AnyPolicy(), policies[0]);
 }
 
-TEST(ParseCertificatePoliciesTest, AnyPolicyWithQualifier) {
+TEST_P(ParseCertificatePoliciesExtensionTest, AnyPolicyWithQualifier) {
   std::string der;
   ASSERT_TRUE(LoadTestData("anypolicy_with_qualifier.pem", &der));
   std::vector<der::Input> policies;
-  EXPECT_TRUE(ParseCertificatePoliciesExtension(der::Input(&der), &policies));
+  EXPECT_TRUE(ParseCertificatePoliciesExtension(
+      der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies));
   ASSERT_EQ(1U, policies.size());
   EXPECT_EQ(AnyPolicy(), policies[0]);
 }
 
-TEST(ParseCertificatePoliciesTest, InvalidAnyPolicyWithCustomQualifier) {
+TEST_P(ParseCertificatePoliciesExtensionTest,
+       InvalidAnyPolicyWithCustomQualifier) {
   std::string der;
   ASSERT_TRUE(
       LoadTestData("invalid-anypolicy_with_custom_qualifier.pem", &der));
   std::vector<der::Input> policies;
-  EXPECT_FALSE(ParseCertificatePoliciesExtension(der::Input(&der), &policies));
+  EXPECT_FALSE(ParseCertificatePoliciesExtension(
+      der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies));
 }
 
-TEST(ParseCertificatePoliciesTest, OnePolicy) {
+TEST_P(ParseCertificatePoliciesExtensionTest, OnePolicy) {
   std::string der;
   ASSERT_TRUE(LoadTestData("policy_1_2_3.pem", &der));
   std::vector<der::Input> policies;
-  EXPECT_TRUE(ParseCertificatePoliciesExtension(der::Input(&der), &policies));
+  EXPECT_TRUE(ParseCertificatePoliciesExtension(
+      der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies));
   ASSERT_EQ(1U, policies.size());
   EXPECT_EQ(der::Input(policy_1_2_3_der), policies[0]);
 }
 
-TEST(ParseCertificatePoliciesTest, OnePolicyWithQualifier) {
+TEST_P(ParseCertificatePoliciesExtensionTest, OnePolicyWithQualifier) {
   std::string der;
   ASSERT_TRUE(LoadTestData("policy_1_2_3_with_qualifier.pem", &der));
   std::vector<der::Input> policies;
-  EXPECT_TRUE(ParseCertificatePoliciesExtension(der::Input(&der), &policies));
+  EXPECT_TRUE(ParseCertificatePoliciesExtension(
+      der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies));
   ASSERT_EQ(1U, policies.size());
   EXPECT_EQ(der::Input(policy_1_2_3_der), policies[0]);
 }
 
-TEST(ParseCertificatePoliciesTest, OnePolicyWithCustomQualifier) {
+TEST_P(ParseCertificatePoliciesExtensionTest, OnePolicyWithCustomQualifier) {
   std::string der;
   ASSERT_TRUE(LoadTestData("policy_1_2_3_with_custom_qualifier.pem", &der));
   std::vector<der::Input> policies;
-  EXPECT_TRUE(ParseCertificatePoliciesExtension(der::Input(&der), &policies));
-  ASSERT_EQ(1U, policies.size());
-  EXPECT_EQ(der::Input(policy_1_2_3_der), policies[0]);
+  bool result = ParseCertificatePoliciesExtension(
+      der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies);
+
+  if (fail_parsing_unknown_qualifier_oids()) {
+    EXPECT_FALSE(result);
+  } else {
+    EXPECT_TRUE(result);
+    ASSERT_EQ(1U, policies.size());
+    EXPECT_EQ(der::Input(policy_1_2_3_der), policies[0]);
+  }
 }
 
-TEST(ParseCertificatePoliciesTest, InvalidPolicyWithDuplicatePolicyOid) {
+TEST_P(ParseCertificatePoliciesExtensionTest,
+       InvalidPolicyWithDuplicatePolicyOid) {
   std::string der;
   ASSERT_TRUE(LoadTestData("invalid-policy_1_2_3_dupe.pem", &der));
   std::vector<der::Input> policies;
-  EXPECT_FALSE(ParseCertificatePoliciesExtension(der::Input(&der), &policies));
+  EXPECT_FALSE(ParseCertificatePoliciesExtension(
+      der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies));
 }
 
-TEST(ParseCertificatePoliciesTest, InvalidPolicyWithEmptyQualifiersSequence) {
+TEST_P(ParseCertificatePoliciesExtensionTest,
+       InvalidPolicyWithEmptyQualifiersSequence) {
   std::string der;
   ASSERT_TRUE(LoadTestData(
       "invalid-policy_1_2_3_with_empty_qualifiers_sequence.pem", &der));
   std::vector<der::Input> policies;
-  EXPECT_FALSE(ParseCertificatePoliciesExtension(der::Input(&der), &policies));
+  EXPECT_FALSE(ParseCertificatePoliciesExtension(
+      der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies));
 }
 
-TEST(ParseCertificatePoliciesTest, InvalidPolicyInformationHasUnconsumedData) {
+TEST_P(ParseCertificatePoliciesExtensionTest,
+       InvalidPolicyInformationHasUnconsumedData) {
   std::string der;
   ASSERT_TRUE(LoadTestData(
       "invalid-policy_1_2_3_policyinformation_unconsumed_data.pem", &der));
   std::vector<der::Input> policies;
-  EXPECT_FALSE(ParseCertificatePoliciesExtension(der::Input(&der), &policies));
+  EXPECT_FALSE(ParseCertificatePoliciesExtension(
+      der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies));
 }
 
-TEST(ParseCertificatePoliciesTest,
-     InvalidPolicyQualifierInfoHasUnconsumedData) {
+TEST_P(ParseCertificatePoliciesExtensionTest,
+       InvalidPolicyQualifierInfoHasUnconsumedData) {
   std::string der;
   ASSERT_TRUE(LoadTestData(
       "invalid-policy_1_2_3_policyqualifierinfo_unconsumed_data.pem", &der));
   std::vector<der::Input> policies;
-  EXPECT_FALSE(ParseCertificatePoliciesExtension(der::Input(&der), &policies));
+  EXPECT_FALSE(ParseCertificatePoliciesExtension(
+      der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies));
 }
 
-TEST(ParseCertificatePoliciesTest, TwoPolicies) {
+TEST_P(ParseCertificatePoliciesExtensionTest, TwoPolicies) {
   std::string der;
   ASSERT_TRUE(LoadTestData("policy_1_2_3_and_1_2_4.pem", &der));
   std::vector<der::Input> policies;
-  EXPECT_TRUE(ParseCertificatePoliciesExtension(der::Input(&der), &policies));
+  EXPECT_TRUE(ParseCertificatePoliciesExtension(
+      der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies));
   ASSERT_EQ(2U, policies.size());
   EXPECT_EQ(der::Input(policy_1_2_3_der), policies[0]);
   EXPECT_EQ(der::Input(policy_1_2_4_der), policies[1]);
 }
 
-TEST(ParseCertificatePoliciesTest, TwoPoliciesWithQualifiers) {
+TEST_P(ParseCertificatePoliciesExtensionTest, TwoPoliciesWithQualifiers) {
   std::string der;
   ASSERT_TRUE(LoadTestData("policy_1_2_3_and_1_2_4_with_qualifiers.pem", &der));
   std::vector<der::Input> policies;
-  EXPECT_TRUE(ParseCertificatePoliciesExtension(der::Input(&der), &policies));
+  EXPECT_TRUE(ParseCertificatePoliciesExtension(
+      der::Input(&der), fail_parsing_unknown_qualifier_oids(), &policies));
   ASSERT_EQ(2U, policies.size());
   EXPECT_EQ(der::Input(policy_1_2_3_der), policies[0]);
   EXPECT_EQ(der::Input(policy_1_2_4_der), policies[1]);
@@ -153,4 +187,5 @@
 // NOTE: The tests for ParseInhibitAnyPolicy() are part of
 // parsed_certificate_unittest.cc
 
+}  // namespace
 }  // namespace net
diff --git a/net/cert/internal/parsed_certificate.cc b/net/cert/internal/parsed_certificate.cc
index 53f0dd5..c8cd67ec 100644
--- a/net/cert/internal/parsed_certificate.cc
+++ b/net/cert/internal/parsed_certificate.cc
@@ -189,8 +189,9 @@
     // Policies.
     if (result->GetExtension(CertificatePoliciesOid(), &extension)) {
       result->has_policy_oids_ = true;
-      if (!ParseCertificatePoliciesExtension(extension.value,
-                                             &result->policy_oids_)) {
+      if (!ParseCertificatePoliciesExtension(
+              extension.value, false /*fail_parsing_unknown_qualifier_oids*/,
+              &result->policy_oids_)) {
         return nullptr;
       }
     }
diff --git a/net/cert/internal/verify_certificate_chain.cc b/net/cert/internal/verify_certificate_chain.cc
index d4f6bc8..4f2c088b 100644
--- a/net/cert/internal/verify_certificate_chain.cc
+++ b/net/cert/internal/verify_certificate_chain.cc
@@ -68,31 +68,42 @@
 DEFINE_CERT_ERROR_ID(kPolicyMappingAnyPolicy,
                      "PolicyMappings must not map anyPolicy");
 
-bool IsHandledCriticalExtensionOid(const der::Input& oid) {
-  if (oid == BasicConstraintsOid())
+bool IsHandledCriticalExtension(const ParsedExtension& extension) {
+  if (extension.oid == BasicConstraintsOid())
     return true;
   // Key Usage is NOT processed for end-entity certificates (this is the
   // responsibility of callers), however it is considered "handled" here in
   // order to allow being marked as critical.
-  if (oid == KeyUsageOid())
+  if (extension.oid == KeyUsageOid())
     return true;
-  if (oid == ExtKeyUsageOid())
+  if (extension.oid == ExtKeyUsageOid())
     return true;
-  if (oid == NameConstraintsOid())
+  if (extension.oid == NameConstraintsOid())
     return true;
-  if (oid == SubjectAltNameOid())
+  if (extension.oid == SubjectAltNameOid())
     return true;
-  // TODO(eroman): The policy qualifiers are not processed (or in some cases
-  // even parsed). This is fine when the policies extension is non-critical,
-  // however if it is critical the code should also ensure that the policy
-  // qualifiers are only recognized ones (CPS and User Notice).
-  if (oid == CertificatePoliciesOid())
+  if (extension.oid == CertificatePoliciesOid()) {
+    // Policy qualifiers are skipped during processing, so if the
+    // extension is marked critical need to ensure there weren't any
+    // qualifiers other than User Notice / CPS.
+    //
+    // This follows from RFC 5280 section 4.2.1.4:
+    //
+    //   If this extension is critical, the path validation software MUST
+    //   be able to interpret this extension (including the optional
+    //   qualifier), or MUST reject the certificate.
+    std::vector<der::Input> unused_policies;
+    return ParseCertificatePoliciesExtension(
+        extension.value, true /*fail_parsing_unknown_qualifier_oids*/,
+        &unused_policies);
+
+    // TODO(eroman): Give a better error message.
+  }
+  if (extension.oid == PolicyMappingsOid())
     return true;
-  if (oid == PolicyMappingsOid())
+  if (extension.oid == PolicyConstraintsOid())
     return true;
-  if (oid == PolicyConstraintsOid())
-    return true;
-  if (oid == InhibitAnyPolicyOid())
+  if (extension.oid == InhibitAnyPolicyOid())
     return true;
 
   return false;
@@ -104,7 +115,7 @@
                                           CertErrors* errors) {
   for (const auto& it : cert.extensions()) {
     const ParsedExtension& extension = it.second;
-    if (extension.critical && !IsHandledCriticalExtensionOid(extension.oid)) {
+    if (extension.critical && !IsHandledCriticalExtension(extension)) {
       errors->AddError(kUnconsumedCriticalExtension,
                        CreateCertErrorParams2Der("oid", extension.oid, "value",
                                                  extension.value));
diff --git a/net/cert/internal/verify_certificate_chain_typed_unittest.h b/net/cert/internal/verify_certificate_chain_typed_unittest.h
index 9135c9c..9175829b 100644
--- a/net/cert/internal/verify_certificate_chain_typed_unittest.h
+++ b/net/cert/internal/verify_certificate_chain_typed_unittest.h
@@ -153,6 +153,13 @@
   this->RunTest("key-rollover/newchain.test");
 }
 
+// Test coverage of policies comes primarily from the PKITS tests. The
+// tests here only cover aspects not already tested by PKITS.
+TYPED_TEST_P(VerifyCertificateChainSingleRootTest, Policies) {
+  this->RunTest("unknown-critical-policy-qualifier/main.test");
+  this->RunTest("unknown-non-critical-policy-qualifier/main.test");
+}
+
 // TODO(eroman): Add test that invalid validity dates where the day or month
 // ordinal not in range, like "March 39, 2016" are rejected.
 
@@ -172,7 +179,8 @@
                            ExtendedKeyUsage,
                            IssuerAndSubjectNotByteForByteEqual,
                            TrustAnchorNotSelfSigned,
-                           KeyRollover);
+                           KeyRollover,
+                           Policies);
 
 }  // namespace net
 
diff --git a/net/data/verify_certificate_chain_unittest/unknown-critical-policy-qualifier/chain.pem b/net/data/verify_certificate_chain_unittest/unknown-critical-policy-qualifier/chain.pem
new file mode 100644
index 0000000..496fafca
--- /dev/null
+++ b/net/data/verify_certificate_chain_unittest/unknown-critical-policy-qualifier/chain.pem
@@ -0,0 +1,277 @@
+[Created by: generate-chains.py]
+
+The intermediate has a policies extension marked as critical, which contains
+an unknown qualifer (1.2.3.4).
+
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1 (0x1)
+    Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN=Intermediate
+        Validity
+            Not Before: Jan  1 12:00:00 2015 GMT
+            Not After : Jan  1 12:00:00 2016 GMT
+        Subject: CN=Target
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:c1:03:58:01:b1:2f:7b:fb:b2:71:dc:49:d0:cb:
+                    06:76:30:64:f7:61:bf:da:55:93:73:29:49:0f:cb:
+                    0a:33:bd:41:0b:28:03:45:35:72:a9:b4:4b:a7:ec:
+                    52:77:3a:8c:ba:cb:87:56:28:3b:39:8d:47:7b:70:
+                    7f:5a:8f:76:8c:7e:13:e8:61:17:19:1d:72:e3:6e:
+                    69:20:bc:83:f7:5b:11:85:6e:1a:b8:fb:7b:f8:fe:
+                    2b:e2:d2:bd:1a:0a:65:62:b0:84:a7:0a:ac:75:ea:
+                    e6:74:c4:1d:2c:e8:04:62:76:4b:4d:04:b6:52:2f:
+                    a6:ba:66:bb:fe:45:d6:6a:21:05:16:e5:f3:25:ae:
+                    94:fd:17:84:80:2f:ac:62:d9:83:e3:17:b0:03:1c:
+                    01:02:8b:47:7f:65:2e:f9:40:cf:ad:92:33:07:8a:
+                    14:44:5e:c2:ed:68:48:a4:d1:f0:7b:f9:67:91:28:
+                    d9:9f:2c:f0:5e:12:92:52:92:97:27:7b:12:dd:c5:
+                    d5:7f:32:8c:9b:26:05:eb:47:e1:26:99:ea:6a:a9:
+                    25:93:64:31:e5:6c:f4:cf:02:27:29:b3:9f:17:94:
+                    0d:38:9c:54:f1:80:ef:b9:b0:4b:6a:12:eb:ca:53:
+                    91:2a:95:ee:16:bf:12:9f:8a:32:a7:8a:81:dd:4c:
+                    02:91
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                EF:56:67:1C:5E:24:60:78:3E:F2:35:40:2E:1A:58:65:4D:B3:4E:BE
+            X509v3 Authority Key Identifier: 
+                keyid:47:8C:F1:C9:1E:F8:EC:25:A8:31:F3:1C:CE:BC:C5:70:9F:11:87:63
+
+            Authority Information Access: 
+                CA Issuers - URI:http://url-for-aia/Intermediate.cer
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://url-for-crl/Intermediate.crl
+
+            X509v3 Key Usage: critical
+                Digital Signature, Key Encipherment
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication, TLS Web Client Authentication
+    Signature Algorithm: sha256WithRSAEncryption
+         7a:5d:ae:ba:54:c3:87:ef:ea:dc:b3:8f:03:b2:82:c0:0b:1c:
+         a2:ce:b4:a6:ab:2c:ec:04:28:3f:f9:25:3d:cc:34:f2:5c:e0:
+         a1:8a:65:56:d8:23:f2:c0:56:d4:39:15:eb:5a:0c:c4:db:8e:
+         8f:1b:1e:d7:d0:64:e8:fe:ec:35:3f:da:68:35:26:84:3d:91:
+         a0:f2:d6:b7:24:cd:1d:fb:65:39:20:15:55:74:f5:04:52:71:
+         be:6d:fb:40:04:1d:f8:4a:3c:87:2b:b9:10:f3:10:c1:67:f1:
+         ea:2c:f6:c3:08:54:fc:56:ba:2d:70:2f:d4:d6:03:a5:60:05:
+         0b:74:f8:9c:3e:7a:50:ac:5e:6e:4f:e2:25:78:16:00:bc:a2:
+         3a:28:48:01:94:09:9e:4a:51:35:1f:de:87:6c:9e:4b:1b:07:
+         02:48:1f:f3:c4:af:21:08:51:ac:a4:b7:0d:2f:d2:dc:3c:a5:
+         d5:e7:35:e4:dc:bb:db:80:2e:79:fd:72:3f:42:c6:d2:04:43:
+         8e:db:6f:bf:45:7d:23:31:cb:c4:0d:2a:bb:15:e2:8d:87:ef:
+         3b:32:bc:76:3c:53:64:7e:01:ab:81:16:8b:ee:bd:88:21:f8:
+         82:1f:37:b8:70:7e:df:4a:3b:38:96:ee:e7:0d:12:9c:65:90:
+         b3:53:10:d5
+-----BEGIN CERTIFICATE-----
+MIIDjTCCAnWgAwIBAgIBATANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxJbnRl
+cm1lZGlhdGUwHhcNMTUwMTAxMTIwMDAwWhcNMTYwMTAxMTIwMDAwWjARMQ8wDQYD
+VQQDDAZUYXJnZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBA1gB
+sS97+7Jx3EnQywZ2MGT3Yb/aVZNzKUkPywozvUELKANFNXKptEun7FJ3Ooy6y4dW
+KDs5jUd7cH9aj3aMfhPoYRcZHXLjbmkgvIP3WxGFbhq4+3v4/ivi0r0aCmVisISn
+Cqx16uZ0xB0s6ARidktNBLZSL6a6Zrv+RdZqIQUW5fMlrpT9F4SAL6xi2YPjF7AD
+HAECi0d/ZS75QM+tkjMHihREXsLtaEik0fB7+WeRKNmfLPBeEpJSkpcnexLdxdV/
+MoybJgXrR+EmmepqqSWTZDHlbPTPAicps58XlA04nFTxgO+5sEtqEuvKU5Eqle4W
+vxKfijKnioHdTAKRAgMBAAGjgekwgeYwHQYDVR0OBBYEFO9WZxxeJGB4PvI1QC4a
+WGVNs06+MB8GA1UdIwQYMBaAFEeM8cke+OwlqDHzHM68xXCfEYdjMD8GCCsGAQUF
+BwEBBDMwMTAvBggrBgEFBQcwAoYjaHR0cDovL3VybC1mb3ItYWlhL0ludGVybWVk
+aWF0ZS5jZXIwNAYDVR0fBC0wKzApoCegJYYjaHR0cDovL3VybC1mb3ItY3JsL0lu
+dGVybWVkaWF0ZS5jcmwwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUF
+BwMBBggrBgEFBQcDAjANBgkqhkiG9w0BAQsFAAOCAQEAel2uulTDh+/q3LOPA7KC
+wAscos60pqss7AQoP/klPcw08lzgoYplVtgj8sBW1DkV61oMxNuOjxse19Bk6P7s
+NT/aaDUmhD2RoPLWtyTNHftlOSAVVXT1BFJxvm37QAQd+Eo8hyu5EPMQwWfx6iz2
+wwhU/Fa6LXAv1NYDpWAFC3T4nD56UKxebk/iJXgWALyiOihIAZQJnkpRNR/eh2ye
+SxsHAkgf88SvIQhRrKS3DS/S3Dyl1ec15Ny724Auef1yP0LG0gRDjttvv0V9IzHL
+xA0quxXijYfvOzK8djxTZH4Bq4EWi+69iCH4gh83uHB+30o7OJbu5w0SnGWQs1MQ
+1Q==
+-----END CERTIFICATE-----
+
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 2 (0x2)
+    Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN=Root
+        Validity
+            Not Before: Jan  1 12:00:00 2015 GMT
+            Not After : Jan  1 12:00:00 2016 GMT
+        Subject: CN=Intermediate
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:ba:0f:08:80:56:6b:27:51:76:78:18:c5:92:b1:
+                    b4:d1:7a:4f:8f:57:6a:6a:96:70:e3:ca:4a:68:9d:
+                    0b:5d:2e:fd:34:1b:2a:d7:f2:a0:e0:3d:98:f8:2c:
+                    88:d1:7e:25:5d:80:80:30:f0:1c:65:a5:e4:60:ed:
+                    7a:31:df:97:20:c3:0c:4e:d0:2a:d8:93:54:d2:21:
+                    fe:9f:85:7d:fe:9d:45:fc:66:14:10:a5:6a:38:e7:
+                    e0:1e:71:fa:fe:9a:c0:79:73:98:87:80:17:a8:e3:
+                    c8:84:cb:9a:a8:db:d2:59:d5:26:40:cc:8b:29:03:
+                    8a:75:3d:05:01:ed:bf:05:57:27:94:e2:a3:7e:2e:
+                    06:95:8b:a2:99:8d:69:d3:3a:86:35:2b:23:19:cd:
+                    53:92:55:fe:7e:75:43:08:4c:05:51:db:1a:14:5d:
+                    6c:bb:4f:de:ef:7f:24:53:b1:e6:fc:90:a0:8a:39:
+                    22:f1:1d:1f:4a:3b:5b:c0:df:ca:a9:57:f2:c8:16:
+                    f5:e0:f4:fa:79:77:9b:93:0d:b8:5a:9d:9b:48:98:
+                    69:75:11:0f:2d:b9:8e:cd:34:4c:06:62:f8:a2:de:
+                    07:d8:7e:a0:5a:88:b0:d1:72:0b:49:67:42:5c:08:
+                    3b:bc:10:60:01:c2:15:ab:f8:31:8f:5d:bb:a2:e6:
+                    da:fb
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                47:8C:F1:C9:1E:F8:EC:25:A8:31:F3:1C:CE:BC:C5:70:9F:11:87:63
+            X509v3 Authority Key Identifier: 
+                keyid:BD:1A:91:15:D9:48:10:F5:7E:D3:B8:CE:06:D8:29:10:AE:43:CE:42
+
+            Authority Information Access: 
+                CA Issuers - URI:http://url-for-aia/Root.cer
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://url-for-crl/Root.crl
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Certificate Policies: critical
+                Policy: 1.2.3
+                    Unknown Qualifier: 1.2.3.4
+
+    Signature Algorithm: sha256WithRSAEncryption
+         18:0e:35:f6:08:db:43:5e:71:33:33:da:79:70:bc:f1:ed:97:
+         2c:49:55:fa:52:12:6a:a2:b6:9a:51:9c:56:5d:f3:6c:ec:91:
+         2e:b7:2b:09:59:a8:1e:02:dd:b1:0d:b8:0d:46:21:ff:41:5b:
+         41:76:94:f3:ea:ed:63:b9:f0:32:f6:2f:b5:1f:ea:f7:74:c3:
+         d8:0d:61:5e:46:04:06:00:41:88:a0:e5:39:4b:5d:eb:a5:9d:
+         31:33:b3:1b:8e:eb:0d:2d:43:17:a0:d1:45:5a:85:b9:a5:5b:
+         a6:a7:f2:0a:1f:43:4a:65:93:0b:6e:44:de:e1:99:a5:0e:4f:
+         9e:30:ac:29:71:15:69:9f:e2:e2:b7:d9:db:6f:96:3d:a5:4f:
+         06:0e:d1:3c:55:54:36:34:10:0e:19:8b:1d:19:0b:88:57:51:
+         ac:b6:77:45:0e:b4:8e:31:59:42:ee:0d:2f:26:80:6e:92:1d:
+         d7:22:fe:7d:99:08:f6:ae:f2:9b:9d:c5:29:57:b3:45:4b:a1:
+         bf:8f:e6:bd:d8:b7:91:13:46:c2:4e:fb:ac:8e:f3:b4:73:83:
+         5e:a7:af:4b:c4:76:01:2e:42:a2:bc:ce:a0:99:89:29:01:32:
+         2f:ee:c1:a2:14:5d:ef:3d:3c:6e:af:83:8b:3e:90:dd:de:90:
+         8d:38:57:d5
+-----BEGIN CERTIFICATE-----
+MIIDjjCCAnagAwIBAgIBAjANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARSb290
+MB4XDTE1MDEwMTEyMDAwMFoXDTE2MDEwMTEyMDAwMFowFzEVMBMGA1UEAwwMSW50
+ZXJtZWRpYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAug8IgFZr
+J1F2eBjFkrG00XpPj1dqapZw48pKaJ0LXS79NBsq1/Kg4D2Y+CyI0X4lXYCAMPAc
+ZaXkYO16Md+XIMMMTtAq2JNU0iH+n4V9/p1F/GYUEKVqOOfgHnH6/prAeXOYh4AX
+qOPIhMuaqNvSWdUmQMyLKQOKdT0FAe2/BVcnlOKjfi4GlYuimY1p0zqGNSsjGc1T
+klX+fnVDCEwFUdsaFF1su0/e738kU7Hm/JCgijki8R0fSjtbwN/KqVfyyBb14PT6
+eXebkw24Wp2bSJhpdREPLbmOzTRMBmL4ot4H2H6gWoiw0XILSWdCXAg7vBBgAcIV
+q/gxj127ouba+wIDAQABo4HsMIHpMB0GA1UdDgQWBBRHjPHJHvjsJagx8xzOvMVw
+nxGHYzAfBgNVHSMEGDAWgBS9GpEV2UgQ9X7TuM4G2CkQrkPOQjA3BggrBgEFBQcB
+AQQrMCkwJwYIKwYBBQUHMAKGG2h0dHA6Ly91cmwtZm9yLWFpYS9Sb290LmNlcjAs
+BgNVHR8EJTAjMCGgH6AdhhtodHRwOi8vdXJsLWZvci1jcmwvUm9vdC5jcmwwDgYD
+VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0gAQH/BBUwEzARBgIq
+AzALMAkGAyoDBAwCaGkwDQYJKoZIhvcNAQELBQADggEBABgONfYI20NecTMz2nlw
+vPHtlyxJVfpSEmqitppRnFZd82zskS63KwlZqB4C3bENuA1GIf9BW0F2lPPq7WO5
+8DL2L7Uf6vd0w9gNYV5GBAYAQYig5TlLXeulnTEzsxuO6w0tQxeg0UVahbmlW6an
+8gofQ0plkwtuRN7hmaUOT54wrClxFWmf4uK32dtvlj2lTwYO0TxVVDY0EA4Zix0Z
+C4hXUay2d0UOtI4xWULuDS8mgG6SHdci/n2ZCPau8pudxSlXs0VLob+P5r3Yt5ET
+RsJO+6yO87Rzg16nr0vEdgEuQqK8zqCZiSkBMi/uwaIUXe89PG6vg4s+kN3ekI04
+V9U=
+-----END CERTIFICATE-----
+
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1 (0x1)
+    Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN=Root
+        Validity
+            Not Before: Jan  1 12:00:00 2015 GMT
+            Not After : Jan  1 12:00:00 2016 GMT
+        Subject: CN=Root
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:ba:3d:c2:46:f3:d5:1b:65:5e:43:a3:bc:db:43:
+                    94:e9:9c:20:e1:ea:84:98:c6:65:51:6d:1c:1d:5f:
+                    8d:f9:81:47:1a:06:18:d9:7c:57:8f:6c:55:5c:36:
+                    63:c2:c6:db:be:47:61:5c:35:46:30:ec:e1:e5:0e:
+                    10:4f:9d:d4:62:58:56:83:00:3a:63:f0:cb:b2:50:
+                    e5:50:52:27:60:41:3e:db:07:61:92:db:d6:60:c2:
+                    66:f8:89:b6:aa:99:cb:5e:9d:74:db:cc:bc:3e:7d:
+                    0b:13:87:29:b8:fa:32:11:e9:fc:9a:e9:77:0d:7c:
+                    03:15:f7:7c:85:6c:f0:2c:2b:b0:32:5b:d9:6f:f8:
+                    f0:82:71:9e:f4:63:5c:6d:98:c9:ea:12:ad:d3:66:
+                    22:da:67:26:3c:ae:b3:23:0e:68:91:b7:28:65:81:
+                    b8:2c:04:34:92:bb:a0:00:39:51:06:53:14:c7:e9:
+                    ae:31:ef:5a:d7:21:28:44:9f:ca:53:cf:ac:4f:60:
+                    56:a9:f4:92:20:ee:c0:db:46:da:83:bd:28:b4:dd:
+                    d2:73:af:93:b5:31:84:55:e8:80:a0:6f:c5:f6:0c:
+                    54:50:dc:3d:b4:26:71:f9:fd:16:3f:62:b1:96:c9:
+                    de:45:b4:28:86:8d:8e:34:ce:aa:41:7c:66:e4:04:
+                    72:bb
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                BD:1A:91:15:D9:48:10:F5:7E:D3:B8:CE:06:D8:29:10:AE:43:CE:42
+            X509v3 Authority Key Identifier: 
+                keyid:BD:1A:91:15:D9:48:10:F5:7E:D3:B8:CE:06:D8:29:10:AE:43:CE:42
+
+            Authority Information Access: 
+                CA Issuers - URI:http://url-for-aia/Root.cer
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://url-for-crl/Root.crl
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+    Signature Algorithm: sha256WithRSAEncryption
+         40:13:b4:c9:ca:a2:93:7e:e0:bd:1a:07:4b:d7:77:5d:52:b3:
+         f8:4d:a8:29:7c:21:5a:04:96:15:97:cf:77:69:b0:d8:71:e3:
+         5a:bb:9b:fa:6d:79:10:a2:bb:8d:ce:9f:ac:6c:91:51:7e:77:
+         8a:0f:2f:39:1b:39:1e:78:52:d9:80:f6:27:a0:c5:5a:41:c7:
+         9d:28:f2:3e:6f:cb:57:55:a8:df:94:8a:e3:ce:0c:fb:9d:74:
+         22:ce:51:0b:bf:5a:3a:17:dc:7d:59:ce:f9:1c:e0:92:7f:53:
+         7d:92:36:a2:b8:e6:16:fc:c3:e2:52:1c:fe:2d:d1:29:9e:e7:
+         f4:89:eb:14:f0:db:5a:39:14:4e:1e:26:44:0b:90:91:4c:88:
+         00:b9:4b:b3:53:9a:f3:7d:96:5a:2b:a3:e9:40:7c:e7:de:f7:
+         56:93:79:e5:b3:a3:ea:17:58:72:ad:05:86:c6:e7:84:17:94:
+         10:ee:e7:61:18:6c:c4:46:d9:f3:c0:48:1f:18:15:30:3a:93:
+         dd:b0:c4:6b:92:f5:ac:c6:d1:c0:bf:9b:02:0f:f9:20:3d:f7:
+         ee:af:f0:44:4f:1b:cb:7b:6f:ae:bb:97:65:b2:d9:a8:be:50:
+         a7:3e:59:b6:a2:da:41:48:50:1e:38:7d:97:37:a8:41:21:b0:
+         5b:18:03:f1
+-----BEGIN CERTIFICATE-----
+MIIDZTCCAk2gAwIBAgIBATANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARSb290
+MB4XDTE1MDEwMTEyMDAwMFoXDTE2MDEwMTEyMDAwMFowDzENMAsGA1UEAwwEUm9v
+dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALo9wkbz1RtlXkOjvNtD
+lOmcIOHqhJjGZVFtHB1fjfmBRxoGGNl8V49sVVw2Y8LG275HYVw1RjDs4eUOEE+d
+1GJYVoMAOmPwy7JQ5VBSJ2BBPtsHYZLb1mDCZviJtqqZy16ddNvMvD59CxOHKbj6
+MhHp/Jrpdw18AxX3fIVs8CwrsDJb2W/48IJxnvRjXG2YyeoSrdNmItpnJjyusyMO
+aJG3KGWBuCwENJK7oAA5UQZTFMfprjHvWtchKESfylPPrE9gVqn0kiDuwNtG2oO9
+KLTd0nOvk7UxhFXogKBvxfYMVFDcPbQmcfn9Fj9isZbJ3kW0KIaNjjTOqkF8ZuQE
+crsCAwEAAaOByzCByDAdBgNVHQ4EFgQUvRqRFdlIEPV+07jOBtgpEK5DzkIwHwYD
+VR0jBBgwFoAUvRqRFdlIEPV+07jOBtgpEK5DzkIwNwYIKwYBBQUHAQEEKzApMCcG
+CCsGAQUFBzAChhtodHRwOi8vdXJsLWZvci1haWEvUm9vdC5jZXIwLAYDVR0fBCUw
+IzAhoB+gHYYbaHR0cDovL3VybC1mb3ItY3JsL1Jvb3QuY3JsMA4GA1UdDwEB/wQE
+AwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBAE7TJyqKT
+fuC9GgdL13ddUrP4TagpfCFaBJYVl893abDYceNau5v6bXkQoruNzp+sbJFRfneK
+Dy85GzkeeFLZgPYnoMVaQcedKPI+b8tXVajflIrjzgz7nXQizlELv1o6F9x9Wc75
+HOCSf1N9kjaiuOYW/MPiUhz+LdEpnuf0iesU8NtaORROHiZEC5CRTIgAuUuzU5rz
+fZZaK6PpQHzn3vdWk3nls6PqF1hyrQWGxueEF5QQ7udhGGzERtnzwEgfGBUwOpPd
+sMRrkvWsxtHAv5sCD/kgPffur/BETxvLe2+uu5dlstmovlCnPlm2otpBSFAeOH2X
+N6hBIbBbGAPx
+-----END CERTIFICATE-----
diff --git a/net/data/verify_certificate_chain_unittest/unknown-critical-policy-qualifier/generate-chains.py b/net/data/verify_certificate_chain_unittest/unknown-critical-policy-qualifier/generate-chains.py
new file mode 100755
index 0000000..caad547
--- /dev/null
+++ b/net/data/verify_certificate_chain_unittest/unknown-critical-policy-qualifier/generate-chains.py
@@ -0,0 +1,28 @@
+#!/usr/bin/python
+# Copyright (c) 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""The intermediate has a policies extension marked as critical, which contains
+an unknown qualifer (1.2.3.4)."""
+
+import sys
+sys.path += ['..']
+
+import common
+
+# Self-signed root certificate (used as trust anchor).
+root = common.create_self_signed_root_certificate('Root')
+
+# Intermediate that has a critical policies extension containing an unknown
+# policy qualifer.
+intermediate = common.create_intermediate_certificate('Intermediate', root)
+intermediate.get_extensions().add_property(
+    '2.5.29.32', ('critical,DER:30:13:30:11:06:02:2a:03:30:0b:30:09:06:03:'
+                  '2a:03:04:0c:02:68:69'))
+
+# Target certificate.
+target = common.create_end_entity_certificate('Target', intermediate)
+
+chain = [target, intermediate, root]
+common.write_chain(__doc__, chain, 'chain.pem')
diff --git a/net/data/verify_certificate_chain_unittest/unknown-critical-policy-qualifier/keys/Intermediate.key b/net/data/verify_certificate_chain_unittest/unknown-critical-policy-qualifier/keys/Intermediate.key
new file mode 100644
index 0000000..1b54756
--- /dev/null
+++ b/net/data/verify_certificate_chain_unittest/unknown-critical-policy-qualifier/keys/Intermediate.key
@@ -0,0 +1,28 @@
+openssl genrsa 2048
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAug8IgFZrJ1F2eBjFkrG00XpPj1dqapZw48pKaJ0LXS79NBsq
+1/Kg4D2Y+CyI0X4lXYCAMPAcZaXkYO16Md+XIMMMTtAq2JNU0iH+n4V9/p1F/GYU
+EKVqOOfgHnH6/prAeXOYh4AXqOPIhMuaqNvSWdUmQMyLKQOKdT0FAe2/BVcnlOKj
+fi4GlYuimY1p0zqGNSsjGc1TklX+fnVDCEwFUdsaFF1su0/e738kU7Hm/JCgijki
+8R0fSjtbwN/KqVfyyBb14PT6eXebkw24Wp2bSJhpdREPLbmOzTRMBmL4ot4H2H6g
+Woiw0XILSWdCXAg7vBBgAcIVq/gxj127ouba+wIDAQABAoIBACtWTxOdjW3mEqMc
+mgB2NfjI9Xkc0mOrUPYt6zD8y2XUCRQ2877C++ffUR8VOHUbXdaulK1RcDvY7R4K
+knjFbK0r4VUGNcSmZI5hbq53aIaC2YdVu3y5GsnQcKqGwADHyOdBHpkJI/mpvM35
+oreMJC3acQOM4SkXcwulv+HYH7M9WQKWENG+4u8FaUbBrpyMG6K7HtrWM0bhRoTA
+HDeAgZxMNly8Lq293ouoKX8ahF3abRX8t+FcQJfblXWYsEC4HTfcBRB+a0QwNGEz
+GWkGEWmUI6MewhFKi/nK31qA/Txwe4nMjWHVpkDbg6mh/hk5NoWnBZ4/YZjZdz1s
+Hh/erHECgYEA3gLuvHcgYpjGFTkUcRNwhsFDDJRil4UvmNDAKWtxh6xF831tg1X1
+RXZjdBma2AGMlvTp0QudISUo+tX3Jx49NAWyrxzhltTAGY/czQNms70EnL265+Ke
+CC8pvYUHUxU9JoKvCJ/yrcNg71yzXwYDTvsVDvk7O5W7JU6N0d3lU3kCgYEA1osK
+xT0GfCjFeu012bQN8ss3owp9MmYOrVrRTvL1EerHvGZ+wUQBU25c/V3CrcXsl+xi
+jsnX52b+Q/xUqNG36xV8KKM/e4w9raAe0FaaS5Ymc/8XJNxhDSkWSyHg4AHz3EhX
+v5ozqzyXf44XBbYxM/JHDA1bTUMU2QWBjX/DsRMCgYEAknVYzRfBU4n5UtLe+2GZ
+oCwqcUgKam7AYY2E1048DQbG9sRN1JCGR5a8MUEa91i+SG+1N+TzeYQsLp2seC/o
+rEL+B6uD3p7nQ3W2ccDGpdgSDNzVSlgjbL7ASHhTxqmCN7+KfKQX0i3L6tw6sHsk
+/5t/3urn+nzigs/5mvNJb/ECgYB8HNmxOtl6kDeWIo4pkDEViRx5lILS2hmZjdVS
+P9kocVL1GbIwaf/+XPUsmcYas0popRZrFjdxpbJocBwlwZUNSVOXSsFZ3sp7nArX
+aZI/MfCy4Bpqd5cpfuBnvIST8m05vRwOOs2zPQ1DfFazNjAGph0VytBLxQfVeIr2
+6beywwKBgQC6DWEYDLoGDeW+6tunPQEniYG1/+TsLTS+y+uCayr+L42AfhyVLC0r
+6So3ZezfMgjO/yRoOPmLmN05ON9OsxSMwnwz5ETMCx2xKCk5xEJ8foThTDa6TBkS
+YXiL+PjJcLNazGK+LZXGd8QeKCQuuQM4XaPjlhfy1EpVdskrig8vAg==
+-----END RSA PRIVATE KEY-----
diff --git a/net/data/verify_certificate_chain_unittest/unknown-critical-policy-qualifier/keys/Root.key b/net/data/verify_certificate_chain_unittest/unknown-critical-policy-qualifier/keys/Root.key
new file mode 100644
index 0000000..85565dc
--- /dev/null
+++ b/net/data/verify_certificate_chain_unittest/unknown-critical-policy-qualifier/keys/Root.key
@@ -0,0 +1,28 @@
+openssl genrsa 2048
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAuj3CRvPVG2VeQ6O820OU6Zwg4eqEmMZlUW0cHV+N+YFHGgYY
+2XxXj2xVXDZjwsbbvkdhXDVGMOzh5Q4QT53UYlhWgwA6Y/DLslDlUFInYEE+2wdh
+ktvWYMJm+Im2qpnLXp1028y8Pn0LE4cpuPoyEen8mul3DXwDFfd8hWzwLCuwMlvZ
+b/jwgnGe9GNcbZjJ6hKt02Yi2mcmPK6zIw5okbcoZYG4LAQ0krugADlRBlMUx+mu
+Me9a1yEoRJ/KU8+sT2BWqfSSIO7A20bag70otN3Sc6+TtTGEVeiAoG/F9gxUUNw9
+tCZx+f0WP2KxlsneRbQoho2ONM6qQXxm5ARyuwIDAQABAoIBAQCsXK3plo6a/TTB
+as9FKgWuMoWoL7Ap6Csqsc08TgwKZx/TsOLcnRw4K23QmrG3OO1z64HqoY9lhkYG
+A/8KgCpoHhNqyBJCHj+WtxGTRYUVqyyt7uXZ0cVU5XYA/QrmVtGFrjnRlmh9eKKA
+ny34+OzP8XqMMBezjhsZAGPo5ApfBTeF5FLd5zLOrC1wEWD8WOBMj0+cOuzHoTQP
+hTRdd0jmfvgTtJlMeLDAXMxBRA9nBiUqNEczxA+sLhlp23f1/Ox6usdbTnfSHfiq
+wfn9lEkMwKVEOJLcN22W3onD0Nzynqjwi7JJKBRhx06u8EpZcDXtTzflVGgGlIsu
+7gR9uuJ5AoGBAOC5u9dK5FvBI0tnKsJJqNwZId2Yc1DjLYrE9mhbjCi2n8qSFSPv
+QRsgubYmejqTWpEGmIVEFoqyp2qHwk2KLfheMn0BT2i2d/bukO2THeW0pzGUw1QO
+HCGbHvXYLJEtZMcdbHbJ+OB7xiEXPBoMvRviAd2722691CFvGy298LkvAoGBANQo
+83DKVIbAUpWqBRm79t14vvUYl/6FiipcloFxg0ts/48lgHipu6UHZvNxq5obI6ZV
+iNnIIR3WML25q6vCGjPqh8XFNBACQl9W9NYrxBw6okHIUk46TSnXZABWKkIpVycY
+qoNvMEaYxMOJcWP5yYAdAuHGAVpcY84BipfTNKQ1AoGAJPboKvWqsl0GjTSfF+49
+1FZd97FH5po3t4fGcEv+tgO0LQHycEK+ltO/OHMDz/bFAu3u0JzuiEnyt9c04sRD
+44K7E0zP3w2Elh2PwURdupcImWM67eyUPALkCNRSIF8zIH16hd+bDHHPmUnVHQPT
+Z265l9t6sLCQ4B8CfXeszQkCgYEAwD6PQQAGEciJzNYwIv6AlnTITB1J9TcaWIJn
+Wsn6F8mf1R09SZk70DFh52xz+4NmDV3qULUY1Ql2RyTMWynaRnrZJqsoHu19KRkJ
+aFxmKtIynvsfIYWQ0D49flSVnDgLogCm2cxR6VaazThoyBfar70aFG0wYqnfQheB
+d0vJopUCgYEAtxIwbKzpFwDi+559shpDbGGSFRYYM7VuLYHGwEvm+sTlYAeNC5BU
+cszZ9psyYsa3euS29aTN2SwdDoV1dLmgadkROXtI+PoApE0G0LGmo7zv+rjrN/8+
+2a6K1uUw6d6zeOHGQjPknxWpZxyp6RVoWciU2o6oIWqKARwo+U0tQo4=
+-----END RSA PRIVATE KEY-----
diff --git a/net/data/verify_certificate_chain_unittest/unknown-critical-policy-qualifier/keys/Target.key b/net/data/verify_certificate_chain_unittest/unknown-critical-policy-qualifier/keys/Target.key
new file mode 100644
index 0000000..9dc5220
--- /dev/null
+++ b/net/data/verify_certificate_chain_unittest/unknown-critical-policy-qualifier/keys/Target.key
@@ -0,0 +1,28 @@
+openssl genrsa 2048
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAwQNYAbEve/uycdxJ0MsGdjBk92G/2lWTcylJD8sKM71BCygD
+RTVyqbRLp+xSdzqMusuHVig7OY1He3B/Wo92jH4T6GEXGR1y425pILyD91sRhW4a
+uPt7+P4r4tK9GgplYrCEpwqsdermdMQdLOgEYnZLTQS2Ui+muma7/kXWaiEFFuXz
+Ja6U/ReEgC+sYtmD4xewAxwBAotHf2Uu+UDPrZIzB4oURF7C7WhIpNHwe/lnkSjZ
+nyzwXhKSUpKXJ3sS3cXVfzKMmyYF60fhJpnqaqklk2Qx5Wz0zwInKbOfF5QNOJxU
+8YDvubBLahLrylORKpXuFr8Sn4oyp4qB3UwCkQIDAQABAoIBABPYFipGX01t+VC/
+h09SMF3iZ98PS7xU5A4JBpv7Ehdzs8ZD3OI2to1AHi8rmubrPCDQYB+5jy1Bm0HT
+tSRIb7XOGJnhsZBBeNhtN53IBAFgeZKwWUr/gCV/bF+rrwmIXBgjceACtuyAtCT5
+LM67JAhDhUZr35rfj1LG0Tk5UOwkPwN9/av8c+K9Cz3SGobmMYgzo+n7QqBWU3KH
+1zrLWmBx7qkH8hXQJVbgl1UcSh7hZh15+R4Z9ksDL9MW8rNjYHfEivLbb1uH111v
+XCFciwJdE9nHljgGOe9iP4sFQXa735HX0pAMLR9K3eD9UWAyUr/w5wYzNBF5wYAO
+Vk12zwkCgYEA9b001ze5qbONg1uWZXKPmgma1zxirTpxra4I+27t/tbhqn8CCach
+G6KePodCl/XWu3IxTyjLKrt2ERASKsq5MyUMAsM/lbCeK0oL6qh/B80cUVPJu3SQ
+7eFWVTNp7NeoMExVuXZrkZFAfm+rOkyKELq4N0JI9NtsatFzeLk0JmcCgYEAyRKH
+pukWvPdJMDjWDa6vQ3/HT0EYIskNOSE/3gfoY7qjf2DX3H/TOl0uQiER2Dar3e9l
+UcX5p6jJOru/WTNq39R/qnvgpLzRJbt/iCHUz6kFVhPbNB55BIL6geZM3ozr2fhh
+fcZbw2SK9aL5f5YdR4NBhjt9tGwhUJ6zoZQOREcCgYEAzZaO16OSW+yQ7x23tIXk
+qB1zArFNaNnlc0WP1DMZP8QspgR+Au9lHoatV2i4CQQus0G1CSH633RYMaw9Vrml
+uak75C/PSX+qxFK9p4NyS2eJUm0D8CV0ft+v57YM344I8udaLMBq/C0GRQFNwod4
+VsiwB+MXwGq2yKMUtSJVA+8CgYEAl9bIXYDTTOuodyPUcQyjCWPqzKypX0tYEvZK
+1o4+LWF5qe0l1Tsp9X78Oa/jW9UdQEFRz8ii2LsbCqFQxDmR0WkU51cYsw5rGgVX
+LyPtIsvOXcws0lTYqlGGfqW9Eqp0SGvKo0IxdYHtdcUWiASCI8i4qHcwXBjzp3/Q
+womdpG0CgYB/ipbn+ikc/9b0+A9RdooSrcpaNwncv1OKKm//2hrE+DX8pcadLEgI
+l6M64Pg68EM2VXMBKgvLgT3ELmEMbNs5cwLln5ICHqNHfjPuSufyQq6h0iAy+3Q/
+wX0WTuMzMFuSr/5/5Xh1K/PPiosF72X2ZxpOK2//r2/1CvAeUwLy/Q==
+-----END RSA PRIVATE KEY-----
diff --git a/net/data/verify_certificate_chain_unittest/unknown-critical-policy-qualifier/main.test b/net/data/verify_certificate_chain_unittest/unknown-critical-policy-qualifier/main.test
new file mode 100644
index 0000000..2aab39c
--- /dev/null
+++ b/net/data/verify_certificate_chain_unittest/unknown-critical-policy-qualifier/main.test
@@ -0,0 +1,10 @@
+chain: chain.pem
+last_cert_trust: TRUSTED_ANCHOR
+utc_time: 150302120000Z
+key_purpose: SERVER_AUTH
+expected_errors:
+----- Certificate i=1 (CN=Intermediate) -----
+ERROR: Unconsumed critical extension
+  oid: 551D20
+  value: 3013301106022A03300B300906032A03040C026869
+
diff --git a/net/data/verify_certificate_chain_unittest/unknown-non-critical-policy-qualifier/chain.pem b/net/data/verify_certificate_chain_unittest/unknown-non-critical-policy-qualifier/chain.pem
new file mode 100644
index 0000000..f890d68
--- /dev/null
+++ b/net/data/verify_certificate_chain_unittest/unknown-non-critical-policy-qualifier/chain.pem
@@ -0,0 +1,276 @@
+[Created by: generate-chains.py]
+
+The intermediate has a policies extension (not marked as critical),
+which contains an unknown qualifer (1.2.3.4).
+
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1 (0x1)
+    Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN=Intermediate
+        Validity
+            Not Before: Jan  1 12:00:00 2015 GMT
+            Not After : Jan  1 12:00:00 2016 GMT
+        Subject: CN=Target
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:be:d9:c3:94:6a:c2:d7:1b:b4:33:1a:29:0d:ca:
+                    48:e2:f1:94:93:27:36:71:c1:a2:dc:67:0e:5d:67:
+                    b0:a9:08:9c:67:08:ba:d9:74:5f:01:62:5d:7f:2a:
+                    bb:32:ed:0c:af:c8:5a:b5:02:24:45:6f:90:4c:83:
+                    ab:0e:30:19:c2:df:bc:d5:25:99:b0:f3:5e:e1:27:
+                    5b:06:2f:ca:3e:d6:49:fb:87:8d:d3:fd:b9:b9:27:
+                    80:be:b5:88:72:3b:1b:20:3f:04:69:04:89:66:ee:
+                    20:f7:c2:90:c1:27:aa:29:fa:88:ff:2f:10:3b:81:
+                    cf:d0:b9:e9:a7:84:dc:f1:a7:d0:49:e0:6e:17:b2:
+                    ba:09:ed:be:9c:a3:f2:66:37:dd:20:98:43:31:bd:
+                    02:d1:55:63:88:f6:55:13:20:b7:b9:0b:c9:c9:fb:
+                    a3:5b:0f:90:56:e8:8a:dc:a5:7a:92:bc:46:5d:82:
+                    a4:e1:42:2c:7c:76:65:63:87:f4:e0:5a:cf:15:22:
+                    13:49:1d:aa:0d:ea:25:08:7c:63:19:39:2f:1d:15:
+                    2e:7c:9a:e7:d5:03:21:76:6c:22:1a:be:12:8b:72:
+                    c5:cb:0f:41:ef:0f:d3:be:78:1d:12:e0:c2:29:eb:
+                    d7:36:28:54:ad:8d:ce:c8:79:2f:4f:13:c1:2b:3b:
+                    e4:ff
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                FE:3D:1B:76:A0:3D:EE:69:00:5B:D4:61:90:68:18:E3:29:EC:66:A3
+            X509v3 Authority Key Identifier: 
+                keyid:49:F0:C4:09:BE:16:68:CF:0A:C1:E0:EF:8F:A6:34:1F:94:63:6F:E6
+
+            Authority Information Access: 
+                CA Issuers - URI:http://url-for-aia/Intermediate.cer
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://url-for-crl/Intermediate.crl
+
+            X509v3 Key Usage: critical
+                Digital Signature, Key Encipherment
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication, TLS Web Client Authentication
+    Signature Algorithm: sha256WithRSAEncryption
+         22:98:2c:68:6e:42:33:22:00:d9:a3:a4:ec:bc:b6:19:c9:a5:
+         07:bd:ec:da:e0:82:4c:71:d6:59:b0:ee:8e:04:78:08:e5:1e:
+         d2:02:30:96:df:2b:ed:a7:c0:89:4d:88:23:84:67:4b:32:d8:
+         cd:61:5c:50:45:3a:8f:de:1e:86:a3:b9:01:7b:02:71:8e:35:
+         02:54:3f:89:de:66:17:2d:06:d2:af:a5:17:40:b7:66:4e:32:
+         35:9e:c0:c1:eb:57:13:af:f1:0e:7a:8a:d7:9b:05:a6:24:55:
+         ae:c1:c1:60:45:c8:70:4f:59:a1:c6:aa:51:03:5a:ca:26:30:
+         33:36:2b:09:45:10:cf:3f:3c:f1:a8:da:34:a8:59:59:94:4b:
+         5b:c1:31:f5:21:1c:cc:02:40:a2:78:bc:03:29:18:df:d4:9d:
+         df:50:38:c6:b5:f2:7c:b6:bc:30:f5:f5:b4:fd:65:16:5c:c3:
+         cf:19:c0:13:72:77:e4:ea:9e:37:2f:78:27:0e:27:10:56:14:
+         4b:c7:88:2c:e9:bb:63:ef:43:0b:25:6e:bb:83:e6:87:94:67:
+         a5:da:94:ff:81:3a:e2:86:c4:1d:a9:50:1f:69:be:ab:63:03:
+         f7:b6:a9:b6:b5:59:b3:4f:11:64:98:40:de:c0:39:c8:ab:b5:
+         3c:69:08:8b
+-----BEGIN CERTIFICATE-----
+MIIDjTCCAnWgAwIBAgIBATANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxJbnRl
+cm1lZGlhdGUwHhcNMTUwMTAxMTIwMDAwWhcNMTYwMTAxMTIwMDAwWjARMQ8wDQYD
+VQQDDAZUYXJnZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+2cOU
+asLXG7QzGikNykji8ZSTJzZxwaLcZw5dZ7CpCJxnCLrZdF8BYl1/Krsy7QyvyFq1
+AiRFb5BMg6sOMBnC37zVJZmw817hJ1sGL8o+1kn7h43T/bm5J4C+tYhyOxsgPwRp
+BIlm7iD3wpDBJ6op+oj/LxA7gc/QuemnhNzxp9BJ4G4XsroJ7b6co/JmN90gmEMx
+vQLRVWOI9lUTILe5C8nJ+6NbD5BW6IrcpXqSvEZdgqThQix8dmVjh/TgWs8VIhNJ
+HaoN6iUIfGMZOS8dFS58mufVAyF2bCIavhKLcsXLD0HvD9O+eB0S4MIp69c2KFSt
+jc7IeS9PE8ErO+T/AgMBAAGjgekwgeYwHQYDVR0OBBYEFP49G3agPe5pAFvUYZBo
+GOMp7GajMB8GA1UdIwQYMBaAFEnwxAm+FmjPCsHg74+mNB+UY2/mMD8GCCsGAQUF
+BwEBBDMwMTAvBggrBgEFBQcwAoYjaHR0cDovL3VybC1mb3ItYWlhL0ludGVybWVk
+aWF0ZS5jZXIwNAYDVR0fBC0wKzApoCegJYYjaHR0cDovL3VybC1mb3ItY3JsL0lu
+dGVybWVkaWF0ZS5jcmwwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUF
+BwMBBggrBgEFBQcDAjANBgkqhkiG9w0BAQsFAAOCAQEAIpgsaG5CMyIA2aOk7Ly2
+GcmlB73s2uCCTHHWWbDujgR4COUe0gIwlt8r7afAiU2II4RnSzLYzWFcUEU6j94e
+hqO5AXsCcY41AlQ/id5mFy0G0q+lF0C3Zk4yNZ7AwetXE6/xDnqK15sFpiRVrsHB
+YEXIcE9ZocaqUQNayiYwMzYrCUUQzz888ajaNKhZWZRLW8Ex9SEczAJAoni8AykY
+39Sd31A4xrXyfLa8MPX1tP1lFlzDzxnAE3J35OqeNy94Jw4nEFYUS8eILOm7Y+9D
+CyVuu4Pmh5RnpdqU/4E64obEHalQH2m+q2MD97aptrVZs08RZJhA3sA5yKu1PGkI
+iw==
+-----END CERTIFICATE-----
+
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 2 (0x2)
+    Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN=Root
+        Validity
+            Not Before: Jan  1 12:00:00 2015 GMT
+            Not After : Jan  1 12:00:00 2016 GMT
+        Subject: CN=Intermediate
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:d9:74:6c:03:22:ab:05:1b:af:d1:34:43:ac:2a:
+                    a3:bd:5d:dc:13:39:5f:df:ff:f4:bd:3c:bd:56:1e:
+                    b5:e9:b2:19:1d:49:ff:9c:5a:31:9c:20:74:87:27:
+                    81:22:50:a3:c2:90:da:48:da:c2:cd:4a:4d:dd:ec:
+                    75:d7:61:5b:32:57:1e:1d:63:82:54:69:49:f1:ff:
+                    3e:a5:67:46:b2:77:73:61:ce:30:9c:d5:f7:36:1f:
+                    83:0e:12:f8:37:48:a9:36:e6:38:61:13:5a:1d:a7:
+                    70:17:d2:0d:81:87:f0:cf:02:3c:13:56:fc:e9:79:
+                    96:c0:6d:8a:5d:a7:ad:e7:c5:3f:09:28:aa:e9:a8:
+                    6b:23:a3:78:fe:34:11:ba:d0:12:59:cf:b3:8a:68:
+                    df:96:2f:44:b0:b9:72:54:cf:ba:1b:2c:8c:56:a4:
+                    9d:db:b8:55:72:42:04:13:77:cc:75:04:3d:e9:b1:
+                    fa:a4:19:1b:3d:6f:0a:c2:7a:48:37:8b:35:c6:e1:
+                    cc:c6:50:b5:45:c0:f2:30:ca:ff:df:75:af:4b:c3:
+                    c7:63:11:da:fb:54:bf:53:57:a0:ce:75:18:53:8e:
+                    c7:49:c3:4a:79:88:a4:1d:34:a4:e0:d2:f4:63:ca:
+                    5a:02:89:c3:94:a3:38:32:f6:3b:e1:06:e4:02:e4:
+                    d0:25
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                49:F0:C4:09:BE:16:68:CF:0A:C1:E0:EF:8F:A6:34:1F:94:63:6F:E6
+            X509v3 Authority Key Identifier: 
+                keyid:27:0D:D0:55:88:5D:DE:1C:37:96:A0:62:14:C2:19:3C:C6:A4:1F:D1
+
+            Authority Information Access: 
+                CA Issuers - URI:http://url-for-aia/Root.cer
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://url-for-crl/Root.crl
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Certificate Policies: 
+                Policy: 1.2.3
+                    Unknown Qualifier: 1.2.3.4
+
+    Signature Algorithm: sha256WithRSAEncryption
+         6e:62:b6:3a:f3:09:f3:b0:03:99:44:33:cb:8d:b5:aa:7c:8d:
+         e8:5e:3e:9a:fe:75:be:61:c9:1f:97:72:3b:b9:e8:76:25:37:
+         ff:ec:cf:66:a5:2f:3a:fb:1a:a3:97:c8:b9:f5:f9:b9:f4:28:
+         7f:f4:57:49:5f:08:2c:26:bd:2e:50:db:13:2b:a6:0b:61:58:
+         32:64:30:b7:2a:92:0f:b2:3d:fa:9a:63:e8:9c:37:03:5f:29:
+         c0:f4:78:47:45:ab:b3:84:43:34:84:21:24:51:64:c1:df:94:
+         95:55:18:2e:a1:a9:41:bd:19:42:73:46:7d:19:59:67:1a:5e:
+         19:1e:56:ad:4a:e5:1d:bb:4e:83:a1:ef:08:9b:ca:0c:5a:8b:
+         c2:a1:15:67:75:d4:80:17:85:d0:c8:27:61:8e:29:66:f5:2d:
+         b8:08:d4:1b:c9:e9:e9:1b:d4:9c:b6:7c:17:4d:c9:58:4f:01:
+         4c:1e:23:c9:f5:75:78:e3:54:81:8b:7e:44:a7:f4:61:cc:38:
+         eb:3f:55:0c:4a:03:ec:02:11:4b:83:c2:e9:77:48:9e:c3:85:
+         d8:99:9d:38:69:26:cb:16:2f:0c:31:05:29:ab:58:2f:d4:ef:
+         20:7e:a9:56:fa:cb:76:bb:f5:15:05:1a:33:fa:88:46:21:2c:
+         23:f3:dd:f8
+-----BEGIN CERTIFICATE-----
+MIIDizCCAnOgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARSb290
+MB4XDTE1MDEwMTEyMDAwMFoXDTE2MDEwMTEyMDAwMFowFzEVMBMGA1UEAwwMSW50
+ZXJtZWRpYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2XRsAyKr
+BRuv0TRDrCqjvV3cEzlf3//0vTy9Vh616bIZHUn/nFoxnCB0hyeBIlCjwpDaSNrC
+zUpN3ex112FbMlceHWOCVGlJ8f8+pWdGsndzYc4wnNX3Nh+DDhL4N0ipNuY4YRNa
+HadwF9INgYfwzwI8E1b86XmWwG2KXaet58U/CSiq6ahrI6N4/jQRutASWc+zimjf
+li9EsLlyVM+6GyyMVqSd27hVckIEE3fMdQQ96bH6pBkbPW8KwnpIN4s1xuHMxlC1
+RcDyMMr/33WvS8PHYxHa+1S/U1egznUYU47HScNKeYikHTSk4NL0Y8paAonDlKM4
+MvY74QbkAuTQJQIDAQABo4HpMIHmMB0GA1UdDgQWBBRJ8MQJvhZozwrB4O+PpjQf
+lGNv5jAfBgNVHSMEGDAWgBQnDdBViF3eHDeWoGIUwhk8xqQf0TA3BggrBgEFBQcB
+AQQrMCkwJwYIKwYBBQUHMAKGG2h0dHA6Ly91cmwtZm9yLWFpYS9Sb290LmNlcjAs
+BgNVHR8EJTAjMCGgH6AdhhtodHRwOi8vdXJsLWZvci1jcmwvUm9vdC5jcmwwDgYD
+VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHAYDVR0gBBUwEzARBgIqAzAL
+MAkGAyoDBAwCaGkwDQYJKoZIhvcNAQELBQADggEBAG5itjrzCfOwA5lEM8uNtap8
+jehePpr+db5hyR+Xcju56HYlN//sz2alLzr7GqOXyLn1+bn0KH/0V0lfCCwmvS5Q
+2xMrpgthWDJkMLcqkg+yPfqaY+icNwNfKcD0eEdFq7OEQzSEISRRZMHflJVVGC6h
+qUG9GUJzRn0ZWWcaXhkeVq1K5R27ToOh7wibygxai8KhFWd11IAXhdDIJ2GOKWb1
+LbgI1BvJ6ekb1Jy2fBdNyVhPAUweI8n1dXjjVIGLfkSn9GHMOOs/VQxKA+wCEUuD
+wul3SJ7DhdiZnThpJssWLwwxBSmrWC/U7yB+qVb6y3a79RUFGjP6iEYhLCPz3fg=
+-----END CERTIFICATE-----
+
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1 (0x1)
+    Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN=Root
+        Validity
+            Not Before: Jan  1 12:00:00 2015 GMT
+            Not After : Jan  1 12:00:00 2016 GMT
+        Subject: CN=Root
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:a0:f9:c1:fa:93:42:7b:bf:e5:1e:21:e2:f5:cd:
+                    db:f7:61:04:6e:ea:06:4c:fc:d5:2e:9f:5e:6e:97:
+                    b2:d4:c3:f1:4c:18:01:5e:3e:85:e2:c0:73:ce:56:
+                    fb:cc:4c:4e:f0:37:b5:e0:c6:31:5c:c0:06:5a:90:
+                    24:d8:5d:88:ab:e3:53:2b:12:90:0b:16:c6:db:19:
+                    74:e7:29:63:53:d9:5b:f3:e7:80:8c:5e:86:ff:e8:
+                    e3:72:6b:09:6c:64:6b:92:34:f2:9c:bd:f4:b7:c1:
+                    31:6f:74:00:31:3a:45:70:9f:5d:a5:d3:9c:91:7f:
+                    fb:87:95:ef:07:f3:8d:8e:c9:a5:cb:ed:cc:2d:23:
+                    bf:e4:98:93:88:8d:be:bc:50:02:2c:3a:0d:52:53:
+                    7e:9a:20:04:da:52:db:a4:e5:72:bc:d6:40:40:7f:
+                    51:86:29:d7:f5:f7:db:85:b3:a0:7d:7a:c5:04:3e:
+                    e9:73:ca:65:3c:13:91:46:a1:b4:fb:6b:8b:a0:5e:
+                    7c:c9:9d:3c:5e:c5:f6:2a:99:df:2e:13:1e:7d:d8:
+                    db:30:02:52:d7:94:16:93:b8:20:5d:77:4d:26:6e:
+                    9c:c8:5e:0a:56:ad:ba:d9:26:c0:80:dd:66:aa:09:
+                    09:18:41:fa:f2:5c:7f:ae:10:45:25:ba:cc:0d:5d:
+                    d8:3b
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                27:0D:D0:55:88:5D:DE:1C:37:96:A0:62:14:C2:19:3C:C6:A4:1F:D1
+            X509v3 Authority Key Identifier: 
+                keyid:27:0D:D0:55:88:5D:DE:1C:37:96:A0:62:14:C2:19:3C:C6:A4:1F:D1
+
+            Authority Information Access: 
+                CA Issuers - URI:http://url-for-aia/Root.cer
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://url-for-crl/Root.crl
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+    Signature Algorithm: sha256WithRSAEncryption
+         01:8b:38:40:44:1d:07:f2:7c:3f:c0:c1:2a:73:7e:9c:7b:42:
+         20:3a:79:cd:8d:ea:20:c5:72:07:0f:72:3b:50:0e:83:1c:43:
+         3d:ed:67:61:be:d1:2a:5d:52:f9:54:5a:5d:04:f8:b9:fd:62:
+         87:a4:52:cf:e5:d3:ff:55:77:9c:80:59:e9:f6:55:7a:5a:51:
+         e4:93:56:35:f7:5d:92:7d:98:8e:ed:1b:16:68:c6:04:3d:28:
+         c4:9c:26:51:dc:a4:38:dc:dc:04:bb:de:ae:8b:25:0a:e1:a1:
+         3d:22:7e:6f:a3:26:76:98:dd:68:d5:e0:29:41:42:2d:17:00:
+         18:ef:a1:34:41:19:24:8c:4b:a8:83:48:f4:57:a8:ef:76:db:
+         a8:bf:c3:6f:f2:7d:4e:c9:1a:84:90:1a:85:61:a4:30:16:3b:
+         a2:62:70:96:a3:4e:55:f9:e2:a5:f0:ac:16:3f:b2:6a:4b:62:
+         fa:73:94:8d:5b:d8:e7:36:04:54:93:a7:3c:59:42:42:f6:4c:
+         41:4a:54:b2:3a:ea:69:33:1b:5b:d7:21:c5:9e:3f:6f:9c:bd:
+         d7:55:73:e6:0f:20:60:4e:88:b4:de:a8:f4:29:3d:f9:fb:2d:
+         61:7e:4d:5d:94:e0:85:0b:47:52:75:f6:94:ad:d0:ba:d5:37:
+         4b:9f:3a:02
+-----BEGIN CERTIFICATE-----
+MIIDZTCCAk2gAwIBAgIBATANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARSb290
+MB4XDTE1MDEwMTEyMDAwMFoXDTE2MDEwMTEyMDAwMFowDzENMAsGA1UEAwwEUm9v
+dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKD5wfqTQnu/5R4h4vXN
+2/dhBG7qBkz81S6fXm6XstTD8UwYAV4+heLAc85W+8xMTvA3teDGMVzABlqQJNhd
+iKvjUysSkAsWxtsZdOcpY1PZW/PngIxehv/o43JrCWxka5I08py99LfBMW90ADE6
+RXCfXaXTnJF/+4eV7wfzjY7JpcvtzC0jv+SYk4iNvrxQAiw6DVJTfpogBNpS26Tl
+crzWQEB/UYYp1/X324WzoH16xQQ+6XPKZTwTkUahtPtri6BefMmdPF7F9iqZ3y4T
+Hn3Y2zACUteUFpO4IF13TSZunMheClatutkmwIDdZqoJCRhB+vJcf64QRSW6zA1d
+2DsCAwEAAaOByzCByDAdBgNVHQ4EFgQUJw3QVYhd3hw3lqBiFMIZPMakH9EwHwYD
+VR0jBBgwFoAUJw3QVYhd3hw3lqBiFMIZPMakH9EwNwYIKwYBBQUHAQEEKzApMCcG
+CCsGAQUFBzAChhtodHRwOi8vdXJsLWZvci1haWEvUm9vdC5jZXIwLAYDVR0fBCUw
+IzAhoB+gHYYbaHR0cDovL3VybC1mb3ItY3JsL1Jvb3QuY3JsMA4GA1UdDwEB/wQE
+AwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQABizhARB0H
+8nw/wMEqc36ce0IgOnnNjeogxXIHD3I7UA6DHEM97WdhvtEqXVL5VFpdBPi5/WKH
+pFLP5dP/VXecgFnp9lV6WlHkk1Y1912SfZiO7RsWaMYEPSjEnCZR3KQ43NwEu96u
+iyUK4aE9In5voyZ2mN1o1eApQUItFwAY76E0QRkkjEuog0j0V6jvdtuov8Nv8n1O
+yRqEkBqFYaQwFjuiYnCWo05V+eKl8KwWP7JqS2L6c5SNW9jnNgRUk6c8WUJC9kxB
+SlSyOuppMxtb1yHFnj9vnL3XVXPmDyBgToi03qj0KT35+y1hfk1dlOCFC0dSdfaU
+rdC61TdLnzoC
+-----END CERTIFICATE-----
diff --git a/net/data/verify_certificate_chain_unittest/unknown-non-critical-policy-qualifier/generate-chains.py b/net/data/verify_certificate_chain_unittest/unknown-non-critical-policy-qualifier/generate-chains.py
new file mode 100755
index 0000000..32dd793a
--- /dev/null
+++ b/net/data/verify_certificate_chain_unittest/unknown-non-critical-policy-qualifier/generate-chains.py
@@ -0,0 +1,28 @@
+#!/usr/bin/python
+# Copyright (c) 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""The intermediate has a policies extension (not marked as critical),
+which contains an unknown qualifer (1.2.3.4)."""
+
+import sys
+sys.path += ['..']
+
+import common
+
+# Self-signed root certificate (used as trust anchor).
+root = common.create_self_signed_root_certificate('Root')
+
+# Intermediate that has a non-critical policies extension containing an unknown
+# policy qualifer.
+intermediate = common.create_intermediate_certificate('Intermediate', root)
+intermediate.get_extensions().add_property(
+    '2.5.29.32', ('DER:30:13:30:11:06:02:2a:03:30:0b:30:09:06:03:'
+                  '2a:03:04:0c:02:68:69'))
+
+# Target certificate.
+target = common.create_end_entity_certificate('Target', intermediate)
+
+chain = [target, intermediate, root]
+common.write_chain(__doc__, chain, 'chain.pem')
diff --git a/net/data/verify_certificate_chain_unittest/unknown-non-critical-policy-qualifier/keys/Intermediate.key b/net/data/verify_certificate_chain_unittest/unknown-non-critical-policy-qualifier/keys/Intermediate.key
new file mode 100644
index 0000000..07d4190
--- /dev/null
+++ b/net/data/verify_certificate_chain_unittest/unknown-non-critical-policy-qualifier/keys/Intermediate.key
@@ -0,0 +1,28 @@
+openssl genrsa 2048
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA2XRsAyKrBRuv0TRDrCqjvV3cEzlf3//0vTy9Vh616bIZHUn/
+nFoxnCB0hyeBIlCjwpDaSNrCzUpN3ex112FbMlceHWOCVGlJ8f8+pWdGsndzYc4w
+nNX3Nh+DDhL4N0ipNuY4YRNaHadwF9INgYfwzwI8E1b86XmWwG2KXaet58U/CSiq
+6ahrI6N4/jQRutASWc+zimjfli9EsLlyVM+6GyyMVqSd27hVckIEE3fMdQQ96bH6
+pBkbPW8KwnpIN4s1xuHMxlC1RcDyMMr/33WvS8PHYxHa+1S/U1egznUYU47HScNK
+eYikHTSk4NL0Y8paAonDlKM4MvY74QbkAuTQJQIDAQABAoIBABiUgGEaWhqJtqX/
+MmcTqs6M/YNuyNeZJyD87Kn/brCr4QregnY5+8GaKZHddUtbsiccGJCDqeFqtMKR
+KADgkF5wWocX6HOCM0mXgI1Rzzv9JJ25oOUZiogjp7y/iC83l2kNPPI8LcOMWa9X
+nw81PpMnvYKyIoSBqfi2aR2NBqtb+xxjMgtHzQVBS4LvvbO93QGqAo/81Y6TKUJf
+P4UCKuU+rcKbwxrpo4NnLzq4SY5T3MnOOY+or8UY3yBWvylxVtLXXxdVRf82IO7I
+MSOmvVhaatTs3eb0quUiHmYw/PygPYpQNgrkgNsy0LjW/EXO+rryb0RrHcyyqzKp
+t5+5TUECgYEA7i9OTN31L2IVrXaLsFE35qY0I48aPgC+nYdboWCKdDgPqbshcgop
+N4mlLMktAfaJouWTJiJYaeGIgsiHbPsKS9CjTx3ow9uTKbYz7n+DSNuHtTIIbqXt
+pjm/tL7xSlXRkBTxtiqHF0KRyDy8tp85B/1ZAg1ggQm67VOMMonzYpcCgYEA6bgv
+BIao/nvx2KdWhuPxKg8mMr8PaD05pMI66stL6UtIdjEGA8cqimHiatpX6cgbHrDN
+b2V58HfM83oFrDthY3V6I5PMHPCr18pylAGtzd1xmAUXJfswS6EpN4XcKU4B+pLk
+Xb05iqcWj++R6vD3CcxWjd7QvPkcn6WjQOd+hqMCgYEAm/QvgZcHNioz5WssVZso
+TgZfNzU4yoY0SPLa5VRP837NiqXzZTcP+8RNeYCTzqg9QIKvEZYFa/Z3KqLgQVQy
+TqfidigwQyWfOv2JDU1Cic7sEZEcAk70xxUi19b6KGD+oSeFiHBaGbnFuGVEbxLc
+BcY1pGlb2cGkOkatEl5PFSsCgYAKhEwxUvHlyaP/UMBW/4jJkLbyT6Cc2yxt2oOW
+LyGyVL3k+52+q8ahRXhPwJV2Ipf90Izd913+URW62wHIVz+xnwao9SyZHMdOxYBU
+YqL0gYNsCMaa4euxa4YURoS6oxT5toRqJ2qgb5ZXUbtqK/+hxD0c0yrnZfpAwM5Q
+zFnoKwKBgDgOT/MACPSDx+kEp85Gs20bVGgl+sSNFH7X4n3645sQPyfnJ/cjsGs/
+atiklVbME2d+mN0Zr6zchz0truajvgDvt1969z5qqo7JQTkaAFaZ+HMiRYLEe/3f
+Mvqaj4Jbrf4RQH8hGstxIYVb0aBVwva3LQNzCBl7YYVJXIl7E7rz
+-----END RSA PRIVATE KEY-----
diff --git a/net/data/verify_certificate_chain_unittest/unknown-non-critical-policy-qualifier/keys/Root.key b/net/data/verify_certificate_chain_unittest/unknown-non-critical-policy-qualifier/keys/Root.key
new file mode 100644
index 0000000..ae35321
--- /dev/null
+++ b/net/data/verify_certificate_chain_unittest/unknown-non-critical-policy-qualifier/keys/Root.key
@@ -0,0 +1,28 @@
+openssl genrsa 2048
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAoPnB+pNCe7/lHiHi9c3b92EEbuoGTPzVLp9ebpey1MPxTBgB
+Xj6F4sBzzlb7zExO8De14MYxXMAGWpAk2F2Iq+NTKxKQCxbG2xl05yljU9lb8+eA
+jF6G/+jjcmsJbGRrkjTynL30t8Exb3QAMTpFcJ9dpdOckX/7h5XvB/ONjsmly+3M
+LSO/5JiTiI2+vFACLDoNUlN+miAE2lLbpOVyvNZAQH9RhinX9ffbhbOgfXrFBD7p
+c8plPBORRqG0+2uLoF58yZ08XsX2KpnfLhMefdjbMAJS15QWk7ggXXdNJm6cyF4K
+Vq262SbAgN1mqgkJGEH68lx/rhBFJbrMDV3YOwIDAQABAoIBAQCB7lY2DNqizBcB
+JVTuFkUv/Wz4oYiIZ6MiyJwOI//CYI3JX67rBQhIKBLRP8Bp/EXS+wSg8xgwxwXZ
+33L9+TExnIEqyQbfDHlhq8CCPRPhQBZKXleR9VJ+CNpdJiwrAzsYfgg9n82A7D0c
+usLU+TXHW7rY+F7tyiGaJNkUz+84ih59oIP3Nf++abxfi97de6CT/wFQ+uM2RWTJ
+lC7V7SaH6p5366ugf7q94AEhAO61rpD4aFg2wEpQ672gyYZrpqZ0ChaYk9vKWkLB
+4n3eU4Y5Lx8Y5C2QMMn83yqy2dfZtey1ASzxqd/7hdJ7FnWcHcGU+c9U6AqTzkyZ
+Y3++alAxAoGBANOQBFxDWAaCodX/X7JA1AlJjqGvEKvouQ4vJMDUdqN4VqL/hNqS
+/xRZBCsFnu7dlbRgxWboJKLm4grsgY32e92KcyHtMjVZFm+3f1VnIqH3Clgmgklb
+WX1LVs705fGJxHoOTpf9A7EabZG4l/KWcHUX0Uzc4kwozkrxXOYrjuPVAoGBAMLJ
+ngeub0QDWCpZx9YjS+VzqcJNugdZ0F9aIhJdJ+2WM/2HPNN693Mf2N+uOuN6aVhE
+pxrPb9KRe9Pvt/2lQQ0Ih9oHvTXHzD9ChMkrrnGk2n48e6P9S/ZHvtQ14FDXZduh
+SFwGwnNCPh062MirE1QnzEVc/6D7LDyB2z7LN6PPAoGAboqKbLbXUJeC/fCwTNAo
+ui2P9BUn7drsjme9mW+qCaIACqzd+uLhmv0j+2K1d3OaHCSQIEPdmKxwoWqQCuAm
+Okxz+d8Y490HpxnG05XO97e/1O1SGAg+CwSJeBtn5juoyGmfUTTnb+syIvjDlAJd
+AiGOv82OBGMx3uCcPLu3rn0CgYEAgQZyEl5+P0wD05HC6FpbBLMXyKgZyJK6jt6y
+YP2p3Bu5dn9lUCRM0spOquPLCDFmpFxdYEn1pUJPBrTsaCxVqZ9z9X1Y3M3qwiB6
+upKfq5FqVWIU+Cxpa8pcYk8JtiLnypLLzoF/vj8ry8rWHB4N4Jm4Gl4eyfB2feH6
+a17LDUsCgYEAr+zVvi5uZDUKSGfSad3HZDmGHy57sCWEkddQ+4cS6mmUbEoBhaWt
+39siTyF4nMclHiKqSiqPNDiZaA7+68QsNrYgatGaXveq7rC7JMPJDk4H9YGCTQWM
+vJSqEH00PYP8n9cR9P9wQRNEtD58r4Fq/9imxpUi1oU6K4FEaeJXvlU=
+-----END RSA PRIVATE KEY-----
diff --git a/net/data/verify_certificate_chain_unittest/unknown-non-critical-policy-qualifier/keys/Target.key b/net/data/verify_certificate_chain_unittest/unknown-non-critical-policy-qualifier/keys/Target.key
new file mode 100644
index 0000000..592f8b6
--- /dev/null
+++ b/net/data/verify_certificate_chain_unittest/unknown-non-critical-policy-qualifier/keys/Target.key
@@ -0,0 +1,28 @@
+openssl genrsa 2048
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAvtnDlGrC1xu0MxopDcpI4vGUkyc2ccGi3GcOXWewqQicZwi6
+2XRfAWJdfyq7Mu0Mr8hatQIkRW+QTIOrDjAZwt+81SWZsPNe4SdbBi/KPtZJ+4eN
+0/25uSeAvrWIcjsbID8EaQSJZu4g98KQwSeqKfqI/y8QO4HP0Lnpp4Tc8afQSeBu
+F7K6Ce2+nKPyZjfdIJhDMb0C0VVjiPZVEyC3uQvJyfujWw+QVuiK3KV6krxGXYKk
+4UIsfHZlY4f04FrPFSITSR2qDeolCHxjGTkvHRUufJrn1QMhdmwiGr4Si3LFyw9B
+7w/TvngdEuDCKevXNihUrY3OyHkvTxPBKzvk/wIDAQABAoIBAAEpdD5gFAWZPpSd
+ESjrVVxEWkI3F+bDZglvey/oZLPywHkEHZcbVUS945HVocQim1QqpRq7A5uEnV1J
+4+23L0D3eb/WdcQVBzvZsH0uJ04QoeytG5jxz4KoEemXU1hWsciVeaJmC6wbwQmh
+Hp5+blPJGbJsB+5E0Q4VQ63oaivydegcANCd65qX0gwlTEItjelKx5r0lvMhvrwJ
+cCj0C4fx0H7souk4oD0eWCXh6vORCfKGcpldXADnuFs0VzbNHdjuOjtw+f/GB3bO
+0B+kbp3+D04QXAJnfavmfMYZEPLQ6aQ9LRZpfs5RAZxMMCo2iagRvIAXVIX15i0/
+h3NaDOkCgYEA7sMSMm+BQczHD4NtGeftJaDhKTBgxYsV3f87Dwkxn1JM03Ex98h1
+MsqWZ/5TRyeH73mH1lMi3a1B7HGyCmmez/5UJ214pSDRBS+ftLiaTDeYhHrQBlCk
+2QUCxcaIogM6ScqhNk3QtjwdAqTDB092yHo9a1qLVmnlmXHS82+rdosCgYEAzKEr
+UG6eZpYP0SAM4c/lST3UCiKZQKlAP747d9Uw3qnkiMzVUx9pTvSHN+VWKDNkIUaI
+hYhf36RlEQ2eeCrQHJKV/1GQ2NDZdAB2tcjYdtWndfUs/9L276aQEOK/WssJWop9
+4EPXCRlA2CZiufRP7Z9vW6P14rBuSMb8WGVEjd0CgYBxEbhAYkXYH131nsvABPFt
+bG60ztBFPMSySPhy+muhg5wWVfjP2wKFdyMbRHmzrKjXY7pMaTqNuEsL6hviinpH
+Da5UracArDXQOulq8DFB/uIfevmgw/gNPi498vMpplWoNGl8CgXZB0bFxbkGcT9T
+WXBUsqBh3+x3akv4NN4CWQKBgFgNrl7RCvZn8mxx1zlARZDruxeVZc7uRchoKpzZ
+L0tRXhcD1XQMWztve9Qwqo9n9dlfyZV0ZpUM37Sj4KIjAiSZa1RPm/3AeSIypOJ6
+h7MzIvleRDvdYwci7HFnsSH3ppHAcIQVjS3MIRwiAG+2UsKUzc1oU4F1nC9S+Pls
+skA9AoGBALxWatyTfR2QkQOXhbiRWdACACsLtbBMbjbulzewspZjGNffixyOWkxc
+Y/u2WEuI1uoHK7qJLZ3f4LEk/CdsaC7mX0pZ4DWozxwJJDpIHvURTLfn2uFRiN5l
+UphJQEkK+7ojHqve/ebMYs8LrBtHCVLvHptb/YAcuf2Bvp9IpPdf
+-----END RSA PRIVATE KEY-----
diff --git a/net/data/verify_certificate_chain_unittest/unknown-non-critical-policy-qualifier/main.test b/net/data/verify_certificate_chain_unittest/unknown-non-critical-policy-qualifier/main.test
new file mode 100644
index 0000000..0bcea7d
--- /dev/null
+++ b/net/data/verify_certificate_chain_unittest/unknown-non-critical-policy-qualifier/main.test
@@ -0,0 +1,5 @@
+chain: chain.pem
+last_cert_trust: TRUSTED_ANCHOR
+utc_time: 150302120000Z
+key_purpose: SERVER_AUTH
+expected_errors:
diff --git a/remoting/host/it2me/it2me_host.cc b/remoting/host/it2me/it2me_host.cc
index 08f91ba7..d70b26b 100644
--- a/remoting/host/it2me/it2me_host.cc
+++ b/remoting/host/it2me/it2me_host.cc
@@ -55,33 +55,29 @@
 
 }  // namespace
 
-It2MeHost::It2MeHost(
-    std::unique_ptr<ChromotingHostContext> host_context,
-    std::unique_ptr<It2MeConfirmationDialogFactory> dialog_factory,
-    base::WeakPtr<It2MeHost::Observer> observer,
-    std::unique_ptr<SignalStrategy> signal_strategy,
-    const std::string& username,
-    const std::string& directory_bot_jid)
-    : host_context_(std::move(host_context)),
-      observer_(observer),
-      signal_strategy_(std::move(signal_strategy)),
-      username_(username),
-      directory_bot_jid_(directory_bot_jid),
-      confirmation_dialog_factory_(std::move(dialog_factory)) {
-  DCHECK(host_context_->ui_task_runner()->BelongsToCurrentThread());
-}
+It2MeHost::It2MeHost() {}
 
 It2MeHost::~It2MeHost() {
   // Check that resources that need to be torn down on the UI thread are gone.
   DCHECK(!desktop_environment_factory_.get());
 }
 
-void It2MeHost::Connect() {
-  if (!host_context_->ui_task_runner()->BelongsToCurrentThread()) {
-    host_context_->ui_task_runner()->PostTask(
-        FROM_HERE, base::Bind(&It2MeHost::Connect, this));
-    return;
-  }
+void It2MeHost::Connect(
+    std::unique_ptr<ChromotingHostContext> host_context,
+    std::unique_ptr<base::DictionaryValue> policies,
+    std::unique_ptr<It2MeConfirmationDialogFactory> dialog_factory,
+    base::WeakPtr<It2MeHost::Observer> observer,
+    std::unique_ptr<SignalStrategy> signal_strategy,
+    const std::string& username,
+    const std::string& directory_bot_jid) {
+  DCHECK(host_context->ui_task_runner()->BelongsToCurrentThread());
+
+  host_context_ = std::move(host_context);
+  observer_ = std::move(observer);
+  confirmation_dialog_factory_ = std::move(dialog_factory);
+  signal_strategy_ = std::move(signal_strategy);
+
+  OnPolicyUpdate(std::move(policies));
 
   desktop_environment_factory_.reset(new It2MeDesktopEnvironmentFactory(
       host_context_->network_task_runner(),
@@ -90,7 +86,8 @@
 
   // Switch to the network thread to start the actual connection.
   host_context_->network_task_runner()->PostTask(
-      FROM_HERE, base::Bind(&It2MeHost::ReadPolicyAndConnect, this));
+      FROM_HERE, base::Bind(&It2MeHost::ConnectOnNetworkThread, this, username,
+                            directory_bot_jid));
 }
 
 void It2MeHost::Disconnect() {
@@ -99,81 +96,25 @@
       FROM_HERE, base::Bind(&It2MeHost::DisconnectOnNetworkThread, this));
 }
 
-void It2MeHost::DisconnectOnNetworkThread() {
-  DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
-
-  // Disconnect() may be called even when after the host been already stopped.
-  // Ignore repeated calls.
-  if (state_ == kDisconnected) {
-    return;
-  }
-
-  confirmation_dialog_proxy_.reset();
-
-  host_event_logger_.reset();
-  if (host_) {
-    host_->RemoveStatusObserver(this);
-    host_.reset();
-  }
-
-  register_request_.reset();
-  host_status_logger_.reset();
-  signal_strategy_.reset();
-
-  // Post tasks to delete UI objects on the UI thread.
-  host_context_->ui_task_runner()->DeleteSoon(
-      FROM_HERE, desktop_environment_factory_.release());
-
-  SetState(kDisconnected, "");
-}
-
-void It2MeHost::RequestNatPolicy() {
-  if (!host_context_->network_task_runner()->BelongsToCurrentThread()) {
-    DCHECK(host_context_->ui_task_runner()->BelongsToCurrentThread());
-    host_context_->network_task_runner()->PostTask(
-        FROM_HERE, base::Bind(&It2MeHost::RequestNatPolicy, this));
-    return;
-  }
-
-  if (policy_received_)
-    UpdateNatPolicy(nat_traversal_enabled_);
-}
-
-void It2MeHost::ReadPolicyAndConnect() {
+void It2MeHost::ConnectOnNetworkThread(const std::string& username,
+                                       const std::string& directory_bot_jid) {
   DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
   DCHECK_EQ(kDisconnected, state_);
 
-  SetState(kStarting, "");
-
-  // Only proceed to FinishConnect() if at least one policy update has been
-  // received.  Otherwise, create the policy watcher and thunk the connect.
-  if (policy_received_) {
-    FinishConnect();
-  } else {
-    pending_connect_ = base::Bind(&It2MeHost::FinishConnect, this);
-  }
-}
-
-void It2MeHost::FinishConnect() {
-  DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
-
-  if (state_ != kStarting) {
-    // Host has been stopped while we were fetching policy.
-    return;
-  }
+  SetState(kStarting, std::string());
 
   // Check the host domain policy.
   if (!required_host_domain_list_.empty()) {
     bool matched = false;
     for (const auto& domain : required_host_domain_list_) {
-      if (base::EndsWith(username_, std::string("@") + domain,
+      if (base::EndsWith(username, std::string("@") + domain,
                          base::CompareCase::INSENSITIVE_ASCII)) {
         matched = true;
         break;
       }
     }
     if (!matched) {
-      SetState(kInvalidDomainError, "");
+      SetState(kInvalidDomainError, std::string());
       return;
     }
   }
@@ -185,7 +126,7 @@
   // Request registration of the host for support.
   std::unique_ptr<RegisterSupportHostRequest> register_request(
       new RegisterSupportHostRequest(
-          signal_strategy_.get(), host_key_pair_, directory_bot_jid_,
+          signal_strategy_.get(), host_key_pair_, directory_bot_jid,
           base::Bind(&It2MeHost::OnReceivedSupportID, base::Unretained(this))));
 
   // Beyond this point nothing can fail, so save the config and request.
@@ -240,7 +181,7 @@
   host_->AddStatusObserver(this);
   host_status_logger_.reset(
       new HostStatusLogger(host_->AsWeakPtr(), ServerLogEntry::IT2ME,
-                           signal_strategy_.get(), directory_bot_jid_));
+                           signal_strategy_.get(), directory_bot_jid));
 
   // Create event logger.
   host_event_logger_ =
@@ -248,9 +189,9 @@
 
   // Connect signaling and start the host.
   signal_strategy_->Connect();
-  host_->Start(username_);
+  host_->Start(username);
 
-  SetState(kRequestedAccessCode, "");
+  SetState(kRequestedAccessCode, std::string());
   return;
 }
 
@@ -288,7 +229,7 @@
       FROM_HERE, base::Bind(&It2MeHost::Observer::OnClientAuthenticated,
                             observer_, client_username));
 
-  SetState(kConnected, "");
+  SetState(kConnected, std::string());
 }
 
 void It2MeHost::OnClientDisconnected(const std::string& jid) {
@@ -341,12 +282,6 @@
                           &port_range_string)) {
     UpdateHostUdpPortRangePolicy(port_range_string);
   }
-
-  policy_received_ = true;
-
-  if (!pending_connect_.is_null()) {
-    base::ResetAndReturn(&pending_connect_).Run();
-  }
 }
 
 void It2MeHost::UpdateNatPolicy(bool nat_traversal_enabled) {
@@ -507,7 +442,35 @@
       FROM_HERE, base::Bind(&It2MeHost::Observer::OnStoreAccessCode, observer_,
                             access_code, lifetime));
 
-  SetState(kReceivedAccessCode, "");
+  SetState(kReceivedAccessCode, std::string());
+}
+
+void It2MeHost::DisconnectOnNetworkThread() {
+  DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
+
+  // Disconnect() may be called even when after the host been already stopped.
+  // Ignore repeated calls.
+  if (state_ == kDisconnected) {
+    return;
+  }
+
+  confirmation_dialog_proxy_.reset();
+
+  host_event_logger_.reset();
+  if (host_) {
+    host_->RemoveStatusObserver(this);
+    host_.reset();
+  }
+
+  register_request_.reset();
+  host_status_logger_.reset();
+  signal_strategy_.reset();
+
+  // Post tasks to delete UI objects on the UI thread.
+  host_context_->ui_task_runner()->DeleteSoon(
+      FROM_HERE, desktop_environment_factory_.release());
+
+  SetState(kDisconnected, std::string());
 }
 
 void It2MeHost::ValidateConnectionDetails(
@@ -595,19 +558,10 @@
 }
 
 It2MeHostFactory::It2MeHostFactory() {}
-
 It2MeHostFactory::~It2MeHostFactory() {}
 
-scoped_refptr<It2MeHost> It2MeHostFactory::CreateIt2MeHost(
-    std::unique_ptr<ChromotingHostContext> context,
-    base::WeakPtr<It2MeHost::Observer> observer,
-    std::unique_ptr<SignalStrategy> signal_strategy,
-    const std::string& username,
-    const std::string& directory_bot_jid) {
-  DCHECK(context->ui_task_runner()->BelongsToCurrentThread());
-  return new It2MeHost(
-      std::move(context), base::MakeUnique<It2MeConfirmationDialogFactory>(),
-      observer, std::move(signal_strategy), username, directory_bot_jid);
+scoped_refptr<It2MeHost> It2MeHostFactory::CreateIt2MeHost() {
+  return new It2MeHost();
 }
 
 }  // namespace remoting
diff --git a/remoting/host/it2me/it2me_host.h b/remoting/host/it2me/it2me_host.h
index 0bd61ce..1ee72f30d 100644
--- a/remoting/host/it2me/it2me_host.h
+++ b/remoting/host/it2me/it2me_host.h
@@ -61,25 +61,23 @@
                                 const std::string& error_message) = 0;
   };
 
-  It2MeHost(std::unique_ptr<ChromotingHostContext> context,
-            std::unique_ptr<It2MeConfirmationDialogFactory> dialog_factory_,
-            base::WeakPtr<It2MeHost::Observer> observer,
-            std::unique_ptr<SignalStrategy> signal_strategy,
-            const std::string& username,
-            const std::string& directory_bot_jid);
+  It2MeHost();
 
   // Methods called by the script object, from the plugin thread.
 
   // Creates It2Me host structures and starts the host.
-  virtual void Connect();
+  virtual void Connect(
+      std::unique_ptr<ChromotingHostContext> context,
+      std::unique_ptr<base::DictionaryValue> policies,
+      std::unique_ptr<It2MeConfirmationDialogFactory> dialog_factory,
+      base::WeakPtr<It2MeHost::Observer> observer,
+      std::unique_ptr<SignalStrategy> signal_strategy,
+      const std::string& username,
+      const std::string& directory_bot_jid);
 
   // Disconnects and shuts down the host.
   virtual void Disconnect();
 
-  // TODO (weitaosu): Remove RequestNatPolicy from It2MeHost.
-  // Request a NAT policy notification.
-  virtual void RequestNatPolicy();
-
   // remoting::HostStatusObserver implementation.
   void OnAccessDenied(const std::string& jid) override;
   void OnClientConnected(const std::string& jid) override;
@@ -107,6 +105,7 @@
   base::WeakPtr<It2MeHost::Observer> observer() { return observer_; }
 
  private:
+  friend class MockIt2MeHost;
   FRIEND_TEST_ALL_PREFIXES(It2MeHostTest, HostUdpPortRangePolicy_ValidRange);
   FRIEND_TEST_ALL_PREFIXES(It2MeHostTest, HostUdpPortRangePolicy_NoRange);
 
@@ -121,11 +120,9 @@
       const protocol::ValidatingAuthenticator::ResultCallback& result_callback,
       It2MeConfirmationDialog::Result result);
 
-  // Called by Connect() to check for policies and start connection process.
-  void ReadPolicyAndConnect();
-
-  // Called by ReadPolicyAndConnect once policies have been read.
-  void FinishConnect();
+  // Task posted to the network thread from Connect().
+  void ConnectOnNetworkThread(const std::string& username,
+                              const std::string& directory_bot_jid);
 
   // Called when the support host registration completes.
   void OnReceivedSupportID(const std::string& support_id,
@@ -151,8 +148,6 @@
   std::unique_ptr<ChromotingHostContext> host_context_;
   base::WeakPtr<It2MeHost::Observer> observer_;
   std::unique_ptr<SignalStrategy> signal_strategy_;
-  std::string username_;
-  std::string directory_bot_jid_;
 
   It2MeHostState state_ = kDisconnected;
 
@@ -181,17 +176,6 @@
   // Tracks the JID of the remote user when in a connecting state.
   std::string connecting_jid_;
 
-  // Indicates whether or not a policy has ever been read. This is to ensure
-  // that on startup, we do not accidentally start a connection before we have
-  // queried our policy restrictions.
-  bool policy_received_ = false;
-
-  // On startup, it is possible to have Connect() called before the policy read
-  // is completed.  Rather than just failing, we thunk the connection call so
-  // it can be executed after at least one successful policy read. This
-  // variable contains the thunk if it is necessary.
-  base::Closure pending_connect_;
-
   DISALLOW_COPY_AND_ASSIGN(It2MeHost);
 };
 
@@ -202,12 +186,7 @@
   It2MeHostFactory();
   virtual ~It2MeHostFactory();
 
-  virtual scoped_refptr<It2MeHost> CreateIt2MeHost(
-      std::unique_ptr<ChromotingHostContext> context,
-      base::WeakPtr<It2MeHost::Observer> observer,
-      std::unique_ptr<SignalStrategy> signal_strategy,
-      const std::string& username,
-      const std::string& directory_bot_jid);
+  virtual scoped_refptr<It2MeHost> CreateIt2MeHost();
 
  private:
   DISALLOW_COPY_AND_ASSIGN(It2MeHostFactory);
diff --git a/remoting/host/it2me/it2me_host_unittest.cc b/remoting/host/it2me/it2me_host_unittest.cc
index c00accc..10cec61 100644
--- a/remoting/host/it2me/it2me_host_unittest.cc
+++ b/remoting/host/it2me/it2me_host_unittest.cc
@@ -169,6 +169,8 @@
   // Used to set ConfirmationDialog behavior.
   FakeIt2MeDialogFactory* dialog_factory_ = nullptr;
 
+  std::unique_ptr<base::DictionaryValue> policies_;
+
   scoped_refptr<It2MeHost> it2me_host_;
 
  private:
@@ -177,6 +179,7 @@
   std::unique_ptr<base::MessageLoop> message_loop_;
   std::unique_ptr<base::RunLoop> run_loop_;
 
+  std::unique_ptr<ChromotingHostContext> host_context_;
   scoped_refptr<AutoThreadTaskRunner> network_task_runner_;
   scoped_refptr<AutoThreadTaskRunner> ui_task_runner_;
 
@@ -186,7 +189,6 @@
 };
 
 It2MeHostTest::It2MeHostTest() : weak_factory_(this) {}
-
 It2MeHostTest::~It2MeHostTest() {}
 
 void It2MeHostTest::SetUp() {
@@ -198,23 +200,10 @@
   message_loop_.reset(new base::MessageLoop());
   run_loop_.reset(new base::RunLoop());
 
-  std::unique_ptr<ChromotingHostContext> host_context(
-      ChromotingHostContext::Create(new AutoThreadTaskRunner(
-          base::ThreadTaskRunnerHandle::Get(), run_loop_->QuitClosure())));
-  network_task_runner_ = host_context->network_task_runner();
-  ui_task_runner_ = host_context->ui_task_runner();
-
-  std::unique_ptr<FakeIt2MeDialogFactory> dialog_factory(
-      new FakeIt2MeDialogFactory());
-  dialog_factory_ = dialog_factory.get();
-  it2me_host_ = new It2MeHost(
-      std::move(host_context),
-      std::move(dialog_factory), weak_factory_.GetWeakPtr(),
-      base::WrapUnique(
-          new FakeSignalStrategy(SignalingAddress("fake_local_jid"))),
-      kTestUserName, "fake_bot_jid");
-
-  it2me_host_->OnPolicyUpdate(PolicyWatcher::GetDefaultPolicies());
+  host_context_ = ChromotingHostContext::Create(new AutoThreadTaskRunner(
+      base::ThreadTaskRunnerHandle::Get(), run_loop_->QuitClosure()));
+  network_task_runner_ = host_context_->network_task_runner();
+  ui_task_runner_ = host_context_->ui_task_runner();
 }
 
 void It2MeHostTest::TearDown() {
@@ -223,6 +212,7 @@
   it2me_host_->Disconnect();
   network_task_runner_ = nullptr;
   ui_task_runner_ = nullptr;
+  host_context_.reset();
   it2me_host_ = nullptr;
   run_loop_->Run();
 }
@@ -237,11 +227,13 @@
 void It2MeHostTest::SetPolicies(
     std::initializer_list<std::pair<base::StringPiece, const base::Value&>>
         policies) {
-  auto dictionary = base::MakeUnique<base::DictionaryValue>();
+  policies_ = base::MakeUnique<base::DictionaryValue>();
   for (const auto& policy : policies) {
-    dictionary->Set(policy.first, policy.second.CreateDeepCopy());
+    policies_->Set(policy.first, policy.second.CreateDeepCopy());
   }
-  it2me_host_->OnPolicyUpdate(std::move(dictionary));
+  if (it2me_host_) {
+    it2me_host_->OnPolicyUpdate(std::move(policies_));
+  }
 }
 
 void It2MeHostTest::StartupHostStateHelper(const base::Closure& quit_closure) {
@@ -259,7 +251,21 @@
 }
 
 void It2MeHostTest::StartHost() {
-  it2me_host_->Connect();
+  if (!policies_) {
+    policies_ = PolicyWatcher::GetDefaultPolicies();
+  }
+
+  std::unique_ptr<FakeIt2MeDialogFactory> dialog_factory(
+      new FakeIt2MeDialogFactory());
+  dialog_factory_ = dialog_factory.get();
+
+  it2me_host_ = new It2MeHost();
+  it2me_host_->Connect(host_context_->Copy(),
+                       base::MakeUnique<base::DictionaryValue>(*policies_),
+                       std::move(dialog_factory), weak_factory_.GetWeakPtr(),
+                       base::WrapUnique(new FakeSignalStrategy(
+                           SignalingAddress("fake_local_jid"))),
+                       kTestUserName, "fake_bot_jid");
 
   base::RunLoop run_loop;
   state_change_callback_ =
@@ -541,8 +547,8 @@
 }
 
 TEST_F(It2MeHostTest, ConnectionValidation_ConfirmationDialog_Reject) {
-  dialog_factory_->set_dialog_result(DialogResult::CANCEL);
   StartHost();
+  dialog_factory_->set_dialog_result(DialogResult::CANCEL);
   RunValidationCallback(kTestClientJid);
   ASSERT_EQ(ValidationResult::ERROR_REJECTED_BY_USER, validation_result_);
   RunUntilStateChanged(It2MeHostState::kDisconnected);
diff --git a/remoting/host/it2me/it2me_native_messaging_host.cc b/remoting/host/it2me/it2me_native_messaging_host.cc
index 3a114c1..b824c1b 100644
--- a/remoting/host/it2me/it2me_native_messaging_host.cc
+++ b/remoting/host/it2me/it2me_native_messaging_host.cc
@@ -30,6 +30,7 @@
 #include "remoting/base/service_urls.h"
 #include "remoting/host/chromoting_host_context.h"
 #include "remoting/host/host_exit_codes.h"
+#include "remoting/host/it2me/it2me_confirmation_dialog.h"
 #include "remoting/host/policy_watcher.h"
 #include "remoting/signaling/delegating_signal_strategy.h"
 
@@ -49,10 +50,10 @@
     {kStarting, "STARTING"},
     {kRequestedAccessCode, "REQUESTED_ACCESS_CODE"},
     {kReceivedAccessCode, "RECEIVED_ACCESS_CODE"},
+    {kConnecting, "CONNECTING"},
     {kConnected, "CONNECTED"},
     {kError, "ERROR"},
     {kInvalidDomainError, "INVALID_DOMAIN_ERROR"},
-    {kConnecting, "CONNECTING"},
 };
 
 #if defined(OS_WIN)
@@ -328,11 +329,11 @@
   }
 
   // Create the It2Me host and start connecting.
-  it2me_host_ = factory_->CreateIt2MeHost(host_context_->Copy(), weak_ptr_,
-                                          std::move(signal_strategy), username,
-                                          directory_bot_jid);
-  it2me_host_->OnPolicyUpdate(std::move(policies));
-  it2me_host_->Connect();
+  it2me_host_ = factory_->CreateIt2MeHost();
+  it2me_host_->Connect(host_context_->Copy(), std::move(policies),
+                       base::MakeUnique<It2MeConfirmationDialogFactory>(),
+                       weak_ptr_, std::move(signal_strategy), username,
+                       directory_bot_jid);
 
   SendMessageToClient(std::move(response));
 }
@@ -520,7 +521,7 @@
     }
   }
 
-  if (it2me_host_.get()) {
+  if (it2me_host_) {
     it2me_host_->OnPolicyUpdate(std::move(policies));
   }
 }
diff --git a/remoting/host/it2me/it2me_native_messaging_host_unittest.cc b/remoting/host/it2me/it2me_native_messaging_host_unittest.cc
index 5c2c4b8..730b61b 100644
--- a/remoting/host/it2me/it2me_native_messaging_host_unittest.cc
+++ b/remoting/host/it2me/it2me_native_messaging_host_unittest.cc
@@ -40,7 +40,8 @@
 namespace {
 
 const char kTestAccessCode[] = "888888";
-const int kTestAccessCodeLifetimeInSeconds = 666;
+constexpr base::TimeDelta kTestAccessCodeLifetime =
+    base::TimeDelta::FromSeconds(666);
 const char kTestClientUsername[] = "some_user@gmail.com";
 
 void VerifyId(std::unique_ptr<base::DictionaryValue> response,
@@ -77,24 +78,21 @@
   EXPECT_EQ(id, int_value);
 }
 
+}  // namespace
+
 class MockIt2MeHost : public It2MeHost {
  public:
-  MockIt2MeHost(std::unique_ptr<ChromotingHostContext> context,
-                base::WeakPtr<It2MeHost::Observer> observer,
-                std::unique_ptr<SignalStrategy> signal_strategy,
-                const std::string& username,
-                const std::string& directory_bot_jid)
-      : It2MeHost(std::move(context),
-                  /*confirmation_dialog_factory=*/nullptr,
-                  observer,
-                  std::move(signal_strategy),
-                  username,
-                  directory_bot_jid) {}
+  MockIt2MeHost() {}
 
   // It2MeHost overrides
-  void Connect() override;
+  void Connect(std::unique_ptr<ChromotingHostContext> context,
+               std::unique_ptr<base::DictionaryValue> policies,
+               std::unique_ptr<It2MeConfirmationDialogFactory> dialog_factory,
+               base::WeakPtr<It2MeHost::Observer> observer,
+               std::unique_ptr<SignalStrategy> signal_strategy,
+               const std::string& username,
+               const std::string& directory_bot_jid) override;
   void Disconnect() override;
-  void RequestNatPolicy() override;
 
  private:
   ~MockIt2MeHost() override {}
@@ -104,30 +102,35 @@
   DISALLOW_COPY_AND_ASSIGN(MockIt2MeHost);
 };
 
-void MockIt2MeHost::Connect() {
-  if (!host_context()->ui_task_runner()->BelongsToCurrentThread()) {
-    host_context()->ui_task_runner()->PostTask(
-        FROM_HERE, base::Bind(&MockIt2MeHost::Connect, this));
-    return;
-  }
+void MockIt2MeHost::Connect(
+    std::unique_ptr<ChromotingHostContext> context,
+    std::unique_ptr<base::DictionaryValue> policies,
+    std::unique_ptr<It2MeConfirmationDialogFactory> dialog_factory,
+    base::WeakPtr<It2MeHost::Observer> observer,
+    std::unique_ptr<SignalStrategy> signal_strategy,
+    const std::string& username,
+    const std::string& directory_bot_jid) {
+  DCHECK(context->ui_task_runner()->BelongsToCurrentThread());
+
+  host_context_ = std::move(context);
+  observer_ = std::move(observer);
+  signal_strategy_ = std::move(signal_strategy);
+
+  OnPolicyUpdate(std::move(policies));
 
   RunSetState(kStarting);
   RunSetState(kRequestedAccessCode);
 
-  std::string access_code(kTestAccessCode);
-  base::TimeDelta lifetime =
-      base::TimeDelta::FromSeconds(kTestAccessCodeLifetimeInSeconds);
   host_context()->ui_task_runner()->PostTask(
-      FROM_HERE, base::Bind(&It2MeHost::Observer::OnStoreAccessCode, observer(),
-                            access_code, lifetime));
+      FROM_HERE, base::Bind(&It2MeHost::Observer::OnStoreAccessCode, observer_,
+                            kTestAccessCode, kTestAccessCodeLifetime));
 
   RunSetState(kReceivedAccessCode);
   RunSetState(kConnecting);
 
-  std::string client_username(kTestClientUsername);
   host_context()->ui_task_runner()->PostTask(
       FROM_HERE, base::Bind(&It2MeHost::Observer::OnClientAuthenticated,
-                            observer(), client_username));
+                            observer_, kTestClientUsername));
 
   RunSetState(kConnected);
 }
@@ -143,8 +146,6 @@
   RunSetState(kDisconnected);
 }
 
-void MockIt2MeHost::RequestNatPolicy() {}
-
 void MockIt2MeHost::RunSetState(It2MeHostState state) {
   if (!host_context()->network_task_runner()->BelongsToCurrentThread()) {
     host_context()->network_task_runner()->PostTask(
@@ -156,37 +157,17 @@
 
 class MockIt2MeHostFactory : public It2MeHostFactory {
  public:
-  MockIt2MeHostFactory();
-  ~MockIt2MeHostFactory() override;
+  MockIt2MeHostFactory() {}
+  ~MockIt2MeHostFactory() override {}
 
-  scoped_refptr<It2MeHost> CreateIt2MeHost(
-      std::unique_ptr<ChromotingHostContext> context,
-      base::WeakPtr<It2MeHost::Observer> observer,
-      std::unique_ptr<SignalStrategy> signal_strategy,
-      const std::string& username,
-      const std::string& directory_bot_jid) override;
+  scoped_refptr<It2MeHost> CreateIt2MeHost() override {
+    return new MockIt2MeHost();
+  }
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockIt2MeHostFactory);
 };
 
-MockIt2MeHostFactory::MockIt2MeHostFactory() : It2MeHostFactory() {}
-
-MockIt2MeHostFactory::~MockIt2MeHostFactory() {}
-
-scoped_refptr<It2MeHost> MockIt2MeHostFactory::CreateIt2MeHost(
-    std::unique_ptr<ChromotingHostContext> context,
-    base::WeakPtr<It2MeHost::Observer> observer,
-    std::unique_ptr<SignalStrategy> signal_strategy,
-    const std::string& username,
-    const std::string& directory_bot_jid) {
-  return new MockIt2MeHost(std::move(context), observer,
-                           std::move(signal_strategy), username,
-                           directory_bot_jid);
-}
-
-}  // namespace
-
 class It2MeNativeMessagingHostTest : public testing::Test {
  public:
   It2MeNativeMessagingHostTest() {}
@@ -418,10 +399,10 @@
         EXPECT_TRUE(response->GetString("accessCode", &value));
         EXPECT_EQ(kTestAccessCode, value);
 
-        int accessCodeLifetime;
+        int access_code_lifetime;
         EXPECT_TRUE(
-            response->GetInteger("accessCodeLifetime", &accessCodeLifetime));
-        EXPECT_EQ(kTestAccessCodeLifetimeInSeconds, accessCodeLifetime);
+            response->GetInteger("accessCodeLifetime", &access_code_lifetime));
+        EXPECT_EQ(kTestAccessCodeLifetime.InSeconds(), access_code_lifetime);
       } else if (state ==
                  It2MeNativeMessagingHost::HostStateToString(kConnecting)) {
         EXPECT_FALSE(connecting_received);
diff --git a/services/service_manager/public/cpp/interface_provider.cc b/services/service_manager/public/cpp/interface_provider.cc
index 484722e2..2d4e273 100644
--- a/services/service_manager/public/cpp/interface_provider.cc
+++ b/services/service_manager/public/cpp/interface_provider.cc
@@ -13,13 +13,27 @@
   pending_request_ = MakeRequest(&interface_provider_);
 }
 
+InterfaceProvider::InterfaceProvider(
+    mojom::InterfaceProviderPtr interface_provider)
+    : interface_provider_(std::move(interface_provider)), weak_factory_(this) {}
+
 InterfaceProvider::~InterfaceProvider() {}
 
+void InterfaceProvider::Close() {
+  if (pending_request_.is_pending())
+    pending_request_.PassMessagePipe().reset();
+  interface_provider_.reset();
+}
+
 void InterfaceProvider::Bind(mojom::InterfaceProviderPtr interface_provider) {
-  DCHECK(pending_request_.is_pending());
+  DCHECK(pending_request_.is_pending() || !interface_provider_);
   DCHECK(forward_callback_.is_null());
-  mojo::FuseInterface(std::move(pending_request_),
-                      interface_provider.PassInterface());
+  if (pending_request_.is_pending()) {
+    mojo::FuseInterface(std::move(pending_request_),
+                        interface_provider.PassInterface());
+  } else {
+    interface_provider_ = std::move(interface_provider);
+  }
 }
 
 void InterfaceProvider::Forward(const ForwardCallback& callback) {
@@ -58,6 +72,14 @@
   }
 }
 
+bool InterfaceProvider::HasBinderForName(const std::string& name) const {
+  return binders_.find(name) != binders_.end();
+}
+
+void InterfaceProvider::ClearBinderForName(const std::string& name) {
+  binders_.erase(name);
+}
+
 void InterfaceProvider::ClearBinders() {
   binders_.clear();
 }
diff --git a/services/service_manager/public/cpp/interface_provider.h b/services/service_manager/public/cpp/interface_provider.h
index af3d4f9..4945387 100644
--- a/services/service_manager/public/cpp/interface_provider.h
+++ b/services/service_manager/public/cpp/interface_provider.h
@@ -35,6 +35,14 @@
       provider_->SetBinderForName(name, binder);
     }
 
+    bool HasBinderForName(const std::string& name) {
+      return provider_->HasBinderForName(name);
+    }
+
+    void ClearBinderForName(const std::string& name) {
+      provider_->ClearBinderForName(name);
+    }
+
     void ClearBinders() {
       provider_->ClearBinders();
     }
@@ -44,9 +52,21 @@
     DISALLOW_COPY_AND_ASSIGN(TestApi);
   };
 
+  // Constructs an InterfaceProvider which is usable immediately despite not
+  // being bound to any actual remote implementation. Must call Bind()
+  // eventually in order for the provider to function properly.
   InterfaceProvider();
+
+  // Constructs an InterfaceProvider which uses |interface_provider| to issue
+  // remote interface requests.
+  explicit InterfaceProvider(mojom::InterfaceProviderPtr interface_provider);
+
   ~InterfaceProvider();
 
+  // Closes the currently bound InterfaceProviderPtr for this object, allowing
+  // it to be rebound to a new InterfaceProviderPtr.
+  void Close();
+
   // Binds this InterfaceProvider to an actual mojom::InterfaceProvider pipe.
   // It is an error to call this on a forwarding InterfaceProvider, i.e. this
   // call is exclusive to Forward().
@@ -106,6 +126,8 @@
       const base::Callback<void(mojo::ScopedMessagePipeHandle)>& binder) {
     binders_[name] = binder;
   }
+  bool HasBinderForName(const std::string& name) const;
+  void ClearBinderForName(const std::string& name);
   void ClearBinders();
 
   using BinderMap = std::map<
diff --git a/testing/buildbot/chromium.perf.fyi.json b/testing/buildbot/chromium.perf.fyi.json
index 28fd9cd..5e961605 100644
--- a/testing/buildbot/chromium.perf.fyi.json
+++ b/testing/buildbot/chromium.perf.fyi.json
@@ -4931,124 +4931,6 @@
       },
       {
         "args": [
-          "v8.runtimestats.browsing_desktop",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=android-chromium"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build249-m4--device7",
-              "os": "Android",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "v8.runtimestats.browsing_desktop",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build249-m4--device7",
-              "os": "Android",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "v8.runtimestats.browsing_desktop_classic",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=android-chromium"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop_classic",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build249-m4--device7",
-              "os": "Android",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "v8.runtimestats.browsing_desktop_classic",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop_classic.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build249-m4--device7",
-              "os": "Android",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "v8.runtimestats.browsing_mobile",
           "-v",
           "--upload-results",
diff --git a/testing/buildbot/chromium.perf.json b/testing/buildbot/chromium.perf.json
index b324ac73..5347294 100644
--- a/testing/buildbot/chromium.perf.json
+++ b/testing/buildbot/chromium.perf.json
@@ -4996,124 +4996,6 @@
       },
       {
         "args": [
-          "v8.runtimestats.browsing_desktop",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=android-chromium"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build48-b1--device7",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "v8.runtimestats.browsing_desktop",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build48-b1--device7",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "v8.runtimestats.browsing_desktop_classic",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=android-chromium"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop_classic",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build48-b1--device7",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "v8.runtimestats.browsing_desktop_classic",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop_classic.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build48-b1--device7",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "v8.runtimestats.browsing_mobile",
           "-v",
           "--upload-results",
@@ -10261,124 +10143,6 @@
       },
       {
         "args": [
-          "v8.runtimestats.browsing_desktop",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=android-chromium"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build75-b1--device7",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "v8.runtimestats.browsing_desktop",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build75-b1--device7",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "v8.runtimestats.browsing_desktop_classic",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=android-chromium"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop_classic",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build75-b1--device7",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "v8.runtimestats.browsing_desktop_classic",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop_classic.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build75-b1--device7",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "v8.runtimestats.browsing_mobile",
           "-v",
           "--upload-results",
@@ -15546,124 +15310,6 @@
       },
       {
         "args": [
-          "v8.runtimestats.browsing_desktop",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=android-chromium"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build45-b1--device7",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "v8.runtimestats.browsing_desktop",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build45-b1--device7",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "v8.runtimestats.browsing_desktop_classic",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=android-chromium"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop_classic",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build45-b1--device7",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "v8.runtimestats.browsing_desktop_classic",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop_classic.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build45-b1--device7",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "v8.runtimestats.browsing_mobile",
           "-v",
           "--upload-results",
@@ -20831,124 +20477,6 @@
       },
       {
         "args": [
-          "v8.runtimestats.browsing_desktop",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=android-chromium"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build49-b1--device7",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "v8.runtimestats.browsing_desktop",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build49-b1--device7",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "v8.runtimestats.browsing_desktop_classic",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=android-chromium"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop_classic",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build49-b1--device7",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "v8.runtimestats.browsing_desktop_classic",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop_classic.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build49-b1--device7",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "v8.runtimestats.browsing_mobile",
           "-v",
           "--upload-results",
@@ -26116,124 +25644,6 @@
       },
       {
         "args": [
-          "v8.runtimestats.browsing_desktop",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=android-chromium"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build47-b1--device7",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "v8.runtimestats.browsing_desktop",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build47-b1--device7",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "v8.runtimestats.browsing_desktop_classic",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=android-chromium"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop_classic",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build47-b1--device7",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "v8.runtimestats.browsing_desktop_classic",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "v8.runtimestats.browsing_desktop_classic.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "android_devices": "1",
-              "id": "build47-b1--device7",
-              "os": "Android",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 10800,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "v8.runtimestats.browsing_mobile",
           "-v",
           "--upload-results",
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG b/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
index 9271457d..6df6959 100644
--- a/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
+++ b/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
@@ -22601,7 +22601,6 @@
 crbug.com/591099 virtual/display_list_2d_canvas/fast/canvas/webgl/webgl-texture-binding-preserved.html [ Failure ]
 crbug.com/591099 virtual/display_list_2d_canvas/fast/canvas/webgl/webgl-viewport-parameters-preserved.html [ Failure ]
 crbug.com/591099 virtual/display_list_2d_canvas/fast/canvas/zero-size-fill-rect.html [ Crash ]
-crbug.com/591099 virtual/enable_wasm/external/wpt/wasm/wasm_local_iframe_test.html [ Crash Timeout ]
 crbug.com/591099 virtual/gpu/fast/canvas/2d.composite.globalAlpha.fillPath.html [ Crash ]
 crbug.com/591099 virtual/gpu/fast/canvas/2d.fillText.gradient.html [ Crash ]
 crbug.com/591099 virtual/gpu/fast/canvas/2d.text.draw.fill.maxWidth.gradient.html [ Crash ]
diff --git a/third_party/WebKit/LayoutTests/NeverFixTests b/third_party/WebKit/LayoutTests/NeverFixTests
index ba6e67c..7fee3d01 100644
--- a/third_party/WebKit/LayoutTests/NeverFixTests
+++ b/third_party/WebKit/LayoutTests/NeverFixTests
@@ -20,7 +20,8 @@
 # same.  Because the whole point is that this test should fail when run, it's
 # not SKIP, just WONTFIX.
 fast/harness/sample-fail-mismatch-reftest.html [ WontFix ]
-
+# Not intended to be run as a test.
+fast/harness/test-expectations.html [ WontFix ]
 # Platform specific virtual test suites.
 [ Win Mac Android ] virtual/linux-subpixel [ WontFix ]
 
@@ -213,7 +214,6 @@
 
 # wasm tests. Currently under virtual/enable_wasm or virtual/enable_wasm_streaming
 crbug.com/642912 http/tests/wasm/ [ WontFix ]
-crbug.com/642912 external/wpt/wasm/ [ WontFix ]
 crbug.com/642912 virtual/mojo-loading/http/tests/wasm/ [ WontFix ]
 crbug.com/712970 http/tests/wasm_streaming/ [ WontFix ]
 crbug.com/712970 virtual/mojo-loading/http/tests/wasm_streaming/ [ WontFix ]
diff --git a/third_party/WebKit/LayoutTests/SmokeTests b/third_party/WebKit/LayoutTests/SmokeTests
index cef78f3..4f186b9 100644
--- a/third_party/WebKit/LayoutTests/SmokeTests
+++ b/third_party/WebKit/LayoutTests/SmokeTests
@@ -19,7 +19,6 @@
 bluetooth/requestDevice/canonicalizeFilter/no-arguments.html
 bluetooth/requestDevice/chooser/device-removed.html
 bluetooth/requestDevice/name-missing-device-from-name-prefix-filter.html
-bluetooth/server/device-same-object.html
 bluetooth/server/disconnect/disconnect-fires-event.html
 bluetooth/server/getPrimaryServices/delayed-discovery-no-permission-present-service-with-uuid.html
 bluetooth/server/getPrimaryServices/device-goes-out-of-range.html
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index 74b6daf..1b9b127 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -2120,6 +2120,11 @@
 crbug.com/724251 virtual/threaded/animations/svg-attribute-interpolation/svg-startOffset-interpolation.html [ Failure Pass ]
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 external/wpt/payment-request/payment-request-show-method.https.html [ Pass Failure ]
+crbug.com/626703 external/wpt/webrtc/RTCPeerConnection-createOffer.html [ Timeout ]
+crbug.com/626703 external/wpt/webrtc/RTCPeerConnection-onnegotiationneeded.html [ Timeout ]
+crbug.com/626703 external/wpt/webrtc/RTCPeerConnection-setLocalDescription.html [ Timeout ]
+crbug.com/626703 external/wpt/webrtc/RTCPeerConnection-setRemoteDescription.html [ Timeout ]
 crbug.com/626703 external/wpt/2dcontext/building-paths/canvas_complexshapes_arcto_001.htm [ Failure ]
 crbug.com/626703 external/wpt/2dcontext/building-paths/canvas_complexshapes_beziercurveto_001.htm [ Failure ]
 crbug.com/626703 external/wpt/2dcontext/drawing-images-to-the-canvas/drawimage_canvas_11.html [ Failure ]
@@ -2673,21 +2678,11 @@
 crbug.com/576815 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/selectors4/dir-style-03b.html [ Failure ]
 crbug.com/576815 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/selectors4/dir-style-04.html [ Failure ]
 
-# Producing incorrect results due to crbug.com/729352
-# TODO(ccameron): Re-enable and re-baseline when fixed.
-crbug.com/729352 images/color-profile-background-image-cross-fade.html [ Pass Failure ]
-crbug.com/729352 images/color-profile-background-image-space.html [ Pass Failure ]
-crbug.com/729352 images/color-profile-group.html [ Pass Failure ]
-crbug.com/729352 images/color-profile-layer-filter.html [ Pass Failure ]
-crbug.com/729352 images/color-profile-svg.html [ Pass Failure ]
-crbug.com/729352 images/color-profile-svg-fill-text.html [ Pass Failure ]
-crbug.com/729352 virtual/gpu-rasterization/images/color-profile-background-image-space.html [ Pass Failure ]
-crbug.com/729352 virtual/gpu-rasterization/images/color-profile-group.html [ Pass Failure ]
-crbug.com/729352 virtual/gpu-rasterization/images/color-profile-layer-filter.html [ Pass Failure ]
-crbug.com/729352 virtual/gpu-rasterization/images/color-profile-svg.html [ Pass Failure ]
-crbug.com/729352 virtual/gpu-rasterization/images/color-profile-svg-fill-text.html [ Pass Failure ]
-crbug.com/729352 virtual/gpu-rasterization/images/color-profile-clip.html [ Pass Failure Timeout ]
-crbug.com/729352 virtual/gpu-rasterization/images/color-profile-layer.html [ Pass Failure Timeout ]
+# Flakily timing out.
+# TODO(ccameron): Investigate this.
+crbug.com/730267 virtual/gpu-rasterization/images/color-profile-group.html [ Pass Timeout ]
+crbug.com/730267 virtual/gpu-rasterization/images/color-profile-layer.html [ Pass Timeout  ]
+crbug.com/730267 virtual/gpu-rasterization/images/color-profile-layer-filter.html [ Pass Timeout ]
 
 # TODO(chrishall): this is a temporary mediation step as part of the P0 issue crbug.com/657646
 # this is not meant to be here for more than a few days (from 2016-11-03 SYD)
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/server/invalid-service-name.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/server/invalid-service-name.js
index fec4374..32544f0 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/server/invalid-service-name.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/server/invalid-service-name.js
@@ -1,12 +1,9 @@
 'use strict';
 promise_test(() => {
-  return setBluetoothFakeAdapter('HeartRateAdapter')
-    .then(() => requestDeviceWithKeyDown({
-      filters: [{services: ['heart_rate']}]}))
-    .then(device => device.gatt.connect())
-    .then(gatt => {
+  return getHealthThermometerDevice()
+    .then(([device]) => {
       return assert_promise_rejects_with_message(
-        gatt.CALLS([
+        device.gatt.CALLS([
           getPrimaryService('wrong_name')|
           getPrimaryServices('wrong_name')
         ]),
diff --git a/third_party/WebKit/LayoutTests/bluetooth/script-tests/server/no-permission-for-any-service.js b/third_party/WebKit/LayoutTests/bluetooth/script-tests/server/no-permission-for-any-service.js
index 83580da7..e2e95c2e 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/script-tests/server/no-permission-for-any-service.js
+++ b/third_party/WebKit/LayoutTests/bluetooth/script-tests/server/no-permission-for-any-service.js
@@ -1,11 +1,8 @@
 'use strict';
 promise_test(() => {
-  return setBluetoothFakeAdapter('HeartRateAdapter')
-    .then(() => requestDeviceWithKeyDown({
-      filters: [{name: 'Heart Rate Device'}]}))
-    .then(device => device.gatt.connect())
-    .then(gattServer => assert_promise_rejects_with_message(
-        gattServer.CALLS([
+  return getHealthThermometerDevice({acceptAllDevices: true})
+    .then(([device]) => assert_promise_rejects_with_message(
+        device.gatt.CALLS([
           getPrimaryService('heart_rate')|
           getPrimaryServices()|
           getPrimaryServices('heart_rate')[UUID]]),
@@ -15,4 +12,3 @@
                          'SecurityError')));
 }, 'Request for present service without permission to access any service. ' +
    'Reject with SecurityError.');
-</script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/connect/connection-succeeds.html b/third_party/WebKit/LayoutTests/bluetooth/server/connect/connection-succeeds.html
index f0d82e1..a5fb002 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/connect/connection-succeeds.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/connect/connection-succeeds.html
@@ -2,13 +2,16 @@
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
 <script src="../../../resources/bluetooth/bluetooth-helpers.js"></script>
+<script src="../../../resources/bluetooth/web-bluetooth-test.js"></script>
+<script src="../../../resources/mojo-helpers.js"></script>
 <script>
 'use strict';
 promise_test(() => {
-  return setBluetoothFakeAdapter('HeartRateAdapter')
-    .then(() => requestDeviceWithKeyDown({
-      filters: [{services: ['heart_rate']}]}))
-    .then(device => device.gatt.connect())
-    .then(gattServer => assert_true(gattServer.connected));
+  return getDiscoveredHealthThermometerDevice()
+    .then(([device, fake_peripheral]) => {
+      return fake_peripheral.setNextGATTConnectionResponse({code: HCI_SUCCESS})
+        .then(() => device.gatt.connect())
+        .then(gatt => assert_true(gatt.connected));
+    });
 }, 'Device will connect');
 </script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/connect/garbage-collection-ran-during-success.html b/third_party/WebKit/LayoutTests/bluetooth/server/connect/garbage-collection-ran-during-success.html
index 00b5c0a..60156c1 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/connect/garbage-collection-ran-during-success.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/connect/garbage-collection-ran-during-success.html
@@ -2,16 +2,21 @@
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
 <script src="../../../resources/bluetooth/bluetooth-helpers.js"></script>
+<script src="../../../resources/bluetooth/web-bluetooth-test.js"></script>
+<script src="../../../resources/mojo-helpers.js"></script>
 <script>
 'use strict';
 promise_test(() => {
-  return setBluetoothFakeAdapter('DisconnectingHealthThermometerAdapter')
-    .then(() => requestDeviceWithKeyDown({
-      filters: [{services: ['health_thermometer']}]}))
-    .then(device => {
-      device.gatt.connect();
+  return getDiscoveredHealthThermometerDevice()
+    .then(([device, fake_peripheral]) => {
+      return fake_peripheral.setNextGATTConnectionResponse({code: HCI_SUCCESS})
+        .then(() => {
+          // Don't return the promise and let |device| go out of scope
+          // so that it gets garbage collected.
+          device.gatt.connect();
+        });
     })
-    .then(runGarbageCollection);
+    .then(runGarbageCollection)
 }, 'Garbage Collection ran during a connect call that succeeds. ' +
    'Should not crash.');
 </script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/connect/get-same-gatt-server.html b/third_party/WebKit/LayoutTests/bluetooth/server/connect/get-same-gatt-server.html
index 1c7774f..d6d1a42 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/connect/get-same-gatt-server.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/connect/get-same-gatt-server.html
@@ -2,16 +2,24 @@
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
 <script src="../../../resources/bluetooth/bluetooth-helpers.js"></script>
+<script src="../../../resources/bluetooth/web-bluetooth-test.js"></script>
+<script src="../../../resources/mojo-helpers.js"></script>
 <script>
 'use strict';
 promise_test(() => {
-  return setBluetoothFakeAdapter('HeartRateAdapter')
-    .then(() => requestDeviceWithKeyDown({
-      filters: [{services: ['heart_rate']}]}))
-    .then(device => {
-      return Promise.all([device.gatt.connect(), device.gatt.connect()])
-    }).then(gattServers => {
-      assert_equals(gattServers[0], gattServers[1]);
-    });
+  return getDiscoveredHealthThermometerDevice()
+    .then(([device, fake_peripheral]) => {
+      return fake_peripheral
+        .setNextGATTConnectionResponse({code: HCI_SUCCESS})
+        .then(() => device.gatt.connect())
+        .then(gatt1 => {
+          // No second response is necessary because an ATT Bearer
+          // already exists from the first connection.
+          // See https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-connect
+          // step 5.1.
+          return device.gatt.connect().then(gatt2 => [gatt1, gatt2]);
+        });
+    })
+    .then(([gatt1, gatt2]) => assert_equals(gatt1, gatt2));
 }, 'Multiple connects should return the same gatt object.');
 </script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/device-same-object.html b/third_party/WebKit/LayoutTests/bluetooth/server/device-same-object.html
index bbba6ddb..c6f391c 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/device-same-object.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/device-same-object.html
@@ -2,15 +2,18 @@
 <script src="../../resources/testharness.js"></script>
 <script src="../../resources/testharnessreport.js"></script>
 <script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
+<script src="../../resources/bluetooth/web-bluetooth-test.js"></script>
+<script src="../../resources/mojo-helpers.js"></script>
 <script>
 'use strict';
 promise_test(() => {
-  return setBluetoothFakeAdapter('HeartRateAdapter')
-    .then(() => requestDeviceWithKeyDown({
-      filters: [{services: ['heart_rate']}]}))
-    .then(device => device.gatt.connect())
-    .then(gattServer => {
-      assert_equals(gattServer.device, gattServer.device);
+  return getDiscoveredHealthThermometerDevice()
+    .then(([device, fake_peripheral]) => {
+      return fake_peripheral.setNextGATTConnectionResponse({code: HCI_SUCCESS})
+        .then(() => device.gatt.connect());
+    })
+    .then(gatt => {
+      assert_equals(gatt.device, gatt.device);
     });
 }, "[SameObject] test for BluetoothRemoteGATTServer's device.");
 </script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/gen-invalid-service-name.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/gen-invalid-service-name.html
index aaa69c8..591757d1 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/gen-invalid-service-name.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/gen-invalid-service-name.html
@@ -8,13 +8,10 @@
 <script>
 'use strict';
 promise_test(() => {
-  return setBluetoothFakeAdapter('HeartRateAdapter')
-    .then(() => requestDeviceWithKeyDown({
-      filters: [{services: ['heart_rate']}]}))
-    .then(device => device.gatt.connect())
-    .then(gatt => {
+  return getHealthThermometerDevice()
+    .then(([device]) => {
       return assert_promise_rejects_with_message(
-        gatt.getPrimaryService('wrong_name'),
+        device.gatt.getPrimaryService('wrong_name'),
         new DOMException(
           'Failed to execute \'getPrimaryService\' on ' +
           '\'BluetoothRemoteGATTServer\': Invalid Service name: ' +
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/gen-no-permission-for-any-service.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/gen-no-permission-for-any-service.html
index c04a98f..d02d25d 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/gen-no-permission-for-any-service.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryService/gen-no-permission-for-any-service.html
@@ -8,18 +8,14 @@
 <script>
 'use strict';
 promise_test(() => {
-  return setBluetoothFakeAdapter('HeartRateAdapter')
-    .then(() => requestDeviceWithKeyDown({
-      filters: [{name: 'Heart Rate Device'}]}))
-    .then(device => device.gatt.connect())
-    .then(gattServer => assert_promise_rejects_with_message(
-        gattServer.getPrimaryService('heart_rate'),
+  return getHealthThermometerDevice({acceptAllDevices: true})
+    .then(([device]) => assert_promise_rejects_with_message(
+        device.gatt.getPrimaryService('heart_rate'),
         new DOMException('Origin is not allowed to access any service. Tip: ' +
                          'Add the service UUID to \'optionalServices\' in ' +
                          'requestDevice() options. https://goo.gl/HxfxSQ',
                          'SecurityError')));
 }, 'Request for present service without permission to access any service. ' +
    'Reject with SecurityError.');
-</script>
 
 </script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-invalid-service-name.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-invalid-service-name.html
index 66c9a37..f8d010b 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-invalid-service-name.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-invalid-service-name.html
@@ -8,13 +8,10 @@
 <script>
 'use strict';
 promise_test(() => {
-  return setBluetoothFakeAdapter('HeartRateAdapter')
-    .then(() => requestDeviceWithKeyDown({
-      filters: [{services: ['heart_rate']}]}))
-    .then(device => device.gatt.connect())
-    .then(gatt => {
+  return getHealthThermometerDevice()
+    .then(([device]) => {
       return assert_promise_rejects_with_message(
-        gatt.getPrimaryServices('wrong_name'),
+        device.gatt.getPrimaryServices('wrong_name'),
         new DOMException(
           'Failed to execute \'getPrimaryServices\' on ' +
           '\'BluetoothRemoteGATTServer\': Invalid Service name: ' +
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service-with-uuid.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service-with-uuid.html
index 12b0ef9..c80c9831 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service-with-uuid.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service-with-uuid.html
@@ -8,18 +8,14 @@
 <script>
 'use strict';
 promise_test(() => {
-  return setBluetoothFakeAdapter('HeartRateAdapter')
-    .then(() => requestDeviceWithKeyDown({
-      filters: [{name: 'Heart Rate Device'}]}))
-    .then(device => device.gatt.connect())
-    .then(gattServer => assert_promise_rejects_with_message(
-        gattServer.getPrimaryServices('heart_rate'),
+  return getHealthThermometerDevice({acceptAllDevices: true})
+    .then(([device]) => assert_promise_rejects_with_message(
+        device.gatt.getPrimaryServices('heart_rate'),
         new DOMException('Origin is not allowed to access any service. Tip: ' +
                          'Add the service UUID to \'optionalServices\' in ' +
                          'requestDevice() options. https://goo.gl/HxfxSQ',
                          'SecurityError')));
 }, 'Request for present service without permission to access any service. ' +
    'Reject with SecurityError.');
-</script>
 
 </script>
diff --git a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service.html b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service.html
index 0b19a37..c771e477 100644
--- a/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service.html
+++ b/third_party/WebKit/LayoutTests/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service.html
@@ -8,18 +8,14 @@
 <script>
 'use strict';
 promise_test(() => {
-  return setBluetoothFakeAdapter('HeartRateAdapter')
-    .then(() => requestDeviceWithKeyDown({
-      filters: [{name: 'Heart Rate Device'}]}))
-    .then(device => device.gatt.connect())
-    .then(gattServer => assert_promise_rejects_with_message(
-        gattServer.getPrimaryServices(),
+  return getHealthThermometerDevice({acceptAllDevices: true})
+    .then(([device]) => assert_promise_rejects_with_message(
+        device.gatt.getPrimaryServices(),
         new DOMException('Origin is not allowed to access any service. Tip: ' +
                          'Add the service UUID to \'optionalServices\' in ' +
                          'requestDevice() options. https://goo.gl/HxfxSQ',
                          'SecurityError')));
 }, 'Request for present service without permission to access any service. ' +
    'Reject with SecurityError.');
-</script>
 
 </script>
diff --git a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json
index 2effa99f..11826a3f 100644
--- a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json
+++ b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json
@@ -2749,6 +2749,12 @@
      {}
     ]
    ],
+   "input-events/input-events-typing-data-manual.html": [
+    [
+     "/input-events/input-events-typing-data-manual.html",
+     {}
+    ]
+   ],
    "magnetometer/Magnetometer_onerror-manual.https.html": [
     [
      "/magnetometer/Magnetometer_onerror-manual.https.html",
@@ -3289,6 +3295,12 @@
      {}
     ]
    ],
+   "pointerevents/pointerevent_touch-action-rotated-divs_touch-manual.html": [
+    [
+     "/pointerevents/pointerevent_touch-action-rotated-divs_touch-manual.html",
+     {}
+    ]
+   ],
    "pointerevents/pointerevent_touch-action-span-test_touch-manual.html": [
     [
      "/pointerevents/pointerevent_touch-action-span-test_touch-manual.html",
@@ -4137,6 +4149,18 @@
      {}
     ]
    ],
+   "assumptions/ahem.html": [
+    [
+     "/assumptions/ahem.html",
+     [
+      [
+       "/assumptions/ahem-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "assumptions/canvas-background.html": [
     [
      "/assumptions/canvas-background.html",
@@ -63664,11 +63688,6 @@
      {}
     ]
    ],
-   "XMLHttpRequest/send-redirect-infinite-expected.txt": [
-    [
-     {}
-    ]
-   ],
    "XMLHttpRequest/send-response-event-order-expected.txt": [
     [
      {}
@@ -63719,6 +63738,11 @@
      {}
     ]
    ],
+   "assumptions/ahem-ref.html": [
+    [
+     {}
+    ]
+   ],
    "assumptions/canvas-background-ref.html": [
     [
      {}
@@ -63759,16 +63783,6 @@
      {}
     ]
    ],
-   "battery-status/battery-iframe.https-expected.txt": [
-    [
-     {}
-    ]
-   ],
-   "battery-status/battery-insecure-context-expected.txt": [
-    [
-     {}
-    ]
-   ],
    "battery-status/support-iframe.html": [
     [
      {}
@@ -79944,11 +79958,6 @@
      {}
     ]
    ],
-   "cssom/CSS-expected.txt": [
-    [
-     {}
-    ]
-   ],
    "cssom/CSSKeyframesRule-expected.txt": [
     [
      {}
@@ -80284,11 +80293,6 @@
      {}
     ]
    ],
-   "custom-elements/reactions/DOMTokenList-expected.txt": [
-    [
-     {}
-    ]
-   ],
    "custom-elements/reactions/HTMLElement-expected.txt": [
     [
      {}
@@ -80729,11 +80733,6 @@
      {}
     ]
    ],
-   "dom/nodes/Element-classlist-expected.txt": [
-    [
-     {}
-    ]
-   ],
    "dom/nodes/Element-closest-expected.txt": [
     [
      {}
@@ -81914,16 +81913,6 @@
      {}
     ]
    ],
-   "fetch/api/headers/header-values-normalize-expected.txt": [
-    [
-     {}
-    ]
-   ],
-   "fetch/api/headers/headers-normalize-expected.txt": [
-    [
-     {}
-    ]
-   ],
    "fetch/api/policies/csp-blocked-worker-expected.txt": [
     [
      {}
@@ -82384,6 +82373,11 @@
      {}
     ]
    ],
+   "fullscreen/api/document-exit-fullscreen-manual-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "fullscreen/api/document-exit-fullscreen-timing-manual-expected.txt": [
     [
      {}
@@ -82409,6 +82403,11 @@
      {}
     ]
    ],
+   "fullscreen/api/element-request-fullscreen-and-exit-iframe-manual-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "fullscreen/api/element-request-fullscreen-and-move-manual-expected.txt": [
     [
      {}
@@ -82429,6 +82428,16 @@
      {}
     ]
    ],
+   "fullscreen/api/element-request-fullscreen-manual-expected.txt": [
+    [
+     {}
+    ]
+   ],
+   "fullscreen/api/element-request-fullscreen-not-allowed-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "fullscreen/api/element-request-fullscreen-timing-manual-expected.txt": [
     [
      {}
@@ -82454,6 +82463,31 @@
      {}
     ]
    ],
+   "fullscreen/model/move-to-iframe-manual-expected.txt": [
+    [
+     {}
+    ]
+   ],
+   "fullscreen/model/remove-first-manual-expected.txt": [
+    [
+     {}
+    ]
+   ],
+   "fullscreen/model/remove-last-manual-expected.txt": [
+    [
+     {}
+    ]
+   ],
+   "fullscreen/model/remove-parent-manual-expected.txt": [
+    [
+     {}
+    ]
+   ],
+   "fullscreen/model/remove-single-manual-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "fullscreen/trusted-click.js": [
     [
      {}
@@ -91939,6 +91973,11 @@
      {}
     ]
    ],
+   "html/semantics/interactive-elements/the-menu-element/menuitem-label-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "html/semantics/interactive-elements/the-summary-element/.gitkeep": [
     [
      {}
@@ -94159,6 +94198,11 @@
      {}
     ]
    ],
+   "interfaces/mediacapture-main.idl": [
+    [
+     {}
+    ]
+   ],
    "interfaces/remoteplayback.idl": [
     [
      {}
@@ -94179,6 +94223,16 @@
      {}
     ]
    ],
+   "keyboard-lock/idlharness.https-expected.txt": [
+    [
+     {}
+    ]
+   ],
+   "keyboard-lock/navigator-requestKeyboardLock-two-parallel-requests.https-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "longtask-timing/resources/makelongtask.js": [
     [
      {}
@@ -99234,6 +99288,11 @@
      {}
     ]
    ],
+   "service-workers/service-worker/clients-matchall-order.https-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "service-workers/service-worker/controller-on-disconnect.https-expected.txt": [
     [
      {}
@@ -99274,6 +99333,11 @@
      {}
     ]
    ],
+   "service-workers/service-worker/fetch-request-xhr-sync.https-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "service-workers/service-worker/import-scripts-updated-flag.https-expected.txt": [
     [
      {}
@@ -99839,6 +99903,16 @@
      {}
     ]
    ],
+   "service-workers/service-worker/resources/fetch-request-xhr-sync-iframe.html": [
+    [
+     {}
+    ]
+   ],
+   "service-workers/service-worker/resources/fetch-request-xhr-sync-worker.js": [
+    [
+     {}
+    ]
+   ],
    "service-workers/service-worker/resources/fetch-request-xhr-worker.js": [
     [
      {}
@@ -101439,6 +101513,11 @@
      {}
     ]
    ],
+   "url/toascii.json": [
+    [
+     {}
+    ]
+   ],
    "url/url-tojson-expected.txt": [
     [
      {}
@@ -101994,6 +102073,11 @@
      {}
     ]
    ],
+   "webrtc/RTCPeerConnection-helper.js": [
+    [
+     {}
+    ]
+   ],
    "webrtc/RTCPeerConnection-iceGatheringState-expected.txt": [
     [
      {}
@@ -117181,6 +117265,12 @@
      {}
     ]
    ],
+   "css/css-position-3/position-sticky-get-bounding-client-rect.html": [
+    [
+     "/css/css-position-3/position-sticky-get-bounding-client-rect.html",
+     {}
+    ]
+   ],
    "css/css-rhythm-1/line-height-step-dynamic-001.html": [
     [
      "/css/css-rhythm-1/line-height-step-dynamic-001.html",
@@ -127547,6 +127637,18 @@
      {}
     ]
    ],
+   "html/rendering/non-replaced-elements/the-hr-element-0/hr.html": [
+    [
+     "/html/rendering/non-replaced-elements/the-hr-element-0/hr.html",
+     {}
+    ]
+   ],
+   "html/rendering/non-replaced-elements/the-hr-element-0/setting-overflow-visible.html": [
+    [
+     "/html/rendering/non-replaced-elements/the-hr-element-0/setting-overflow-visible.html",
+     {}
+    ]
+   ],
    "html/rendering/non-replaced-elements/the-page/iframe-body-margin-attributes.html": [
     [
      "/html/rendering/non-replaced-elements/the-page/iframe-body-margin-attributes.html",
@@ -132515,6 +132617,30 @@
      {}
     ]
    ],
+   "image-decodes/image-decode-iframe.html": [
+    [
+     "/image-decodes/image-decode-iframe.html",
+     {
+      "timeout": "long"
+     }
+    ]
+   ],
+   "image-decodes/image-decode-path-changes.html": [
+    [
+     "/image-decodes/image-decode-path-changes.html",
+     {
+      "timeout": "long"
+     }
+    ]
+   ],
+   "image-decodes/image-decode.html": [
+    [
+     "/image-decodes/image-decode.html",
+     {
+      "timeout": "long"
+     }
+    ]
+   ],
    "imagebitmap-renderingcontext/bitmaprenderer-as-imagesource.html": [
     [
      "/imagebitmap-renderingcontext/bitmaprenderer-as-imagesource.html",
@@ -132575,6 +132701,36 @@
      {}
     ]
    ],
+   "keyboard-lock/idlharness.https.html": [
+    [
+     "/keyboard-lock/idlharness.https.html",
+     {}
+    ]
+   ],
+   "keyboard-lock/navigator-cancelKeyboardLock.https.html": [
+    [
+     "/keyboard-lock/navigator-cancelKeyboardLock.https.html",
+     {}
+    ]
+   ],
+   "keyboard-lock/navigator-requestKeyboardLock-two-parallel-requests.https.html": [
+    [
+     "/keyboard-lock/navigator-requestKeyboardLock-two-parallel-requests.https.html",
+     {}
+    ]
+   ],
+   "keyboard-lock/navigator-requestKeyboardLock-two-sequential-requests.https.html": [
+    [
+     "/keyboard-lock/navigator-requestKeyboardLock-two-sequential-requests.https.html",
+     {}
+    ]
+   ],
+   "keyboard-lock/navigator-requestKeyboardLock.https.html": [
+    [
+     "/keyboard-lock/navigator-requestKeyboardLock.https.html",
+     {}
+    ]
+   ],
    "longtask-timing/longtask-attributes.html": [
     [
      "/longtask-timing/longtask-attributes.html",
@@ -133079,6 +133235,18 @@
      {}
     ]
    ],
+   "mediacapture-streams/MediaDevices-IDL-all.html": [
+    [
+     "/mediacapture-streams/MediaDevices-IDL-all.html",
+     {}
+    ]
+   ],
+   "mediacapture-streams/MediaDevices-IDL-enumerateDevices.html": [
+    [
+     "/mediacapture-streams/MediaDevices-IDL-enumerateDevices.html",
+     {}
+    ]
+   ],
    "mediacapture-streams/MediaDevices-enumerateDevices.https.html": [
     [
      "/mediacapture-streams/MediaDevices-enumerateDevices.https.html",
@@ -144017,6 +144185,18 @@
      {}
     ]
    ],
+   "payment-request/payment-request-abort-method.https.html": [
+    [
+     "/payment-request/payment-request-abort-method.https.html",
+     {}
+    ]
+   ],
+   "payment-request/payment-request-canmakepayment-method.html": [
+    [
+     "/payment-request/payment-request-canmakepayment-method.html",
+     {}
+    ]
+   ],
    "payment-request/payment-request-constructor.https.html": [
     [
      "/payment-request/payment-request-constructor.https.html",
@@ -144241,6 +144421,12 @@
      {}
     ]
    ],
+   "preload/reflected-as-value.html": [
+    [
+     "/preload/reflected-as-value.html",
+     {}
+    ]
+   ],
    "preload/single-download-late-used-preload.html": [
     [
      "/preload/single-download-late-used-preload.html",
@@ -152601,12 +152787,48 @@
      {}
     ]
    ],
+   "scroll-anchoring/abspos-containing-block-outside-scroller.html": [
+    [
+     "/scroll-anchoring/abspos-containing-block-outside-scroller.html",
+     {}
+    ]
+   ],
+   "scroll-anchoring/abspos-contributes-to-static-parent-bounds.html": [
+    [
+     "/scroll-anchoring/abspos-contributes-to-static-parent-bounds.html",
+     {}
+    ]
+   ],
+   "scroll-anchoring/ancestor-change-heuristic.html": [
+    [
+     "/scroll-anchoring/ancestor-change-heuristic.html",
+     {}
+    ]
+   ],
+   "scroll-anchoring/anchor-updates-after-explicit-scroll.html": [
+    [
+     "/scroll-anchoring/anchor-updates-after-explicit-scroll.html",
+     {}
+    ]
+   ],
+   "scroll-anchoring/anchoring-with-bounds-clamping-div.html": [
+    [
+     "/scroll-anchoring/anchoring-with-bounds-clamping-div.html",
+     {}
+    ]
+   ],
    "scroll-anchoring/anchoring-with-bounds-clamping.html": [
     [
      "/scroll-anchoring/anchoring-with-bounds-clamping.html",
      {}
     ]
    ],
+   "scroll-anchoring/anonymous-block-box.html": [
+    [
+     "/scroll-anchoring/anonymous-block-box.html",
+     {}
+    ]
+   ],
    "scroll-anchoring/basic.html": [
     [
      "/scroll-anchoring/basic.html",
@@ -152619,6 +152841,66 @@
      {}
     ]
    ],
+   "scroll-anchoring/descend-into-container-with-float.html": [
+    [
+     "/scroll-anchoring/descend-into-container-with-float.html",
+     {}
+    ]
+   ],
+   "scroll-anchoring/descend-into-container-with-overflow.html": [
+    [
+     "/scroll-anchoring/descend-into-container-with-overflow.html",
+     {}
+    ]
+   ],
+   "scroll-anchoring/exclude-fixed-position.html": [
+    [
+     "/scroll-anchoring/exclude-fixed-position.html",
+     {}
+    ]
+   ],
+   "scroll-anchoring/inline-block.html": [
+    [
+     "/scroll-anchoring/inline-block.html",
+     {}
+    ]
+   ],
+   "scroll-anchoring/negative-layout-overflow.html": [
+    [
+     "/scroll-anchoring/negative-layout-overflow.html",
+     {}
+    ]
+   ],
+   "scroll-anchoring/opt-out.html": [
+    [
+     "/scroll-anchoring/opt-out.html",
+     {}
+    ]
+   ],
+   "scroll-anchoring/position-change-heuristic.html": [
+    [
+     "/scroll-anchoring/position-change-heuristic.html",
+     {}
+    ]
+   ],
+   "scroll-anchoring/start-edge-in-block-layout-direction.html": [
+    [
+     "/scroll-anchoring/start-edge-in-block-layout-direction.html",
+     {}
+    ]
+   ],
+   "scroll-anchoring/subtree-exclusion.html": [
+    [
+     "/scroll-anchoring/subtree-exclusion.html",
+     {}
+    ]
+   ],
+   "scroll-anchoring/wrapped-text.html": [
+    [
+     "/scroll-anchoring/wrapped-text.html",
+     {}
+    ]
+   ],
    "secure-contexts/basic-dedicated-worker.html": [
     [
      "/secure-contexts/basic-dedicated-worker.html",
@@ -153561,6 +153843,12 @@
      {}
     ]
    ],
+   "service-workers/service-worker/fetch-request-xhr-sync.https.html": [
+    [
+     "/service-workers/service-worker/fetch-request-xhr-sync.https.html",
+     {}
+    ]
+   ],
    "service-workers/service-worker/fetch-request-xhr.https.html": [
     [
      "/service-workers/service-worker/fetch-request-xhr.https.html",
@@ -155797,6 +156085,12 @@
      {}
     ]
    ],
+   "url/toascii.window.js": [
+    [
+     "/url/toascii.window.html",
+     {}
+    ]
+   ],
    "url/url-constructor.html": [
     [
      "/url/url-constructor.html",
@@ -157211,6 +157505,12 @@
      {}
     ]
    ],
+   "webrtc/RTCCertificate.html": [
+    [
+     "/webrtc/RTCCertificate.html",
+     {}
+    ]
+   ],
    "webrtc/RTCConfiguration-iceCandidatePoolSize.html": [
     [
      "/webrtc/RTCConfiguration-iceCandidatePoolSize.html",
@@ -157241,6 +157541,12 @@
      {}
     ]
    ],
+   "webrtc/RTCPeerConnection-addTransceiver.html": [
+    [
+     "/webrtc/RTCPeerConnection-addTransceiver.html",
+     {}
+    ]
+   ],
    "webrtc/RTCPeerConnection-canTrickleIceCandidates.html": [
     [
      "/webrtc/RTCPeerConnection-canTrickleIceCandidates.html",
@@ -157253,12 +157559,36 @@
      {}
     ]
    ],
+   "webrtc/RTCPeerConnection-createAnswer.html": [
+    [
+     "/webrtc/RTCPeerConnection-createAnswer.html",
+     {}
+    ]
+   ],
    "webrtc/RTCPeerConnection-createDataChannel.html": [
     [
      "/webrtc/RTCPeerConnection-createDataChannel.html",
      {}
     ]
    ],
+   "webrtc/RTCPeerConnection-createOffer.html": [
+    [
+     "/webrtc/RTCPeerConnection-createOffer.html",
+     {}
+    ]
+   ],
+   "webrtc/RTCPeerConnection-generateCertificate.html": [
+    [
+     "/webrtc/RTCPeerConnection-generateCertificate.html",
+     {}
+    ]
+   ],
+   "webrtc/RTCPeerConnection-getTransceivers.html": [
+    [
+     "/webrtc/RTCPeerConnection-getTransceivers.html",
+     {}
+    ]
+   ],
    "webrtc/RTCPeerConnection-iceGatheringState.html": [
     [
      "/webrtc/RTCPeerConnection-iceGatheringState.html",
@@ -157271,6 +157601,18 @@
      {}
     ]
    ],
+   "webrtc/RTCPeerConnection-onnegotiationneeded.html": [
+    [
+     "/webrtc/RTCPeerConnection-onnegotiationneeded.html",
+     {}
+    ]
+   ],
+   "webrtc/RTCPeerConnection-setLocalDescription.html": [
+    [
+     "/webrtc/RTCPeerConnection-setLocalDescription.html",
+     {}
+    ]
+   ],
    "webrtc/RTCPeerConnection-setRemoteDescription.html": [
     [
      "/webrtc/RTCPeerConnection-setRemoteDescription.html",
@@ -162637,7 +162979,7 @@
    "support"
   ],
   "./check_stability.py": [
-   "04e811b4bccb5916cf17414befefe5f2d565a677",
+   "164119b28e9dee2e29eff569dc3a59ec94a04a59",
    "support"
   ],
   "./ci_built_diff.sh": [
@@ -162661,7 +163003,7 @@
    "support"
   ],
   "./update-built-tests.sh": [
-   "75ea35a5ce9d8e3e32e8d0c336dc12e04691d16a",
+   "99b5beb84b30521fa4c4a8a061acc309ee3d0d4a",
    "support"
   ],
   "./wptrun": [
@@ -169985,7 +170327,7 @@
    "support"
   ],
   "XMLHttpRequest/resources/infinite-redirects.py": [
-   "0b73a37de1d20fb329bc60588b5e00f2adc48e85",
+   "bd033f0de21dc68ed1d4303fee49e64dd0916722",
    "support"
   ],
   "XMLHttpRequest/resources/init.htm": [
@@ -170412,10 +170754,6 @@
    "02ba73594e49226355aea8df228f49a57ed4a93c",
    "testharness"
   ],
-  "XMLHttpRequest/send-redirect-infinite-expected.txt": [
-   "9642536821dec4ceb2f3c9de028e105d48189361",
-   "support"
-  ],
   "XMLHttpRequest/send-redirect-infinite-sync.htm": [
    "63bcb776518d71f4fdc66441a411fcd989137d5e",
    "testharness"
@@ -170720,6 +171058,14 @@
    "b9ba0287c92e5dbda1dc207ab45e9c90e8618878",
    "reftest"
   ],
+  "assumptions/ahem-ref.html": [
+   "f38cdd07d07558540e19c2b2ec063dbc54f8be7f",
+   "support"
+  ],
+  "assumptions/ahem.html": [
+   "e097b6e6eb9ecf107cea94b3984661cc62c7ac67",
+   "reftest"
+  ],
   "assumptions/canvas-background-ref.html": [
    "0868a5443b1aacb8fd95327bc7c71d071158b0f1",
    "support"
@@ -170796,18 +171142,10 @@
    "0fb90d807a27ce9e78dab040926effc1be7f71be",
    "manual"
   ],
-  "battery-status/battery-iframe.https-expected.txt": [
-   "eb6453b6d1c328ffc20afdbb96fe5d7602a7c1d1",
-   "support"
-  ],
   "battery-status/battery-iframe.https.html": [
    "b4df9429c1ff9ace253fb40296b20c06f8eb5eb3",
    "testharness"
   ],
-  "battery-status/battery-insecure-context-expected.txt": [
-   "6be1447d61155d2656c958a00674ea311031b8eb",
-   "support"
-  ],
   "battery-status/battery-insecure-context.html": [
    "39639373d161846186cbcb7eb33466493bcc77ad",
    "testharness"
@@ -172401,7 +172739,7 @@
    "testharness"
   ],
   "content-security-policy/embedded-enforcement/required_csp-header.html": [
-   "ea1ce769c6f529ac0f0f288634c9fe26d99dcbed",
+   "8813b5d09c78cc61f883c45f28ed228d34541205",
    "testharness"
   ],
   "content-security-policy/embedded-enforcement/subsumption_algorithm-general.html": [
@@ -173925,7 +174263,7 @@
    "testharness"
   ],
   "css-typed-om/styleMap-update-function-expected.txt": [
-   "0721805eb1cbd501f0b19aa377b0f40c426e1b28",
+   "bbcf689bd0b536339ec537f888d3e91bb8ec09ff",
    "support"
   ],
   "css-typed-om/styleMap-update-function.html": [
@@ -188868,6 +189206,10 @@
    "2a908e60a635dbf765987c0f93d0f33c8ea85de6",
    "reftest"
   ],
+  "css/css-position-3/position-sticky-get-bounding-client-rect.html": [
+   "77da4ac9e0eea6433c4fa890cd4a2151f46c35a3",
+   "testharness"
+  ],
   "css/css-position-3/position-sticky-left-ref.html": [
    "9de7a8ba6019395d729b32e514cc3bd9fee25d2b",
    "support"
@@ -206592,10 +206934,6 @@
    "a3529271f75eecb40da8ec1975202d8aa07b5d4a",
    "testharness"
   ],
-  "cssom/CSS-expected.txt": [
-   "3e1d59c3328a78285d77a883f10e00f2905ac861",
-   "support"
-  ],
   "cssom/CSS.html": [
    "2c55d573bbd90f5ca3e564131ae697b547e4a43c",
    "testharness"
@@ -207164,10 +207502,6 @@
    "ad0b3fd9ec6ac895c2abd6685e23bc92b60f0733",
    "testharness"
   ],
-  "custom-elements/reactions/DOMTokenList-expected.txt": [
-   "f4b61b6ed54590d0e65611c03fe9061a2fedbe8d",
-   "support"
-  ],
   "custom-elements/reactions/DOMTokenList.html": [
    "7482fa56e7f4d95cfe8b8f337a9b5e63a83a6e9d",
    "testharness"
@@ -207189,7 +207523,7 @@
    "testharness"
   ],
   "custom-elements/reactions/HTMLElement-expected.txt": [
-   "f30c19c462c4b322553f38401106b2f70442e1c7",
+   "fd027b233d354d933eb260def242be36ea6bccf8",
    "support"
   ],
   "custom-elements/reactions/HTMLElement.html": [
@@ -207537,7 +207871,7 @@
    "testharness"
   ],
   "dom/interfaces-expected.txt": [
-   "ee2b93473877f87d12dd9649452df2ed89706fab",
+   "133f60942eae89418f507d7cea7cee10d3f0e4b0",
    "support"
   ],
   "dom/interfaces.html": [
@@ -208132,10 +208466,6 @@
    "e3fe31ea198922fe64fbf985ae99d1cd4512567a",
    "testharness"
   ],
-  "dom/nodes/Element-classlist-expected.txt": [
-   "3e610f97449595d17e58504e9dc2f9733562aa90",
-   "support"
-  ],
   "dom/nodes/Element-classlist.html": [
    "c197df35960b77a7794eed10a1a927867a6658f4",
    "testharness"
@@ -208469,11 +208799,11 @@
    "testharness"
   ],
   "dom/nodes/ParentNode-querySelector-All-content.html": [
-   "2fe29f94771df7368eb02888aeff862197d6131f",
+   "6f8c08e2c6dd453f07040f6e0dfaac2496d83bfa",
    "support"
   ],
   "dom/nodes/ParentNode-querySelector-All-content.xht": [
-   "42dd306a4c1466ba22464c959d4d8bbb83f1a95a",
+   "05af5651daed7b7f01e5413c7f8f95b0a4673bf2",
    "support"
   ],
   "dom/nodes/ParentNode-querySelector-All-expected.txt": [
@@ -208493,7 +208823,7 @@
    "testharness"
   ],
   "dom/nodes/ParentNode-querySelector-All.js": [
-   "9466cb7dd4c3400f4a9dc683db1a729a5988c993",
+   "2bb7218b5c57e98cf7e92c2db5c8b1f61f86198e",
    "support"
   ],
   "dom/nodes/ProcessingInstruction-escapes-1.xhtml": [
@@ -208701,7 +209031,7 @@
    "testharness"
   ],
   "dom/nodes/selectors.js": [
-   "da3b97296a9fec2838a2cf1f5506893416627802",
+   "bca6d8c3b9ffa845cce8568cd6b8730d01a84f25",
    "support"
   ],
   "dom/ranges/Range-attributes.html": [
@@ -210856,10 +211186,6 @@
    "9c542539ac4bf0654d56bb86770810f2efcce53a",
    "testharness"
   ],
-  "fetch/api/headers/header-values-normalize-expected.txt": [
-   "f1013306349ed7ecb4f513e729d1df7e63e01276",
-   "support"
-  ],
   "fetch/api/headers/header-values-normalize.html": [
    "889c328c2677763c352ec9aa0264218e01a523d4",
    "testharness"
@@ -210888,10 +211214,6 @@
    "42dc60a28c86ef378a8aa614f411f13bae0a081a",
    "testharness"
   ],
-  "fetch/api/headers/headers-normalize-expected.txt": [
-   "aefd54a621a4f7ef5ed5221d6663909bddb144a0",
-   "support"
-  ],
   "fetch/api/headers/headers-normalize.html": [
    "6ccdfba06f9a8692029d72ef81aee16a1996ca01",
    "testharness"
@@ -211373,7 +211695,7 @@
    "testharness"
   ],
   "fetch/api/response/response-clone-expected.txt": [
-   "fcde0384d772850a5931b5a19fd34662f1ba70b0",
+   "bde075808be5034bf1abee675645621ddbb5385e",
    "support"
   ],
   "fetch/api/response/response-clone.html": [
@@ -211612,6 +211934,10 @@
    "b4010cb68f5fa7f97e8d405e67977335e6a00795",
    "testharness"
   ],
+  "fullscreen/api/document-exit-fullscreen-manual-expected.txt": [
+   "c167856f71136c06d7733118f4def0c248989394",
+   "support"
+  ],
   "fullscreen/api/document-exit-fullscreen-manual.html": [
    "398a52fc8728e07771249c017baf0c1867c4ea44",
    "manual"
@@ -211700,6 +212026,10 @@
    "f8c6c1a63b5738a442bcf01c09535d4bb48512a7",
    "testharness"
   ],
+  "fullscreen/api/element-request-fullscreen-and-exit-iframe-manual-expected.txt": [
+   "a473d95d2e72c45121680fc47ced9dcba2d73145",
+   "support"
+  ],
   "fullscreen/api/element-request-fullscreen-and-exit-iframe-manual.html": [
    "870575cb59c5a7f76097e19da8b3854120d6cb86",
    "manual"
@@ -211736,6 +212066,10 @@
    "86c1ac20aa86e860cfa8f05a9873f3a3cddbdcd9",
    "manual"
   ],
+  "fullscreen/api/element-request-fullscreen-manual-expected.txt": [
+   "b14caf034cd09dc2d84c76042c6ce66c3b85d8e2",
+   "support"
+  ],
   "fullscreen/api/element-request-fullscreen-manual.html": [
    "ed7683b3c4a7134b640e07a7329a21361b122402",
    "manual"
@@ -211744,6 +212078,10 @@
    "c346255bb659b952d8d27b2ab136e7815c1161d9",
    "manual"
   ],
+  "fullscreen/api/element-request-fullscreen-not-allowed-expected.txt": [
+   "c0be9280569576bb9e14a1e339bb90e335892c2b",
+   "support"
+  ],
   "fullscreen/api/element-request-fullscreen-not-allowed.html": [
    "8991e8df530fa7c24a9e084f2ab17fa9c70fb120",
    "testharness"
@@ -211801,13 +212139,17 @@
    "testharness"
   ],
   "fullscreen/interfaces-expected.txt": [
-   "ad9dbeb1ee339c858138e3bab0827eca418de781",
+   "d22cb0ab5f83fd919fcce3e334ca6916405c9945",
    "support"
   ],
   "fullscreen/interfaces.html": [
    "f6f0dbc8a505896a0e7ec7aca2746bbd5c1eb7d9",
    "testharness"
   ],
+  "fullscreen/model/move-to-iframe-manual-expected.txt": [
+   "e7019c27330910de32ea2a7f0283377e6dd1cb75",
+   "support"
+  ],
   "fullscreen/model/move-to-iframe-manual.html": [
    "818cb1b5db729db4959591dc75d4bb1ae3c7542d",
    "manual"
@@ -211816,18 +212158,34 @@
    "b1142930c6c972057213bd477cf116fcc9e7fc2a",
    "manual"
   ],
+  "fullscreen/model/remove-first-manual-expected.txt": [
+   "ef976589b9898457661d8304518a5013776b8681",
+   "support"
+  ],
   "fullscreen/model/remove-first-manual.html": [
    "3de98ae96822370fa80c1b8d61df254910a63ff9",
    "manual"
   ],
+  "fullscreen/model/remove-last-manual-expected.txt": [
+   "6813bede8236c163823407e7abbba719bed2c177",
+   "support"
+  ],
   "fullscreen/model/remove-last-manual.html": [
    "8caa21a892edeaba9996a7f2bf1c670385e0a91b",
    "manual"
   ],
+  "fullscreen/model/remove-parent-manual-expected.txt": [
+   "0e4526d0b0bd833feed4ddedd2c35ae7d6a904f8",
+   "support"
+  ],
   "fullscreen/model/remove-parent-manual.html": [
    "e5791db04ab5e2b75a00c922457fcc8ba87c7ce7",
    "manual"
   ],
+  "fullscreen/model/remove-single-manual-expected.txt": [
+   "9d0112ef4bf35d3a6a80e2dcb836b4c57ca850e2",
+   "support"
+  ],
   "fullscreen/model/remove-single-manual.html": [
    "c7fc8323d503adb6d7f0c390a8add90c5c9e8082",
    "manual"
@@ -215537,7 +215895,7 @@
    "support"
   ],
   "html/dom/elements-metadata.js": [
-   "059d2c83a4354f173056ca489412d19ab77ef110",
+   "7e6becc4e2f4136a1c64d28724d1f21160900dd1",
    "support"
   ],
   "html/dom/elements-misc.js": [
@@ -216389,7 +216747,7 @@
    "testharness"
   ],
   "html/dom/reflection-forms-expected.txt": [
-   "b93e3eb7f43df1b24217d301df821f659cf40717",
+   "894f7725356b34612d700805df8adb1805128ba8",
    "support"
   ],
   "html/dom/reflection-forms.html": [
@@ -216401,7 +216759,7 @@
    "testharness"
   ],
   "html/dom/reflection-metadata-expected.txt": [
-   "f592a2fc5957c73953917cd7aa2474d9cd2812ad",
+   "b15548d7d88f894dcf309d4a042e9e2b9fc09a77",
    "support"
   ],
   "html/dom/reflection-metadata.html": [
@@ -216409,7 +216767,7 @@
    "testharness"
   ],
   "html/dom/reflection-misc-expected.txt": [
-   "f2046b22833fa1c167a8e898ce4c6f814937d0f2",
+   "a536ab0562bb16aef91dc32350d38773a217fd43",
    "support"
   ],
   "html/dom/reflection-misc.html": [
@@ -216449,7 +216807,7 @@
    "testharness"
   ],
   "html/dom/reflection.js": [
-   "c05afee11097eac0e05c8299c1ad8322afc47421",
+   "406cdc2d3a78d7a16d6f6e41430992a36678eb84",
    "support"
   ],
   "html/dom/resources/self-origin-subframe.html": [
@@ -221316,6 +221674,14 @@
    "37ba851b3393d3fe2e60985f502bf29438947449",
    "reftest"
   ],
+  "html/rendering/non-replaced-elements/the-hr-element-0/hr.html": [
+   "c9b0d9a027965ad3d2b9414b2a1bf9cadaa845c5",
+   "testharness"
+  ],
+  "html/rendering/non-replaced-elements/the-hr-element-0/setting-overflow-visible.html": [
+   "3feaff941f19801bff86d3ee364a4da9e07ab60b",
+   "testharness"
+  ],
   "html/rendering/non-replaced-elements/the-hr-element-0/width-ref.html": [
    "33d7505b42b0507d8fd73151e32fa71d73e3b862",
    "support"
@@ -224132,6 +224498,10 @@
    "a79ad27e8f1e2eee47c89fa4530f7babfbb07dd5",
    "support"
   ],
+  "html/semantics/interactive-elements/the-menu-element/menuitem-label-expected.txt": [
+   "237def2c38478a46dbaf16029c5c2b2d5ff04993",
+   "support"
+  ],
   "html/semantics/interactive-elements/the-menu-element/menuitem-label.html": [
    "d11f6d0ef667da33d7981860b47e6d02f8c0ad24",
    "testharness"
@@ -224145,7 +224515,7 @@
    "testharness"
   ],
   "html/semantics/interfaces-expected.txt": [
-   "66b9d2904c0b48ff310813f23947c8f27d45b1ec",
+   "5bde359d32efb77ff7ed13a3bc646eade3a92d8d",
    "support"
   ],
   "html/semantics/interfaces.html": [
@@ -224381,7 +224751,7 @@
    "support"
   ],
   "html/semantics/scripting-1/the-script-element/module/crossorigin-import-different.sub.html": [
-   "eac09ab0af026a54e327d2677e5ce59b357e4f87",
+   "2de317a6d55ecd7068aa5807387480c777cdd91a",
    "support"
   ],
   "html/semantics/scripting-1/the-script-element/module/crossorigin-import-missingheader.sub.html": [
@@ -224397,7 +224767,7 @@
    "support"
   ],
   "html/semantics/scripting-1/the-script-element/module/crossorigin-root-different.sub.html": [
-   "e49b493849c6dd9475415ffe3a2605a1cf3d8bbc",
+   "5828ce9cc3f468b6b9b6db9cec2ecec077680852",
    "support"
   ],
   "html/semantics/scripting-1/the-script-element/module/crossorigin-root-missingheader.sub.html": [
@@ -224417,7 +224787,7 @@
    "support"
   ],
   "html/semantics/scripting-1/the-script-element/module/crossorigin.html": [
-   "d18faac90e11617824f0a2bc2ead29ad14de3622",
+   "a52aa9bb0ea2e266fd04760ca3f5b21498d12029",
    "testharness"
   ],
   "html/semantics/scripting-1/the-script-element/module/currentScript-null.html": [
@@ -224581,7 +224951,7 @@
    "support"
   ],
   "html/semantics/scripting-1/the-script-element/module/imports-cycle.js": [
-   "462fe462bf92e1df232228cc1ce338ff522febbd",
+   "e9cad195e5e718080c21f7cf15a58a775a1226de",
    "support"
   ],
   "html/semantics/scripting-1/the-script-element/module/imports-inc-a.js": [
@@ -224601,11 +224971,11 @@
    "support"
   ],
   "html/semantics/scripting-1/the-script-element/module/imports-self.js": [
-   "26b832413187999e45c0520e171f5fc61a911b20",
+   "a18743fbd553b68e0ceb657a5d65e3424e6f6a52",
    "support"
   ],
   "html/semantics/scripting-1/the-script-element/module/imports.html": [
-   "997cee37dcd202498196e63e0f66035979121b7f",
+   "15b0b32d86bc6411b29c5d978db71053c00a1d65",
    "testharness"
   ],
   "html/semantics/scripting-1/the-script-element/module/instantiation-error-1.html": [
@@ -227336,6 +227706,18 @@
    "bc4c7a8b5eef0e082a5fa7c8c763957d92730747",
    "support"
   ],
+  "image-decodes/image-decode-iframe.html": [
+   "2632f69b005ea382ed91bfb71a7e0e2ee931a0c8",
+   "testharness"
+  ],
+  "image-decodes/image-decode-path-changes.html": [
+   "64dde3f7b1f9ed02e4433858f075123f18c092a4",
+   "testharness"
+  ],
+  "image-decodes/image-decode.html": [
+   "02d38d593fb42587c3ef261c349f7c42db345005",
+   "testharness"
+  ],
   "imagebitmap-renderingcontext/bitmaprenderer-as-imagesource.html": [
    "f80d1496329c64643d2b40e478779929de20c499",
    "testharness"
@@ -227564,6 +227946,10 @@
    "cd25518dd402033694667ccd1982fd3b85faa412",
    "testharness"
   ],
+  "input-events/input-events-typing-data-manual.html": [
+   "f94325d74e69fbd62bfdff3c0f4676a3d2c50d09",
+   "manual"
+  ],
   "interfaces/cssom.idl": [
    "bb17bbe93776dbeb33f061a7a90889e922e3138e",
    "support"
@@ -227584,6 +227970,10 @@
    "b0e4f7ea859518c04a21db8f0b69a07c6e11bbbe",
    "support"
   ],
+  "interfaces/mediacapture-main.idl": [
+   "3d59aebd6219a0312ade8c543bb389030d670d82",
+   "support"
+  ],
   "interfaces/remoteplayback.idl": [
    "f7c100f4275f2e32fbca3bb8d9c4900df879ffe4",
    "support"
@@ -227600,6 +227990,34 @@
    "4f94c4236168ed722f71d81bd957e0da72b29c71",
    "support"
   ],
+  "keyboard-lock/idlharness.https-expected.txt": [
+   "0c68884ea12132394c772cf6f47836fd6b17e2f1",
+   "support"
+  ],
+  "keyboard-lock/idlharness.https.html": [
+   "8b2fe1d77e8c4cc8a759a31bfb6f3d962f24992e",
+   "testharness"
+  ],
+  "keyboard-lock/navigator-cancelKeyboardLock.https.html": [
+   "5109eb45591bba9ce48d3db91fa02c0590397886",
+   "testharness"
+  ],
+  "keyboard-lock/navigator-requestKeyboardLock-two-parallel-requests.https-expected.txt": [
+   "723e8f552f3dd5279429b9b9c7114d77901886e2",
+   "support"
+  ],
+  "keyboard-lock/navigator-requestKeyboardLock-two-parallel-requests.https.html": [
+   "6a05080698fbeff768c4f5c85dbbc89cf3cfa09a",
+   "testharness"
+  ],
+  "keyboard-lock/navigator-requestKeyboardLock-two-sequential-requests.https.html": [
+   "79ed9f93e2f72bf8e11d04e25c6fa847c91971e4",
+   "testharness"
+  ],
+  "keyboard-lock/navigator-requestKeyboardLock.https.html": [
+   "6f8091035f4aa18131c548a81cbe80ba328169c9",
+   "testharness"
+  ],
   "longtask-timing/longtask-attributes.html": [
    "a88bd658adcb9ef3dcbfa803397910e531bc864b",
    "testharness"
@@ -228297,7 +228715,7 @@
    "testharness"
   ],
   "mediacapture-streams/GUM-api.https.html": [
-   "6f4e3b2b4fdb287f99935193f273cb21becb9669",
+   "8df77e469f423b7c7e84ddc6924e01d63b1ffc20",
    "testharness"
   ],
   "mediacapture-streams/GUM-deny.https-expected.txt": [
@@ -228305,7 +228723,7 @@
    "support"
   ],
   "mediacapture-streams/GUM-deny.https.html": [
-   "58581b53ff1a1edd5144c428145d668b073ef22f",
+   "ff6cf94acac74d1a650d6c2515b762fa483b110f",
    "testharness"
   ],
   "mediacapture-streams/GUM-empty-option-param.https-expected.txt": [
@@ -228340,6 +228758,14 @@
    "bfd825c8fb46797d043d57d70689a766fa557bd3",
    "testharness"
   ],
+  "mediacapture-streams/MediaDevices-IDL-all.html": [
+   "f1927866a05c7700dc2218e3a45eae1532a4171c",
+   "testharness"
+  ],
+  "mediacapture-streams/MediaDevices-IDL-enumerateDevices.html": [
+   "0bebfb5c5a6204257f30d03fb16e4a35b8943814",
+   "testharness"
+  ],
   "mediacapture-streams/MediaDevices-enumerateDevices.https.html": [
    "487ce67220fdefbb70d507c86d34711a315521fa",
    "testharness"
@@ -236712,8 +237138,16 @@
    "29af302db74de64e2bd1352ad92092a309d28c92",
    "testharness"
   ],
+  "payment-request/payment-request-abort-method.https.html": [
+   "c9ee5af2ccd5ad364090807c8427f1d4624d3747",
+   "testharness"
+  ],
+  "payment-request/payment-request-canmakepayment-method.html": [
+   "b20131bc3f2717212f9940920183d650ee111333",
+   "testharness"
+  ],
   "payment-request/payment-request-constructor.https.html": [
-   "12cb2d46800b03554830fb145f2d8dca37043f79",
+   "44d2656f2990c51063254326521a02218a7fc500",
    "testharness"
   ],
   "payment-request/payment-request-id.https.html": [
@@ -236733,7 +237167,7 @@
    "support"
   ],
   "payment-request/payment-request-show-method.https.html": [
-   "026e28265a88be54a56e2dadc0fb485303864dfc",
+   "518136ad885f95172e578f6e2c165a559c51896b",
    "testharness"
   ],
   "performance-timeline/case-sensitivity.any.js": [
@@ -237056,6 +237490,10 @@
    "0d656f37a7307047b07f59ed340a82d9a33a5b87",
    "manual"
   ],
+  "pointerevents/pointerevent_touch-action-rotated-divs_touch-manual.html": [
+   "e34340964c74ac0601911de3ac3b260db6ad620d",
+   "manual"
+  ],
   "pointerevents/pointerevent_touch-action-span-test_touch-manual.html": [
    "666e92e9be855d1290e97f1c732e021a98c666de",
    "manual"
@@ -237161,7 +237599,7 @@
    "testharness"
   ],
   "preload/download-resources.html": [
-   "c41bf773f52547e930503c588864c42c14416b86",
+   "df19c269c7193a5dc86630799afcd16a941f58a5",
    "testharness"
   ],
   "preload/dynamic-adding-preload.html": [
@@ -237189,11 +237627,11 @@
    "support"
   ],
   "preload/onerror-event.html": [
-   "aeb775170f956860edf37d492484531d1cd5aabc",
+   "8d07912e570ef8ece9ffdc8f6cc8eeae219a3e5c",
    "testharness"
   ],
   "preload/onload-event.html": [
-   "d00ad51acfea81056a2426d396c3a73d5f15120b",
+   "8d190661c002ffe0c6f2fca51454fc2d67a6f465",
    "testharness"
   ],
   "preload/preload-csp.sub.html": [
@@ -237208,6 +237646,10 @@
    "b15b6022d8fd4bdc88e7a54429667eb223bc2464",
    "testharness"
   ],
+  "preload/reflected-as-value.html": [
+   "835cd55f66486f2a66fd65d201a9184fb276ddab",
+   "testharness"
+  ],
   "preload/resources/dummy.css": [
    "788dde4e1879591cdd0e8298741f30b0d12578cd",
    "support"
@@ -245200,16 +245642,80 @@
    "08b4d5a7a055cacf28b5f2011a22f17e682f320a",
    "support"
   ],
+  "scroll-anchoring/abspos-containing-block-outside-scroller.html": [
+   "d7a8e9904637c833d897b2e9c0da0a1628455670",
+   "testharness"
+  ],
+  "scroll-anchoring/abspos-contributes-to-static-parent-bounds.html": [
+   "22a644ace4ce062795fe22b9adfb8832eae52cdd",
+   "testharness"
+  ],
+  "scroll-anchoring/ancestor-change-heuristic.html": [
+   "71fe4701baacc42151795ada988d2de67d863e85",
+   "testharness"
+  ],
+  "scroll-anchoring/anchor-updates-after-explicit-scroll.html": [
+   "c2d5fccdb6beaa8944aee84365597cabd325ff89",
+   "testharness"
+  ],
+  "scroll-anchoring/anchoring-with-bounds-clamping-div.html": [
+   "07a8a3784fbf105342b66b3702b91471973a475b",
+   "testharness"
+  ],
   "scroll-anchoring/anchoring-with-bounds-clamping.html": [
-   "1cdccfe793c6e011c8db4a6ab729f39091decb30",
+   "f317bd3dfd5c0e2e8311a805e0eb036506b19fa4",
+   "testharness"
+  ],
+  "scroll-anchoring/anonymous-block-box.html": [
+   "5a2a5de699a8dd46c2c0de5cc93a9cfa1242c3b7",
    "testharness"
   ],
   "scroll-anchoring/basic.html": [
-   "dc6cb2d5c8b89199a3209873f6d2600899794c2d",
+   "c185fc1ceb75bb2ee74db77850f7e0e4434a9485",
    "testharness"
   ],
   "scroll-anchoring/clipped-scrollers-skipped.html": [
-   "abf4b19b11f4d1e543cebddb7a52579e0465761b",
+   "6cb749057f4db48c69a915e28efff9cd257574a2",
+   "testharness"
+  ],
+  "scroll-anchoring/descend-into-container-with-float.html": [
+   "3f576c8cfdad4de96244d2939e9d4685369ebe52",
+   "testharness"
+  ],
+  "scroll-anchoring/descend-into-container-with-overflow.html": [
+   "29549296be64ffbffb88ed3465de975936fadc25",
+   "testharness"
+  ],
+  "scroll-anchoring/exclude-fixed-position.html": [
+   "b1d73a505f0e6c94ef4f66f0c0057413be45ffb8",
+   "testharness"
+  ],
+  "scroll-anchoring/inline-block.html": [
+   "c22dd1ba4df2e04cc93dc32234b48224cffe6c0a",
+   "testharness"
+  ],
+  "scroll-anchoring/negative-layout-overflow.html": [
+   "2682ff6a640c4b45a8a9195a17c5ff8dc3c90228",
+   "testharness"
+  ],
+  "scroll-anchoring/opt-out.html": [
+   "1064eb745186a2558dfa53c258dca8e685a496a1",
+   "testharness"
+  ],
+  "scroll-anchoring/position-change-heuristic.html": [
+   "c88ed793fa9253fb53118df5f2d10836b1f60b48",
+   "testharness"
+  ],
+  "scroll-anchoring/start-edge-in-block-layout-direction.html": [
+   "0eb274f0e5d9bcb9ba2bb80cb65e5d3e663a3235",
+   "testharness"
+  ],
+  "scroll-anchoring/subtree-exclusion.html": [
+   "dbfd02f30f8dc2750d697756e3c5f95bc1937c8a",
+   "testharness"
+  ],
+  "scroll-anchoring/wrapped-text.html": [
+   "de66dba5bce15b7403e9e582d982d4e3e4aed552",
    "testharness"
   ],
   "secure-contexts/basic-dedicated-worker.html": [
@@ -245229,7 +245735,7 @@
    "testharness"
   ],
   "secure-contexts/basic-popup-and-iframe-tests.https.js": [
-   "69573c7565eacce1d1069f8ae116615340d404c2",
+   "26c7c307334b8a0e10265cf6fe3664d84cdd72cc",
    "support"
   ],
   "secure-contexts/basic-shared-worker.html": [
@@ -245924,6 +246430,10 @@
    "9737491d653de76a70f9c0143baa70760f2f95c4",
    "testharness"
   ],
+  "service-workers/service-worker/clients-matchall-order.https-expected.txt": [
+   "07ddecdb2465c1dab486fabd2a43f71346b2ae0e",
+   "support"
+  ],
   "service-workers/service-worker/clients-matchall-order.https.html": [
    "b2617b7dce0dce64c1a354a3dc07c67f1fa0adf2",
    "testharness"
@@ -246073,7 +246583,7 @@
    "support"
   ],
   "service-workers/service-worker/fetch-request-fallback.https.html": [
-   "7537d16b43abab8ff1d03728329638d1f061dda3",
+   "31a150bf8504f0607ac77a617ca8094336d1f31b",
    "testharness"
   ],
   "service-workers/service-worker/fetch-request-html-imports.https.html": [
@@ -246096,6 +246606,14 @@
    "cb072581704868845fe1ec57b3e7c70d53bac543",
    "testharness"
   ],
+  "service-workers/service-worker/fetch-request-xhr-sync.https-expected.txt": [
+   "74ec4922419ee10d8f6edda48ac860fab5cfa0e3",
+   "support"
+  ],
+  "service-workers/service-worker/fetch-request-xhr-sync.https.html": [
+   "4aaa0b1995643f4e18c47d1947476a1a67fe997d",
+   "testharness"
+  ],
   "service-workers/service-worker/fetch-request-xhr.https.html": [
    "088581ef6036d3cc8688d5974461d8e5b8ed1cfd",
    "testharness"
@@ -246765,7 +247283,7 @@
    "support"
   ],
   "service-workers/service-worker/resources/fetch-request-fallback-iframe.html": [
-   "f31f15d645452fb2e8cfd9b5fb45e240de575202",
+   "deb9dc758d0c481f20f585b92014f6530ef6f425",
    "support"
   ],
   "service-workers/service-worker/resources/fetch-request-fallback-worker.js": [
@@ -246804,6 +247322,14 @@
    "3e514258608705763ee932821bee89696be1d2e9",
    "support"
   ],
+  "service-workers/service-worker/resources/fetch-request-xhr-sync-iframe.html": [
+   "a168a0326207e734f0229d49cce12af9a37e81ec",
+   "support"
+  ],
+  "service-workers/service-worker/resources/fetch-request-xhr-sync-worker.js": [
+   "fe1386a87464c16d62e23eb102b25891960e7209",
+   "support"
+  ],
   "service-workers/service-worker/resources/fetch-request-xhr-worker.js": [
    "fcbb50668ec1a3f7bdbd4331babda5b6e0295f2e",
    "support"
@@ -247061,7 +247587,7 @@
    "support"
   ],
   "service-workers/service-worker/resources/opaque-response-preloaded-iframe.html": [
-   "8c17061aeebbbfdbe49496a515153bf6530513c0",
+   "df5f1602891ffd5344dfcdf8d2e406f27b3f54e8",
    "support"
   ],
   "service-workers/service-worker/resources/opaque-response-preloaded-worker.js": [
@@ -249608,6 +250134,14 @@
    "6e388e46caf1a0259a6dc6778e7b33619e389751",
    "support"
   ],
+  "url/toascii.json": [
+   "e8f5d819b9b4608d730a0a601e16ac2dd6c2d134",
+   "support"
+  ],
+  "url/toascii.window.js": [
+   "7166d07215b578c5d11d7ac831ddb47d0821155a",
+   "testharness"
+  ],
   "url/url-constructor.html": [
    "478523551e13b4066293fc2244972dd82b9bd87f",
    "testharness"
@@ -250984,6 +251518,10 @@
    "7665873355c531e009824021a2b75daaf2dd6e3f",
    "testharness"
   ],
+  "webrtc/RTCCertificate.html": [
+   "76e0c5f601c8ba4aefb06d1ebab8454c78fe07df",
+   "testharness"
+  ],
   "webrtc/RTCConfiguration-iceCandidatePoolSize-expected.txt": [
    "a3c6a87b158deb84be0f56e8467b1f9bf16aba71",
    "support"
@@ -251016,6 +251554,10 @@
    "6ca52e2e6ec19dc38a7cc0a4ce132f7c5de9e398",
    "testharness"
   ],
+  "webrtc/RTCPeerConnection-addTransceiver.html": [
+   "0ce85d1a83181d1bd0f337c7dbb5443efbda32cd",
+   "testharness"
+  ],
   "webrtc/RTCPeerConnection-canTrickleIceCandidates-expected.txt": [
    "2ac075fe0ee3b4c0da551ad770db34192b426e8a",
    "support"
@@ -251029,7 +251571,11 @@
    "support"
   ],
   "webrtc/RTCPeerConnection-constructor.html": [
-   "54fff02eab02b0be84c945ec7e77afe722d9f988",
+   "60e9ec4bec3e3d3b785568be8cf089c959e71813",
+   "testharness"
+  ],
+  "webrtc/RTCPeerConnection-createAnswer.html": [
+   "454d00f0f44a423457148002e944b89ac06d3b9c",
    "testharness"
   ],
   "webrtc/RTCPeerConnection-createDataChannel-expected.txt": [
@@ -251040,6 +251586,22 @@
    "025b3ac2230499375796145bdbcc3a9274a41605",
    "testharness"
   ],
+  "webrtc/RTCPeerConnection-createOffer.html": [
+   "82fa509a012476b364662f1459051ef05998b378",
+   "testharness"
+  ],
+  "webrtc/RTCPeerConnection-generateCertificate.html": [
+   "27fb46922255203da0fc26a63808aeb98a60b640",
+   "testharness"
+  ],
+  "webrtc/RTCPeerConnection-getTransceivers.html": [
+   "498e9cb9d143626fb78839c1c550d05e9a46c22f",
+   "testharness"
+  ],
+  "webrtc/RTCPeerConnection-helper.js": [
+   "70133a446dbd66e9f81ac8632fd80e85261dda4f",
+   "support"
+  ],
   "webrtc/RTCPeerConnection-iceGatheringState-expected.txt": [
    "6be33b9fb07075dfc81993ff1753e48a86f295b4",
    "support"
@@ -251056,16 +251618,24 @@
    "e322b3ab7f8e0bc7ff802f00234a9a6e80b8285a",
    "testharness"
   ],
+  "webrtc/RTCPeerConnection-onnegotiationneeded.html": [
+   "e74e16479aca577be6673056eef0fd0212ec7151",
+   "testharness"
+  ],
+  "webrtc/RTCPeerConnection-setLocalDescription.html": [
+   "ad354759c2c03909ed3834a0c9c10dee36becc31",
+   "testharness"
+  ],
   "webrtc/RTCPeerConnection-setRemoteDescription-expected.txt": [
    "e48c21dca1fce4f9959bdde0bd302112146088ff",
    "support"
   ],
   "webrtc/RTCPeerConnection-setRemoteDescription.html": [
-   "ee89fd092059a4cc489536898d4a47a7bbbf36f2",
+   "e5764f28f4d1b33323b574d87176f8ae9806308f",
    "testharness"
   ],
   "webrtc/RTCPeerConnectionIceEvent-constructor.html": [
-   "46bb1ba3a908f99b46a656d34094aae8998ed484",
+   "f273bd7fdfc883a15e8fb16fef5309061254c6cc",
    "testharness"
   ],
   "webrtc/datachannel-emptystring-expected.txt": [
diff --git a/third_party/WebKit/LayoutTests/external/wpt/XMLHttpRequest/send-entity-body-document.htm b/third_party/WebKit/LayoutTests/external/wpt/XMLHttpRequest/send-entity-body-document.htm
index ceafc48..5f1cb68 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/XMLHttpRequest/send-entity-body-document.htm
+++ b/third_party/WebKit/LayoutTests/external/wpt/XMLHttpRequest/send-entity-body-document.htm
@@ -29,7 +29,7 @@
           client.send(input)
           var exp = expectations[number]
           assert_equals(client.getResponseHeader('X-Request-Content-Type'), exp.contentType, 'document should be serialized and sent as '+exp.contentType+' (TEST#'+number+')')
-          // The indexOf() assertion will overlook some stuff, i.e. XML prologues that shouldn't be there (looking at you, Presto).
+          // The indexOf() assertation will overlook some stuff, i.e. XML prologues that shouldn't be there (looking at you, Presto).
           // However, arguably these things have little to do with the XHR functionality we're testing.
           if(exp.responseText){ // This test does not want to assert anything about what markup a standalone IMG should be wrapped in. Hence the GIF test lacks a responseText expectation.
             assert_true(client.responseText.indexOf(exp.responseText) != -1,
diff --git a/third_party/WebKit/LayoutTests/external/wpt/XMLHttpRequest/status-async.htm b/third_party/WebKit/LayoutTests/external/wpt/XMLHttpRequest/status-async.htm
index 8e82701..dcf7d62 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/XMLHttpRequest/status-async.htm
+++ b/third_party/WebKit/LayoutTests/external/wpt/XMLHttpRequest/status-async.htm
@@ -52,7 +52,7 @@
       status(401, "OH HELLO", "Not today.", "")
       status(402, "FIVE BUCKS", "<x>402<\/x>", "text/xml")
       status(402, "FREE", "Nice!", "text/doesnotmatter")
-      status(402, "402 THE AWESOME", "", "")
+      status(402, "402 TEH AWESOME", "", "")
       status(502, "YO", "", "")
       status(502, "lowercase", "SWEET POTATO", "text/plain")
       status(503, "HOUSTON WE HAVE A", "503", "text/plain")
diff --git a/third_party/WebKit/LayoutTests/external/wpt/XMLHttpRequest/status-basic.htm b/third_party/WebKit/LayoutTests/external/wpt/XMLHttpRequest/status-basic.htm
index 80147e8..fed7cabec 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/XMLHttpRequest/status-basic.htm
+++ b/third_party/WebKit/LayoutTests/external/wpt/XMLHttpRequest/status-basic.htm
@@ -41,7 +41,7 @@
       status(401, "OH HELLO", "Not today.", "")
       status(402, "FIVE BUCKS", "<x>402<\/x>", "text/xml")
       status(402, "FREE", "Nice!", "text/doesnotmatter")
-      status(402, "402 THE AWESOME", "", "")
+      status(402, "402 TEH AWESOME", "", "")
       status(502, "YO", "", "")
       status(502, "lowercase", "SWEET POTATO", "text/plain")
       status(503, "HOUSTON WE HAVE A", "503", "text/plain")
diff --git a/third_party/WebKit/LayoutTests/external/wpt/XMLHttpRequest/xmlhttprequest-basic.htm b/third_party/WebKit/LayoutTests/external/wpt/XMLHttpRequest/xmlhttprequest-basic.htm
index 1b9a36c..c48b610f 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/XMLHttpRequest/xmlhttprequest-basic.htm
+++ b/third_party/WebKit/LayoutTests/external/wpt/XMLHttpRequest/xmlhttprequest-basic.htm
@@ -12,9 +12,9 @@
     <div id="log"></div>
     <script>
       test(function() {
-        XMLHttpRequest.prototype.test = function() { return "THE" }
+        XMLHttpRequest.prototype.test = function() { return "TEH" }
         var client = new XMLHttpRequest()
-        assert_equals(client.test(), "THE")
+        assert_equals(client.test(), "TEH")
         var members = ["onreadystatechange",
                        "open",
                        "setRequestHeader",
diff --git a/third_party/WebKit/LayoutTests/external/wpt/assumptions/ahem-ref.html b/third_party/WebKit/LayoutTests/external/wpt/assumptions/ahem-ref.html
new file mode 100644
index 0000000..2272bdab
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/assumptions/ahem-ref.html
@@ -0,0 +1,300 @@
+
+<!doctype html>
+<title>Ahem checker</title>
+<style>
+@font-face {
+  font-family: Ahem;
+  src: url("../css/fonts/ahem/ahem.ttf");
+}
+* {
+  padding: 0;
+  margin: 0;
+  border: none;
+}
+
+table {
+  font: 15px/1 Ahem;
+  border-collapse: separate;
+  border-spacing: 1px;
+  table-layout: fixed;
+}
+
+td {
+  width: 34px;
+}
+</style>
+<table>
+    <tr>
+        <td>&#x0020;x <!-- U+0020: SPACE -->
+        <td>&#x0021;x <!-- U+0021: EXCLAMATION MARK -->
+        <td>&#x0022;x <!-- U+0022: QUOTATION MARK -->
+        <td>&#x0023;x <!-- U+0023: NUMBER SIGN -->
+        <td>&#x0024;x <!-- U+0024: DOLLAR SIGN -->
+        <td>&#x0025;x <!-- U+0025: PERCENT SIGN -->
+        <td>&#x0026;x <!-- U+0026: AMPERSAND -->
+        <td>&#x0028;x <!-- U+0028: LEFT PARENTHESIS -->
+        <td>&#x0029;x <!-- U+0029: RIGHT PARENTHESIS -->
+        <td>&#x002A;x <!-- U+002A: ASTERISK -->
+        <td>&#x002B;x <!-- U+002B: PLUS SIGN -->
+        <td>&#x002C;x <!-- U+002C: COMMA -->
+        <td>&#x002D;x <!-- U+002D: HYPHEN-MINUS -->
+        <td>&#x002E;x <!-- U+002E: FULL STOP -->
+        <td>&#x002F;x <!-- U+002F: SOLIDUS -->
+        <td>&#x0030;x <!-- U+0030: DIGIT ZERO -->
+        <td>&#x0031;x <!-- U+0031: DIGIT ONE -->
+    <tr>
+        <td>&#x0032;x <!-- U+0032: DIGIT TWO -->
+        <td>&#x0033;x <!-- U+0033: DIGIT THREE -->
+        <td>&#x0034;x <!-- U+0034: DIGIT FOUR -->
+        <td>&#x0035;x <!-- U+0035: DIGIT FIVE -->
+        <td>&#x0036;x <!-- U+0036: DIGIT SIX -->
+        <td>&#x0037;x <!-- U+0037: DIGIT SEVEN -->
+        <td>&#x0038;x <!-- U+0038: DIGIT EIGHT -->
+        <td>&#x0039;x <!-- U+0039: DIGIT NINE -->
+        <td>&#x003A;x <!-- U+003A: COLON -->
+        <td>&#x003B;x <!-- U+003B: SEMICOLON -->
+        <td>&#x003C;x <!-- U+003C: LESS-THAN SIGN -->
+        <td>&#x003D;x <!-- U+003D: EQUALS SIGN -->
+        <td>&#x003E;x <!-- U+003E: GREATER-THAN SIGN -->
+        <td>&#x003F;x <!-- U+003F: QUESTION MARK -->
+        <td>&#x0040;x <!-- U+0040: COMMERCIAL AT -->
+        <td>&#x0041;x <!-- U+0041: LATIN CAPITAL LETTER A -->
+        <td>&#x0042;x <!-- U+0042: LATIN CAPITAL LETTER B -->
+    <tr>
+        <td>&#x0043;x <!-- U+0043: LATIN CAPITAL LETTER C -->
+        <td>&#x0044;x <!-- U+0044: LATIN CAPITAL LETTER D -->
+        <td>&#x0045;x <!-- U+0045: LATIN CAPITAL LETTER E -->
+        <td>&#x0046;x <!-- U+0046: LATIN CAPITAL LETTER F -->
+        <td>&#x0047;x <!-- U+0047: LATIN CAPITAL LETTER G -->
+        <td>&#x0048;x <!-- U+0048: LATIN CAPITAL LETTER H -->
+        <td>&#x0049;x <!-- U+0049: LATIN CAPITAL LETTER I -->
+        <td>&#x004A;x <!-- U+004A: LATIN CAPITAL LETTER J -->
+        <td>&#x004B;x <!-- U+004B: LATIN CAPITAL LETTER K -->
+        <td>&#x004C;x <!-- U+004C: LATIN CAPITAL LETTER L -->
+        <td>&#x004D;x <!-- U+004D: LATIN CAPITAL LETTER M -->
+        <td>&#x004E;x <!-- U+004E: LATIN CAPITAL LETTER N -->
+        <td>&#x004F;x <!-- U+004F: LATIN CAPITAL LETTER O -->
+        <td>&#x0050;x <!-- U+0050: LATIN CAPITAL LETTER P -->
+        <td>&#x0051;x <!-- U+0051: LATIN CAPITAL LETTER Q -->
+        <td>&#x0052;x <!-- U+0052: LATIN CAPITAL LETTER R -->
+        <td>&#x0053;x <!-- U+0053: LATIN CAPITAL LETTER S -->
+    <tr>
+        <td>&#x0054;x <!-- U+0054: LATIN CAPITAL LETTER T -->
+        <td>&#x0055;x <!-- U+0055: LATIN CAPITAL LETTER U -->
+        <td>&#x0056;x <!-- U+0056: LATIN CAPITAL LETTER V -->
+        <td>&#x0057;x <!-- U+0057: LATIN CAPITAL LETTER W -->
+        <td>&#x0058;x <!-- U+0058: LATIN CAPITAL LETTER X -->
+        <td>&#x0059;x <!-- U+0059: LATIN CAPITAL LETTER Y -->
+        <td>&#x005A;x <!-- U+005A: LATIN CAPITAL LETTER Z -->
+        <td>&#x005B;x <!-- U+005B: LEFT SQUARE BRACKET -->
+        <td>&#x005C;x <!-- U+005C: REVERSE SOLIDUS -->
+        <td>&#x005D;x <!-- U+005D: RIGHT SQUARE BRACKET -->
+        <td>&#x005E;x <!-- U+005E: CIRCUMFLEX ACCENT -->
+        <td>&#x005F;x <!-- U+005F: LOW LINE -->
+        <td>&#x0060;x <!-- U+0060: GRAVE ACCENT -->
+        <td>&#x0061;x <!-- U+0061: LATIN SMALL LETTER A -->
+        <td>&#x0062;x <!-- U+0062: LATIN SMALL LETTER B -->
+        <td>&#x0063;x <!-- U+0063: LATIN SMALL LETTER C -->
+        <td>&#x0064;x <!-- U+0064: LATIN SMALL LETTER D -->
+    <tr>
+        <td>&#x0065;x <!-- U+0065: LATIN SMALL LETTER E -->
+        <td>&#x0066;x <!-- U+0066: LATIN SMALL LETTER F -->
+        <td>&#x0067;x <!-- U+0067: LATIN SMALL LETTER G -->
+        <td>&#x0068;x <!-- U+0068: LATIN SMALL LETTER H -->
+        <td>&#x0069;x <!-- U+0069: LATIN SMALL LETTER I -->
+        <td>&#x006A;x <!-- U+006A: LATIN SMALL LETTER J -->
+        <td>&#x006B;x <!-- U+006B: LATIN SMALL LETTER K -->
+        <td>&#x006C;x <!-- U+006C: LATIN SMALL LETTER L -->
+        <td>&#x006D;x <!-- U+006D: LATIN SMALL LETTER M -->
+        <td>&#x006E;x <!-- U+006E: LATIN SMALL LETTER N -->
+        <td>&#x006F;x <!-- U+006F: LATIN SMALL LETTER O -->
+        <td>&#x0070;x <!-- U+0070: LATIN SMALL LETTER P -->
+        <td>&#x0071;x <!-- U+0071: LATIN SMALL LETTER Q -->
+        <td>&#x0072;x <!-- U+0072: LATIN SMALL LETTER R -->
+        <td>&#x0073;x <!-- U+0073: LATIN SMALL LETTER S -->
+        <td>&#x0074;x <!-- U+0074: LATIN SMALL LETTER T -->
+        <td>&#x0075;x <!-- U+0075: LATIN SMALL LETTER U -->
+    <tr>
+        <td>&#x0076;x <!-- U+0076: LATIN SMALL LETTER V -->
+        <td>&#x0077;x <!-- U+0077: LATIN SMALL LETTER W -->
+        <td>&#x0078;x <!-- U+0078: LATIN SMALL LETTER X -->
+        <td>&#x0079;x <!-- U+0079: LATIN SMALL LETTER Y -->
+        <td>&#x007A;x <!-- U+007A: LATIN SMALL LETTER Z -->
+        <td>&#x007B;x <!-- U+007B: LEFT CURLY BRACKET -->
+        <td>&#x007C;x <!-- U+007C: VERTICAL LINE -->
+        <td>&#x007D;x <!-- U+007D: RIGHT CURLY BRACKET -->
+        <td>&#x007E;x <!-- U+007E: TILDE -->
+        <td>&#x00A0;x <!-- U+00A0: NO-BREAK SPACE -->
+        <td>&#x00A1;x <!-- U+00A1: INVERTED EXCLAMATION MARK -->
+        <td>&#x00A2;x <!-- U+00A2: CENT SIGN -->
+        <td>&#x00A3;x <!-- U+00A3: POUND SIGN -->
+        <td>&#x00A4;x <!-- U+00A4: CURRENCY SIGN -->
+        <td>&#x00A5;x <!-- U+00A5: YEN SIGN -->
+        <td>&#x00A6;x <!-- U+00A6: BROKEN BAR -->
+        <td>&#x00A7;x <!-- U+00A7: SECTION SIGN -->
+    <tr>
+        <td>&#x00A8;x <!-- U+00A8: DIAERESIS -->
+        <td>&#x00A9;x <!-- U+00A9: COPYRIGHT SIGN -->
+        <td>&#x00AA;x <!-- U+00AA: FEMININE ORDINAL INDICATOR -->
+        <td>&#x00AB;x <!-- U+00AB: LEFT-POINTING DOUBLE ANGLE QUOTATION MARK -->
+        <td>&#x00AC;x <!-- U+00AC: NOT SIGN -->
+        <td>&#x00AD;x <!-- U+00AD: SOFT HYPHEN -->
+        <td>&#x00AE;x <!-- U+00AE: REGISTERED SIGN -->
+        <td>&#x00AF;x <!-- U+00AF: MACRON -->
+        <td>&#x00B0;x <!-- U+00B0: DEGREE SIGN -->
+        <td>&#x00B1;x <!-- U+00B1: PLUS-MINUS SIGN -->
+        <td>&#x00B2;x <!-- U+00B2: SUPERSCRIPT TWO -->
+        <td>&#x00B3;x <!-- U+00B3: SUPERSCRIPT THREE -->
+        <td>&#x00B4;x <!-- U+00B4: ACUTE ACCENT -->
+        <td>&#x00B5;x <!-- U+00B5: MICRO SIGN -->
+        <td>&#x00B6;x <!-- U+00B6: PILCROW SIGN -->
+        <td>&#x00B7;x <!-- U+00B7: MIDDLE DOT -->
+        <td>&#x00B8;x <!-- U+00B8: CEDILLA -->
+    <tr>
+        <td>&#x00B9;x <!-- U+00B9: SUPERSCRIPT ONE -->
+        <td>&#x00BA;x <!-- U+00BA: MASCULINE ORDINAL INDICATOR -->
+        <td>&#x00BB;x <!-- U+00BB: RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK -->
+        <td>&#x00BC;x <!-- U+00BC: VULGAR FRACTION ONE QUARTER -->
+        <td>&#x00BD;x <!-- U+00BD: VULGAR FRACTION ONE HALF -->
+        <td>&#x00BE;x <!-- U+00BE: VULGAR FRACTION THREE QUARTERS -->
+        <td>&#x00BF;x <!-- U+00BF: INVERTED QUESTION MARK -->
+        <td>&#x00C0;x <!-- U+00C0: LATIN CAPITAL LETTER A WITH GRAVE -->
+        <td>&#x00C1;x <!-- U+00C1: LATIN CAPITAL LETTER A WITH ACUTE -->
+        <td>&#x00C2;x <!-- U+00C2: LATIN CAPITAL LETTER A WITH CIRCUMFLEX -->
+        <td>&#x00C3;x <!-- U+00C3: LATIN CAPITAL LETTER A WITH TILDE -->
+        <td>&#x00C4;x <!-- U+00C4: LATIN CAPITAL LETTER A WITH DIAERESIS -->
+        <td>&#x00C5;x <!-- U+00C5: LATIN CAPITAL LETTER A WITH RING ABOVE -->
+        <td>&#x00C6;x <!-- U+00C6: LATIN CAPITAL LETTER AE -->
+        <td>&#x00C7;x <!-- U+00C7: LATIN CAPITAL LETTER C WITH CEDILLA -->
+        <td>&#x00C8;x <!-- U+00C8: LATIN CAPITAL LETTER E WITH GRAVE -->
+        <td>&#x00C9;x <!-- U+00C9: LATIN CAPITAL LETTER E WITH ACUTE -->
+    <tr>
+        <td>&#x00CA;x <!-- U+00CA: LATIN CAPITAL LETTER E WITH CIRCUMFLEX -->
+        <td>&#x00CB;x <!-- U+00CB: LATIN CAPITAL LETTER E WITH DIAERESIS -->
+        <td>&#x00CC;x <!-- U+00CC: LATIN CAPITAL LETTER I WITH GRAVE -->
+        <td>&#x00CD;x <!-- U+00CD: LATIN CAPITAL LETTER I WITH ACUTE -->
+        <td>&#x00CE;x <!-- U+00CE: LATIN CAPITAL LETTER I WITH CIRCUMFLEX -->
+        <td>&#x00CF;x <!-- U+00CF: LATIN CAPITAL LETTER I WITH DIAERESIS -->
+        <td>&#x00D0;x <!-- U+00D0: LATIN CAPITAL LETTER ETH -->
+        <td>&#x00D1;x <!-- U+00D1: LATIN CAPITAL LETTER N WITH TILDE -->
+        <td>&#x00D2;x <!-- U+00D2: LATIN CAPITAL LETTER O WITH GRAVE -->
+        <td>&#x00D3;x <!-- U+00D3: LATIN CAPITAL LETTER O WITH ACUTE -->
+        <td>&#x00D4;x <!-- U+00D4: LATIN CAPITAL LETTER O WITH CIRCUMFLEX -->
+        <td>&#x00D5;x <!-- U+00D5: LATIN CAPITAL LETTER O WITH TILDE -->
+        <td>&#x00D6;x <!-- U+00D6: LATIN CAPITAL LETTER O WITH DIAERESIS -->
+        <td>&#x00D7;x <!-- U+00D7: MULTIPLICATION SIGN -->
+        <td>&#x00D8;x <!-- U+00D8: LATIN CAPITAL LETTER O WITH STROKE -->
+        <td>&#x00D9;x <!-- U+00D9: LATIN CAPITAL LETTER U WITH GRAVE -->
+        <td>&#x00DA;x <!-- U+00DA: LATIN CAPITAL LETTER U WITH ACUTE -->
+    <tr>
+        <td>&#x00DB;x <!-- U+00DB: LATIN CAPITAL LETTER U WITH CIRCUMFLEX -->
+        <td>&#x00DC;x <!-- U+00DC: LATIN CAPITAL LETTER U WITH DIAERESIS -->
+        <td>&#x00DD;x <!-- U+00DD: LATIN CAPITAL LETTER Y WITH ACUTE -->
+        <td>&#x00DE;x <!-- U+00DE: LATIN CAPITAL LETTER THORN -->
+        <td>&#x00DF;x <!-- U+00DF: LATIN SMALL LETTER SHARP S -->
+        <td>&#x00E0;x <!-- U+00E0: LATIN SMALL LETTER A WITH GRAVE -->
+        <td>&#x00E1;x <!-- U+00E1: LATIN SMALL LETTER A WITH ACUTE -->
+        <td>&#x00E2;x <!-- U+00E2: LATIN SMALL LETTER A WITH CIRCUMFLEX -->
+        <td>&#x00E3;x <!-- U+00E3: LATIN SMALL LETTER A WITH TILDE -->
+        <td>&#x00E4;x <!-- U+00E4: LATIN SMALL LETTER A WITH DIAERESIS -->
+        <td>&#x00E5;x <!-- U+00E5: LATIN SMALL LETTER A WITH RING ABOVE -->
+        <td>&#x00E6;x <!-- U+00E6: LATIN SMALL LETTER AE -->
+        <td>&#x00E7;x <!-- U+00E7: LATIN SMALL LETTER C WITH CEDILLA -->
+        <td>&#x00E8;x <!-- U+00E8: LATIN SMALL LETTER E WITH GRAVE -->
+        <td>&#x00E9;x <!-- U+00E9: LATIN SMALL LETTER E WITH ACUTE -->
+        <td>&#x00EA;x <!-- U+00EA: LATIN SMALL LETTER E WITH CIRCUMFLEX -->
+        <td>&#x00EB;x <!-- U+00EB: LATIN SMALL LETTER E WITH DIAERESIS -->
+    <tr>
+        <td>&#x00EC;x <!-- U+00EC: LATIN SMALL LETTER I WITH GRAVE -->
+        <td>&#x00ED;x <!-- U+00ED: LATIN SMALL LETTER I WITH ACUTE -->
+        <td>&#x00EE;x <!-- U+00EE: LATIN SMALL LETTER I WITH CIRCUMFLEX -->
+        <td>&#x00EF;x <!-- U+00EF: LATIN SMALL LETTER I WITH DIAERESIS -->
+        <td>&#x00F0;x <!-- U+00F0: LATIN SMALL LETTER ETH -->
+        <td>&#x00F1;x <!-- U+00F1: LATIN SMALL LETTER N WITH TILDE -->
+        <td>&#x00F2;x <!-- U+00F2: LATIN SMALL LETTER O WITH GRAVE -->
+        <td>&#x00F3;x <!-- U+00F3: LATIN SMALL LETTER O WITH ACUTE -->
+        <td>&#x00F4;x <!-- U+00F4: LATIN SMALL LETTER O WITH CIRCUMFLEX -->
+        <td>&#x00F5;x <!-- U+00F5: LATIN SMALL LETTER O WITH TILDE -->
+        <td>&#x00F6;x <!-- U+00F6: LATIN SMALL LETTER O WITH DIAERESIS -->
+        <td>&#x00F7;x <!-- U+00F7: DIVISION SIGN -->
+        <td>&#x00F8;x <!-- U+00F8: LATIN SMALL LETTER O WITH STROKE -->
+        <td>&#x00F9;x <!-- U+00F9: LATIN SMALL LETTER U WITH GRAVE -->
+        <td>&#x00FA;x <!-- U+00FA: LATIN SMALL LETTER U WITH ACUTE -->
+        <td>&#x00FB;x <!-- U+00FB: LATIN SMALL LETTER U WITH CIRCUMFLEX -->
+        <td>&#x00FC;x <!-- U+00FC: LATIN SMALL LETTER U WITH DIAERESIS -->
+    <tr>
+        <td>&#x00FD;x <!-- U+00FD: LATIN SMALL LETTER Y WITH ACUTE -->
+        <td>&#x00FE;x <!-- U+00FE: LATIN SMALL LETTER THORN -->
+        <td>&#x00FF;x <!-- U+00FF: LATIN SMALL LETTER Y WITH DIAERESIS -->
+        <td>&#x0131;x <!-- U+0131: LATIN SMALL LETTER DOTLESS I -->
+        <td>&#x0152;x <!-- U+0152: LATIN CAPITAL LIGATURE OE -->
+        <td>&#x0153;x <!-- U+0153: LATIN SMALL LIGATURE OE -->
+        <td>&#x0178;x <!-- U+0178: LATIN CAPITAL LETTER Y WITH DIAERESIS -->
+        <td>&#x0192;x <!-- U+0192: LATIN SMALL LETTER F WITH HOOK -->
+        <td>&#x02C6;x <!-- U+02C6: MODIFIER LETTER CIRCUMFLEX ACCENT -->
+        <td>&#x02C7;x <!-- U+02C7: CARON -->
+        <td>&#x02C9;x <!-- U+02C9: MODIFIER LETTER MACRON -->
+        <td>&#x02D8;x <!-- U+02D8: BREVE -->
+        <td>&#x02D9;x <!-- U+02D9: DOT ABOVE -->
+        <td>&#x02DA;x <!-- U+02DA: RING ABOVE -->
+        <td>&#x02DB;x <!-- U+02DB: OGONEK -->
+        <td>&#x02DC;x <!-- U+02DC: SMALL TILDE -->
+        <td>&#x02DD;x <!-- U+02DD: DOUBLE ACUTE ACCENT -->
+    <tr>
+        <td>&#x0394;x <!-- U+0394: GREEK CAPITAL LETTER DELTA -->
+        <td>&#x03A9;x <!-- U+03A9: GREEK CAPITAL LETTER OMEGA -->
+        <td>&#x03BC;x <!-- U+03BC: GREEK SMALL LETTER MU -->
+        <td>&#x03C0;x <!-- U+03C0: GREEK SMALL LETTER PI -->
+        <td>&#x2002;x <!-- U+2002: EN SPACE -->
+        <td>&#x2003;x <!-- U+2003: EM SPACE -->
+        <td>&#x2004;x <!-- U+2004: THREE-PER-EM SPACE -->
+        <td>&#x2005;x <!-- U+2005: FOUR-PER-EM SPACE -->
+        <td>&#x2006;x <!-- U+2006: SIX-PER-EM SPACE -->
+        <td>&#x2009;x <!-- U+2009: THIN SPACE -->
+        <td>&#x200A;x <!-- U+200A: HAIR SPACE -->
+        <td>&#x200B;x <!-- U+200B: ZERO WIDTH SPACE -->
+        <td>&#x200C;x <!-- U+200C: ZERO WIDTH NON-JOINER -->
+        <td>&#x200D;x <!-- U+200D: ZERO WIDTH JOINER -->
+        <td>&#x2010;x <!-- U+2010: HYPHEN -->
+        <td>&#x2013;x <!-- U+2013: EN DASH -->
+        <td>&#x2014;x <!-- U+2014: EM DASH -->
+    <tr>
+        <td>&#x2018;x <!-- U+2018: LEFT SINGLE QUOTATION MARK -->
+        <td>&#x2019;x <!-- U+2019: RIGHT SINGLE QUOTATION MARK -->
+        <td>&#x201A;x <!-- U+201A: SINGLE LOW-9 QUOTATION MARK -->
+        <td>&#x201C;x <!-- U+201C: LEFT DOUBLE QUOTATION MARK -->
+        <td>&#x201D;x <!-- U+201D: RIGHT DOUBLE QUOTATION MARK -->
+        <td>&#x201E;x <!-- U+201E: DOUBLE LOW-9 QUOTATION MARK -->
+        <td>&#x2020;x <!-- U+2020: DAGGER -->
+        <td>&#x2021;x <!-- U+2021: DOUBLE DAGGER -->
+        <td>&#x2022;x <!-- U+2022: BULLET -->
+        <td>&#x2026;x <!-- U+2026: HORIZONTAL ELLIPSIS -->
+        <td>&#x2030;x <!-- U+2030: PER MILLE SIGN -->
+        <td>&#x2039;x <!-- U+2039: SINGLE LEFT-POINTING ANGLE QUOTATION MARK -->
+        <td>&#x203A;x <!-- U+203A: SINGLE RIGHT-POINTING ANGLE QUOTATION MARK -->
+        <td>&#x2044;x <!-- U+2044: FRACTION SLASH -->
+        <td>&#x2122;x <!-- U+2122: TRADE MARK SIGN -->
+        <td>&#x2126;x <!-- U+2126: OHM SIGN -->
+        <td>&#x2202;x <!-- U+2202: PARTIAL DIFFERENTIAL -->
+    <tr>
+        <td>&#x2206;x <!-- U+2206: INCREMENT -->
+        <td>&#x220F;x <!-- U+220F: N-ARY PRODUCT -->
+        <td>&#x2211;x <!-- U+2211: N-ARY SUMMATION -->
+        <td>&#x2212;x <!-- U+2212: MINUS SIGN -->
+        <td>&#x2219;x <!-- U+2219: BULLET OPERATOR -->
+        <td>&#x221A;x <!-- U+221A: SQUARE ROOT -->
+        <td>&#x221E;x <!-- U+221E: INFINITY -->
+        <td>&#x222B;x <!-- U+222B: INTEGRAL -->
+        <td>&#x2248;x <!-- U+2248: ALMOST EQUAL TO -->
+        <td>&#x2260;x <!-- U+2260: NOT EQUAL TO -->
+        <td>&#x2264;x <!-- U+2264: LESS-THAN OR EQUAL TO -->
+        <td>&#x2265;x <!-- U+2265: GREATER-THAN OR EQUAL TO -->
+        <td>&#x22F2;x <!-- U+22F2: ELEMENT OF WITH LONG HORIZONTAL STROKE -->
+        <td>&#x25CA;x <!-- U+25CA: LOZENGE -->
+        <td>&#x3000;x <!-- U+3000: IDEOGRAPHIC SPACE -->
+        <td>&#xF000;x <!-- U+F000 -->
+        <td>&#xF001;x <!-- U+F001 -->
+    <tr>
+        <td>&#xF002;x <!-- U+F002 -->
+        <td>&#xFEFF;x <!-- U+FEFF: ZERO WIDTH NO-BREAK SPACE -->
+</table>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/assumptions/ahem.html b/third_party/WebKit/LayoutTests/external/wpt/assumptions/ahem.html
new file mode 100644
index 0000000..dee1b75c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/assumptions/ahem.html
@@ -0,0 +1,297 @@
+
+<!doctype html>
+<title>Ahem checker</title>
+<link rel="match" href="ahem-ref.html">
+<style>
+* {
+  padding: 0;
+  margin: 0;
+  border: none;
+}
+
+table {
+  font: 15px/1 Ahem;
+  border-collapse: separate;
+  border-spacing: 1px;
+  table-layout: fixed;
+}
+
+td {
+  width: 34px;
+}
+</style>
+<table>
+    <tr>
+        <td>&#x0020;x <!-- U+0020: SPACE -->
+        <td>&#x0021;x <!-- U+0021: EXCLAMATION MARK -->
+        <td>&#x0022;x <!-- U+0022: QUOTATION MARK -->
+        <td>&#x0023;x <!-- U+0023: NUMBER SIGN -->
+        <td>&#x0024;x <!-- U+0024: DOLLAR SIGN -->
+        <td>&#x0025;x <!-- U+0025: PERCENT SIGN -->
+        <td>&#x0026;x <!-- U+0026: AMPERSAND -->
+        <td>&#x0028;x <!-- U+0028: LEFT PARENTHESIS -->
+        <td>&#x0029;x <!-- U+0029: RIGHT PARENTHESIS -->
+        <td>&#x002A;x <!-- U+002A: ASTERISK -->
+        <td>&#x002B;x <!-- U+002B: PLUS SIGN -->
+        <td>&#x002C;x <!-- U+002C: COMMA -->
+        <td>&#x002D;x <!-- U+002D: HYPHEN-MINUS -->
+        <td>&#x002E;x <!-- U+002E: FULL STOP -->
+        <td>&#x002F;x <!-- U+002F: SOLIDUS -->
+        <td>&#x0030;x <!-- U+0030: DIGIT ZERO -->
+        <td>&#x0031;x <!-- U+0031: DIGIT ONE -->
+    <tr>
+        <td>&#x0032;x <!-- U+0032: DIGIT TWO -->
+        <td>&#x0033;x <!-- U+0033: DIGIT THREE -->
+        <td>&#x0034;x <!-- U+0034: DIGIT FOUR -->
+        <td>&#x0035;x <!-- U+0035: DIGIT FIVE -->
+        <td>&#x0036;x <!-- U+0036: DIGIT SIX -->
+        <td>&#x0037;x <!-- U+0037: DIGIT SEVEN -->
+        <td>&#x0038;x <!-- U+0038: DIGIT EIGHT -->
+        <td>&#x0039;x <!-- U+0039: DIGIT NINE -->
+        <td>&#x003A;x <!-- U+003A: COLON -->
+        <td>&#x003B;x <!-- U+003B: SEMICOLON -->
+        <td>&#x003C;x <!-- U+003C: LESS-THAN SIGN -->
+        <td>&#x003D;x <!-- U+003D: EQUALS SIGN -->
+        <td>&#x003E;x <!-- U+003E: GREATER-THAN SIGN -->
+        <td>&#x003F;x <!-- U+003F: QUESTION MARK -->
+        <td>&#x0040;x <!-- U+0040: COMMERCIAL AT -->
+        <td>&#x0041;x <!-- U+0041: LATIN CAPITAL LETTER A -->
+        <td>&#x0042;x <!-- U+0042: LATIN CAPITAL LETTER B -->
+    <tr>
+        <td>&#x0043;x <!-- U+0043: LATIN CAPITAL LETTER C -->
+        <td>&#x0044;x <!-- U+0044: LATIN CAPITAL LETTER D -->
+        <td>&#x0045;x <!-- U+0045: LATIN CAPITAL LETTER E -->
+        <td>&#x0046;x <!-- U+0046: LATIN CAPITAL LETTER F -->
+        <td>&#x0047;x <!-- U+0047: LATIN CAPITAL LETTER G -->
+        <td>&#x0048;x <!-- U+0048: LATIN CAPITAL LETTER H -->
+        <td>&#x0049;x <!-- U+0049: LATIN CAPITAL LETTER I -->
+        <td>&#x004A;x <!-- U+004A: LATIN CAPITAL LETTER J -->
+        <td>&#x004B;x <!-- U+004B: LATIN CAPITAL LETTER K -->
+        <td>&#x004C;x <!-- U+004C: LATIN CAPITAL LETTER L -->
+        <td>&#x004D;x <!-- U+004D: LATIN CAPITAL LETTER M -->
+        <td>&#x004E;x <!-- U+004E: LATIN CAPITAL LETTER N -->
+        <td>&#x004F;x <!-- U+004F: LATIN CAPITAL LETTER O -->
+        <td>&#x0050;x <!-- U+0050: LATIN CAPITAL LETTER P -->
+        <td>&#x0051;x <!-- U+0051: LATIN CAPITAL LETTER Q -->
+        <td>&#x0052;x <!-- U+0052: LATIN CAPITAL LETTER R -->
+        <td>&#x0053;x <!-- U+0053: LATIN CAPITAL LETTER S -->
+    <tr>
+        <td>&#x0054;x <!-- U+0054: LATIN CAPITAL LETTER T -->
+        <td>&#x0055;x <!-- U+0055: LATIN CAPITAL LETTER U -->
+        <td>&#x0056;x <!-- U+0056: LATIN CAPITAL LETTER V -->
+        <td>&#x0057;x <!-- U+0057: LATIN CAPITAL LETTER W -->
+        <td>&#x0058;x <!-- U+0058: LATIN CAPITAL LETTER X -->
+        <td>&#x0059;x <!-- U+0059: LATIN CAPITAL LETTER Y -->
+        <td>&#x005A;x <!-- U+005A: LATIN CAPITAL LETTER Z -->
+        <td>&#x005B;x <!-- U+005B: LEFT SQUARE BRACKET -->
+        <td>&#x005C;x <!-- U+005C: REVERSE SOLIDUS -->
+        <td>&#x005D;x <!-- U+005D: RIGHT SQUARE BRACKET -->
+        <td>&#x005E;x <!-- U+005E: CIRCUMFLEX ACCENT -->
+        <td>&#x005F;x <!-- U+005F: LOW LINE -->
+        <td>&#x0060;x <!-- U+0060: GRAVE ACCENT -->
+        <td>&#x0061;x <!-- U+0061: LATIN SMALL LETTER A -->
+        <td>&#x0062;x <!-- U+0062: LATIN SMALL LETTER B -->
+        <td>&#x0063;x <!-- U+0063: LATIN SMALL LETTER C -->
+        <td>&#x0064;x <!-- U+0064: LATIN SMALL LETTER D -->
+    <tr>
+        <td>&#x0065;x <!-- U+0065: LATIN SMALL LETTER E -->
+        <td>&#x0066;x <!-- U+0066: LATIN SMALL LETTER F -->
+        <td>&#x0067;x <!-- U+0067: LATIN SMALL LETTER G -->
+        <td>&#x0068;x <!-- U+0068: LATIN SMALL LETTER H -->
+        <td>&#x0069;x <!-- U+0069: LATIN SMALL LETTER I -->
+        <td>&#x006A;x <!-- U+006A: LATIN SMALL LETTER J -->
+        <td>&#x006B;x <!-- U+006B: LATIN SMALL LETTER K -->
+        <td>&#x006C;x <!-- U+006C: LATIN SMALL LETTER L -->
+        <td>&#x006D;x <!-- U+006D: LATIN SMALL LETTER M -->
+        <td>&#x006E;x <!-- U+006E: LATIN SMALL LETTER N -->
+        <td>&#x006F;x <!-- U+006F: LATIN SMALL LETTER O -->
+        <td>&#x0070;x <!-- U+0070: LATIN SMALL LETTER P -->
+        <td>&#x0071;x <!-- U+0071: LATIN SMALL LETTER Q -->
+        <td>&#x0072;x <!-- U+0072: LATIN SMALL LETTER R -->
+        <td>&#x0073;x <!-- U+0073: LATIN SMALL LETTER S -->
+        <td>&#x0074;x <!-- U+0074: LATIN SMALL LETTER T -->
+        <td>&#x0075;x <!-- U+0075: LATIN SMALL LETTER U -->
+    <tr>
+        <td>&#x0076;x <!-- U+0076: LATIN SMALL LETTER V -->
+        <td>&#x0077;x <!-- U+0077: LATIN SMALL LETTER W -->
+        <td>&#x0078;x <!-- U+0078: LATIN SMALL LETTER X -->
+        <td>&#x0079;x <!-- U+0079: LATIN SMALL LETTER Y -->
+        <td>&#x007A;x <!-- U+007A: LATIN SMALL LETTER Z -->
+        <td>&#x007B;x <!-- U+007B: LEFT CURLY BRACKET -->
+        <td>&#x007C;x <!-- U+007C: VERTICAL LINE -->
+        <td>&#x007D;x <!-- U+007D: RIGHT CURLY BRACKET -->
+        <td>&#x007E;x <!-- U+007E: TILDE -->
+        <td>&#x00A0;x <!-- U+00A0: NO-BREAK SPACE -->
+        <td>&#x00A1;x <!-- U+00A1: INVERTED EXCLAMATION MARK -->
+        <td>&#x00A2;x <!-- U+00A2: CENT SIGN -->
+        <td>&#x00A3;x <!-- U+00A3: POUND SIGN -->
+        <td>&#x00A4;x <!-- U+00A4: CURRENCY SIGN -->
+        <td>&#x00A5;x <!-- U+00A5: YEN SIGN -->
+        <td>&#x00A6;x <!-- U+00A6: BROKEN BAR -->
+        <td>&#x00A7;x <!-- U+00A7: SECTION SIGN -->
+    <tr>
+        <td>&#x00A8;x <!-- U+00A8: DIAERESIS -->
+        <td>&#x00A9;x <!-- U+00A9: COPYRIGHT SIGN -->
+        <td>&#x00AA;x <!-- U+00AA: FEMININE ORDINAL INDICATOR -->
+        <td>&#x00AB;x <!-- U+00AB: LEFT-POINTING DOUBLE ANGLE QUOTATION MARK -->
+        <td>&#x00AC;x <!-- U+00AC: NOT SIGN -->
+        <td>&#x00AD;x <!-- U+00AD: SOFT HYPHEN -->
+        <td>&#x00AE;x <!-- U+00AE: REGISTERED SIGN -->
+        <td>&#x00AF;x <!-- U+00AF: MACRON -->
+        <td>&#x00B0;x <!-- U+00B0: DEGREE SIGN -->
+        <td>&#x00B1;x <!-- U+00B1: PLUS-MINUS SIGN -->
+        <td>&#x00B2;x <!-- U+00B2: SUPERSCRIPT TWO -->
+        <td>&#x00B3;x <!-- U+00B3: SUPERSCRIPT THREE -->
+        <td>&#x00B4;x <!-- U+00B4: ACUTE ACCENT -->
+        <td>&#x00B5;x <!-- U+00B5: MICRO SIGN -->
+        <td>&#x00B6;x <!-- U+00B6: PILCROW SIGN -->
+        <td>&#x00B7;x <!-- U+00B7: MIDDLE DOT -->
+        <td>&#x00B8;x <!-- U+00B8: CEDILLA -->
+    <tr>
+        <td>&#x00B9;x <!-- U+00B9: SUPERSCRIPT ONE -->
+        <td>&#x00BA;x <!-- U+00BA: MASCULINE ORDINAL INDICATOR -->
+        <td>&#x00BB;x <!-- U+00BB: RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK -->
+        <td>&#x00BC;x <!-- U+00BC: VULGAR FRACTION ONE QUARTER -->
+        <td>&#x00BD;x <!-- U+00BD: VULGAR FRACTION ONE HALF -->
+        <td>&#x00BE;x <!-- U+00BE: VULGAR FRACTION THREE QUARTERS -->
+        <td>&#x00BF;x <!-- U+00BF: INVERTED QUESTION MARK -->
+        <td>&#x00C0;x <!-- U+00C0: LATIN CAPITAL LETTER A WITH GRAVE -->
+        <td>&#x00C1;x <!-- U+00C1: LATIN CAPITAL LETTER A WITH ACUTE -->
+        <td>&#x00C2;x <!-- U+00C2: LATIN CAPITAL LETTER A WITH CIRCUMFLEX -->
+        <td>&#x00C3;x <!-- U+00C3: LATIN CAPITAL LETTER A WITH TILDE -->
+        <td>&#x00C4;x <!-- U+00C4: LATIN CAPITAL LETTER A WITH DIAERESIS -->
+        <td>&#x00C5;x <!-- U+00C5: LATIN CAPITAL LETTER A WITH RING ABOVE -->
+        <td>&#x00C6;x <!-- U+00C6: LATIN CAPITAL LETTER AE -->
+        <td>&#x00C7;x <!-- U+00C7: LATIN CAPITAL LETTER C WITH CEDILLA -->
+        <td>&#x00C8;x <!-- U+00C8: LATIN CAPITAL LETTER E WITH GRAVE -->
+        <td>&#x00C9;x <!-- U+00C9: LATIN CAPITAL LETTER E WITH ACUTE -->
+    <tr>
+        <td>&#x00CA;x <!-- U+00CA: LATIN CAPITAL LETTER E WITH CIRCUMFLEX -->
+        <td>&#x00CB;x <!-- U+00CB: LATIN CAPITAL LETTER E WITH DIAERESIS -->
+        <td>&#x00CC;x <!-- U+00CC: LATIN CAPITAL LETTER I WITH GRAVE -->
+        <td>&#x00CD;x <!-- U+00CD: LATIN CAPITAL LETTER I WITH ACUTE -->
+        <td>&#x00CE;x <!-- U+00CE: LATIN CAPITAL LETTER I WITH CIRCUMFLEX -->
+        <td>&#x00CF;x <!-- U+00CF: LATIN CAPITAL LETTER I WITH DIAERESIS -->
+        <td>&#x00D0;x <!-- U+00D0: LATIN CAPITAL LETTER ETH -->
+        <td>&#x00D1;x <!-- U+00D1: LATIN CAPITAL LETTER N WITH TILDE -->
+        <td>&#x00D2;x <!-- U+00D2: LATIN CAPITAL LETTER O WITH GRAVE -->
+        <td>&#x00D3;x <!-- U+00D3: LATIN CAPITAL LETTER O WITH ACUTE -->
+        <td>&#x00D4;x <!-- U+00D4: LATIN CAPITAL LETTER O WITH CIRCUMFLEX -->
+        <td>&#x00D5;x <!-- U+00D5: LATIN CAPITAL LETTER O WITH TILDE -->
+        <td>&#x00D6;x <!-- U+00D6: LATIN CAPITAL LETTER O WITH DIAERESIS -->
+        <td>&#x00D7;x <!-- U+00D7: MULTIPLICATION SIGN -->
+        <td>&#x00D8;x <!-- U+00D8: LATIN CAPITAL LETTER O WITH STROKE -->
+        <td>&#x00D9;x <!-- U+00D9: LATIN CAPITAL LETTER U WITH GRAVE -->
+        <td>&#x00DA;x <!-- U+00DA: LATIN CAPITAL LETTER U WITH ACUTE -->
+    <tr>
+        <td>&#x00DB;x <!-- U+00DB: LATIN CAPITAL LETTER U WITH CIRCUMFLEX -->
+        <td>&#x00DC;x <!-- U+00DC: LATIN CAPITAL LETTER U WITH DIAERESIS -->
+        <td>&#x00DD;x <!-- U+00DD: LATIN CAPITAL LETTER Y WITH ACUTE -->
+        <td>&#x00DE;x <!-- U+00DE: LATIN CAPITAL LETTER THORN -->
+        <td>&#x00DF;x <!-- U+00DF: LATIN SMALL LETTER SHARP S -->
+        <td>&#x00E0;x <!-- U+00E0: LATIN SMALL LETTER A WITH GRAVE -->
+        <td>&#x00E1;x <!-- U+00E1: LATIN SMALL LETTER A WITH ACUTE -->
+        <td>&#x00E2;x <!-- U+00E2: LATIN SMALL LETTER A WITH CIRCUMFLEX -->
+        <td>&#x00E3;x <!-- U+00E3: LATIN SMALL LETTER A WITH TILDE -->
+        <td>&#x00E4;x <!-- U+00E4: LATIN SMALL LETTER A WITH DIAERESIS -->
+        <td>&#x00E5;x <!-- U+00E5: LATIN SMALL LETTER A WITH RING ABOVE -->
+        <td>&#x00E6;x <!-- U+00E6: LATIN SMALL LETTER AE -->
+        <td>&#x00E7;x <!-- U+00E7: LATIN SMALL LETTER C WITH CEDILLA -->
+        <td>&#x00E8;x <!-- U+00E8: LATIN SMALL LETTER E WITH GRAVE -->
+        <td>&#x00E9;x <!-- U+00E9: LATIN SMALL LETTER E WITH ACUTE -->
+        <td>&#x00EA;x <!-- U+00EA: LATIN SMALL LETTER E WITH CIRCUMFLEX -->
+        <td>&#x00EB;x <!-- U+00EB: LATIN SMALL LETTER E WITH DIAERESIS -->
+    <tr>
+        <td>&#x00EC;x <!-- U+00EC: LATIN SMALL LETTER I WITH GRAVE -->
+        <td>&#x00ED;x <!-- U+00ED: LATIN SMALL LETTER I WITH ACUTE -->
+        <td>&#x00EE;x <!-- U+00EE: LATIN SMALL LETTER I WITH CIRCUMFLEX -->
+        <td>&#x00EF;x <!-- U+00EF: LATIN SMALL LETTER I WITH DIAERESIS -->
+        <td>&#x00F0;x <!-- U+00F0: LATIN SMALL LETTER ETH -->
+        <td>&#x00F1;x <!-- U+00F1: LATIN SMALL LETTER N WITH TILDE -->
+        <td>&#x00F2;x <!-- U+00F2: LATIN SMALL LETTER O WITH GRAVE -->
+        <td>&#x00F3;x <!-- U+00F3: LATIN SMALL LETTER O WITH ACUTE -->
+        <td>&#x00F4;x <!-- U+00F4: LATIN SMALL LETTER O WITH CIRCUMFLEX -->
+        <td>&#x00F5;x <!-- U+00F5: LATIN SMALL LETTER O WITH TILDE -->
+        <td>&#x00F6;x <!-- U+00F6: LATIN SMALL LETTER O WITH DIAERESIS -->
+        <td>&#x00F7;x <!-- U+00F7: DIVISION SIGN -->
+        <td>&#x00F8;x <!-- U+00F8: LATIN SMALL LETTER O WITH STROKE -->
+        <td>&#x00F9;x <!-- U+00F9: LATIN SMALL LETTER U WITH GRAVE -->
+        <td>&#x00FA;x <!-- U+00FA: LATIN SMALL LETTER U WITH ACUTE -->
+        <td>&#x00FB;x <!-- U+00FB: LATIN SMALL LETTER U WITH CIRCUMFLEX -->
+        <td>&#x00FC;x <!-- U+00FC: LATIN SMALL LETTER U WITH DIAERESIS -->
+    <tr>
+        <td>&#x00FD;x <!-- U+00FD: LATIN SMALL LETTER Y WITH ACUTE -->
+        <td>&#x00FE;x <!-- U+00FE: LATIN SMALL LETTER THORN -->
+        <td>&#x00FF;x <!-- U+00FF: LATIN SMALL LETTER Y WITH DIAERESIS -->
+        <td>&#x0131;x <!-- U+0131: LATIN SMALL LETTER DOTLESS I -->
+        <td>&#x0152;x <!-- U+0152: LATIN CAPITAL LIGATURE OE -->
+        <td>&#x0153;x <!-- U+0153: LATIN SMALL LIGATURE OE -->
+        <td>&#x0178;x <!-- U+0178: LATIN CAPITAL LETTER Y WITH DIAERESIS -->
+        <td>&#x0192;x <!-- U+0192: LATIN SMALL LETTER F WITH HOOK -->
+        <td>&#x02C6;x <!-- U+02C6: MODIFIER LETTER CIRCUMFLEX ACCENT -->
+        <td>&#x02C7;x <!-- U+02C7: CARON -->
+        <td>&#x02C9;x <!-- U+02C9: MODIFIER LETTER MACRON -->
+        <td>&#x02D8;x <!-- U+02D8: BREVE -->
+        <td>&#x02D9;x <!-- U+02D9: DOT ABOVE -->
+        <td>&#x02DA;x <!-- U+02DA: RING ABOVE -->
+        <td>&#x02DB;x <!-- U+02DB: OGONEK -->
+        <td>&#x02DC;x <!-- U+02DC: SMALL TILDE -->
+        <td>&#x02DD;x <!-- U+02DD: DOUBLE ACUTE ACCENT -->
+    <tr>
+        <td>&#x0394;x <!-- U+0394: GREEK CAPITAL LETTER DELTA -->
+        <td>&#x03A9;x <!-- U+03A9: GREEK CAPITAL LETTER OMEGA -->
+        <td>&#x03BC;x <!-- U+03BC: GREEK SMALL LETTER MU -->
+        <td>&#x03C0;x <!-- U+03C0: GREEK SMALL LETTER PI -->
+        <td>&#x2002;x <!-- U+2002: EN SPACE -->
+        <td>&#x2003;x <!-- U+2003: EM SPACE -->
+        <td>&#x2004;x <!-- U+2004: THREE-PER-EM SPACE -->
+        <td>&#x2005;x <!-- U+2005: FOUR-PER-EM SPACE -->
+        <td>&#x2006;x <!-- U+2006: SIX-PER-EM SPACE -->
+        <td>&#x2009;x <!-- U+2009: THIN SPACE -->
+        <td>&#x200A;x <!-- U+200A: HAIR SPACE -->
+        <td>&#x200B;x <!-- U+200B: ZERO WIDTH SPACE -->
+        <td>&#x200C;x <!-- U+200C: ZERO WIDTH NON-JOINER -->
+        <td>&#x200D;x <!-- U+200D: ZERO WIDTH JOINER -->
+        <td>&#x2010;x <!-- U+2010: HYPHEN -->
+        <td>&#x2013;x <!-- U+2013: EN DASH -->
+        <td>&#x2014;x <!-- U+2014: EM DASH -->
+    <tr>
+        <td>&#x2018;x <!-- U+2018: LEFT SINGLE QUOTATION MARK -->
+        <td>&#x2019;x <!-- U+2019: RIGHT SINGLE QUOTATION MARK -->
+        <td>&#x201A;x <!-- U+201A: SINGLE LOW-9 QUOTATION MARK -->
+        <td>&#x201C;x <!-- U+201C: LEFT DOUBLE QUOTATION MARK -->
+        <td>&#x201D;x <!-- U+201D: RIGHT DOUBLE QUOTATION MARK -->
+        <td>&#x201E;x <!-- U+201E: DOUBLE LOW-9 QUOTATION MARK -->
+        <td>&#x2020;x <!-- U+2020: DAGGER -->
+        <td>&#x2021;x <!-- U+2021: DOUBLE DAGGER -->
+        <td>&#x2022;x <!-- U+2022: BULLET -->
+        <td>&#x2026;x <!-- U+2026: HORIZONTAL ELLIPSIS -->
+        <td>&#x2030;x <!-- U+2030: PER MILLE SIGN -->
+        <td>&#x2039;x <!-- U+2039: SINGLE LEFT-POINTING ANGLE QUOTATION MARK -->
+        <td>&#x203A;x <!-- U+203A: SINGLE RIGHT-POINTING ANGLE QUOTATION MARK -->
+        <td>&#x2044;x <!-- U+2044: FRACTION SLASH -->
+        <td>&#x2122;x <!-- U+2122: TRADE MARK SIGN -->
+        <td>&#x2126;x <!-- U+2126: OHM SIGN -->
+        <td>&#x2202;x <!-- U+2202: PARTIAL DIFFERENTIAL -->
+    <tr>
+        <td>&#x2206;x <!-- U+2206: INCREMENT -->
+        <td>&#x220F;x <!-- U+220F: N-ARY PRODUCT -->
+        <td>&#x2211;x <!-- U+2211: N-ARY SUMMATION -->
+        <td>&#x2212;x <!-- U+2212: MINUS SIGN -->
+        <td>&#x2219;x <!-- U+2219: BULLET OPERATOR -->
+        <td>&#x221A;x <!-- U+221A: SQUARE ROOT -->
+        <td>&#x221E;x <!-- U+221E: INFINITY -->
+        <td>&#x222B;x <!-- U+222B: INTEGRAL -->
+        <td>&#x2248;x <!-- U+2248: ALMOST EQUAL TO -->
+        <td>&#x2260;x <!-- U+2260: NOT EQUAL TO -->
+        <td>&#x2264;x <!-- U+2264: LESS-THAN OR EQUAL TO -->
+        <td>&#x2265;x <!-- U+2265: GREATER-THAN OR EQUAL TO -->
+        <td>&#x22F2;x <!-- U+22F2: ELEMENT OF WITH LONG HORIZONTAL STROKE -->
+        <td>&#x25CA;x <!-- U+25CA: LOZENGE -->
+        <td>&#x3000;x <!-- U+3000: IDEOGRAPHIC SPACE -->
+        <td>&#xF000;x <!-- U+F000 -->
+        <td>&#xF001;x <!-- U+F001 -->
+    <tr>
+        <td>&#xF002;x <!-- U+F002 -->
+        <td>&#xFEFF;x <!-- U+FEFF: ZERO WIDTH NO-BREAK SPACE -->
+</table>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/check_stability.py b/third_party/WebKit/LayoutTests/external/wpt/check_stability.py
index a306c6a..af8d5278 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/check_stability.py
+++ b/third_party/WebKit/LayoutTests/external/wpt/check_stability.py
@@ -13,7 +13,6 @@
 from abc import ABCMeta, abstractmethod
 from cStringIO import StringIO as CStringIO
 from collections import defaultdict, OrderedDict
-from distutils.spawn import find_executable
 from io import BytesIO, StringIO
 
 import requests
@@ -173,12 +172,6 @@
     def wptrunner_args(self):
         return NotImplemented
 
-    def prepare_environment(self):
-        """Do any additional setup of the environment required to start the
-           browser successfully
-        """
-        pass
-
 
 class Firefox(Browser):
     """Firefox-specific interface.
@@ -296,26 +289,6 @@
             "test_types": ["testharness", "reftest"]
         }
 
-    def prepare_environment(self):
-        # https://bugs.chromium.org/p/chromium/issues/detail?id=713947
-        logger.debug("DBUS_SESSION_BUS_ADDRESS %s" % os.environ.get("DBUS_SESSION_BUS_ADDRESS"))
-        if "DBUS_SESSION_BUS_ADDRESS" not in os.environ:
-            if find_executable("dbus-launch"):
-                logger.debug("Attempting to start dbus")
-                dbus_conf = subprocess.check_output(["dbus-launch"])
-                logger.debug(dbus_conf)
-
-                # From dbus-launch(1):
-                #
-                # > When dbus-launch prints bus information to standard output,
-                # > by default it is in a simple key-value pairs format.
-                for line in dbus_conf.strip().split("\n"):
-                    key, _, value = line.partition("=")
-                    os.environ[key] = value
-            else:
-                logger.critical("dbus not running and can't be started")
-                sys.exit(1)
-
 
 class Sauce(Browser):
     """Sauce-specific interface.
@@ -977,8 +950,6 @@
                                 args.iterations,
                                 browser)
 
-        browser.prepare_environment()
-
     with TravisFold("running_tests"):
         logger.info("Starting %i test iterations" % args.iterations)
         with open("raw.log", "wb") as log:
diff --git a/third_party/WebKit/LayoutTests/external/wpt/content-security-policy/blink-contrib/inline-style-attribute-on-html.sub.html b/third_party/WebKit/LayoutTests/external/wpt/content-security-policy/blink-contrib/inline-style-attribute-on-html.sub.html
index a0b9ed1c..b002af9 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/content-security-policy/blink-contrib/inline-style-attribute-on-html.sub.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/content-security-policy/blink-contrib/inline-style-attribute-on-html.sub.html
@@ -15,7 +15,7 @@
 </head>
 
 <body>
-    <p>Even though this page has a CSP policy the blocks inline style, the style attribute on the HTML element still takes effect because it precedes the meta element.
+    <p>Even though this page has a CSP policy the blocks inline style, the style attribute on the HTML element still takes effect because it preceeds the meta element.
     </p>
     <script>
         log(document.documentElement.style.length > 0 ? 'PASS' : 'FAIL');
diff --git a/third_party/WebKit/LayoutTests/external/wpt/content-security-policy/support/checkReport.sub.js b/third_party/WebKit/LayoutTests/external/wpt/content-security-policy/support/checkReport.sub.js
index ab52b7c..803dc06 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/content-security-policy/support/checkReport.sub.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/content-security-policy/support/checkReport.sub.js
@@ -51,7 +51,7 @@
           }
           // Firefox expands 'self' or origins in a policy to the actual origin value
           // so "www.example.com" becomes "http://www.example.com:80".
-          // Accommodate this by just testing that the correct directive name
+          // Accomodate this by just testing that the correct directive name
           // is reported, not the details...
 
           if(data["csp-report"] != undefined && data["csp-report"][reportField] != undefined) {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/cors/redirect-preflight.htm b/third_party/WebKit/LayoutTests/external/wpt/cors/redirect-preflight.htm
index f4af1243..ff64284 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/cors/redirect-preflight.htm
+++ b/third_party/WebKit/LayoutTests/external/wpt/cors/redirect-preflight.htm
@@ -20,10 +20,10 @@
 var CROSSDOMAIN_URL = CROSSDOMAIN + 'resources/cors-makeheader.py?';
 
 /*
- * Redirection after successful (200) preflight.
+ * Redirection after successfull (200) preflight.
  */
 function redir_after_successfull_preflight(code) {
-  var desc = 'Should allow redirect ' + code + ' after successful (200) preflight';
+  var desc = 'Should allow redirect ' + code + ' after succesful (200) preflight';
   async_test(desc).step(function() {
     var client = new XMLHttpRequest();
     var redirect = encodeURIComponent(
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/floats-clear/margin-collapse-033.xht b/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/floats-clear/margin-collapse-033.xht
index 53c55c4..89c8af3 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/floats-clear/margin-collapse-033.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/floats-clear/margin-collapse-033.xht
@@ -12,7 +12,7 @@
   <link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#flow-control" />
   <link rel="match" href="../reference/ref-filled-green-100px-square.xht" />
   <meta content="" name="flags" />
-  <meta content="Margin-top of following siblings of a block on which 'clear' has been set (to a different value than 'none') must not be subtracted when calculating clearance. When an element's own margins collapse, and that element has had clearance applied to it, its top margin collapses with the adjoining margins of following siblings." name="assert" />
+  <meta content="Margin-top of following siblings of a block on which 'clear' has been set (to a different value than 'none') must not be substracted when calculating clearance. When an element's own margins collapse, and that element has had clearance applied to it, its top margin collapses with the adjoining margins of following siblings." name="assert" />
 
   <style type="text/css"><![CDATA[
   div#overlapped-red
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/floats-clear/margin-collapse-034.xht b/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/floats-clear/margin-collapse-034.xht
index 3c0e1a36..17c11e5 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/floats-clear/margin-collapse-034.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/floats-clear/margin-collapse-034.xht
@@ -12,7 +12,7 @@
   <link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#flow-control" />
   <link rel="match" href="../reference/ref-filled-green-100px-square.xht" />
   <meta content="" name="flags" />
-  <meta content="Margin-top of following siblings of a block on which 'clear' has been set (to a different value than 'none') must not be subtracted when calculating clearance. When an element's own margins collapse, and that element has had clearance applied to it, its top margin collapses with the adjoining margins of following siblings." name="assert" />
+  <meta content="Margin-top of following siblings of a block on which 'clear' has been set (to a different value than 'none') must not be substracted when calculating clearance. When an element's own margins collapse, and that element has had clearance applied to it, its top margin collapses with the adjoining margins of following siblings." name="assert" />
 
   <style type="text/css"><![CDATA[
   div#overlapped-red
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/floats-clear/margin-collapse-035.xht b/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/floats-clear/margin-collapse-035.xht
index 87b1b92..608bdd9 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/floats-clear/margin-collapse-035.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/floats-clear/margin-collapse-035.xht
@@ -12,7 +12,7 @@
   <link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#flow-control" />
   <link rel="match" href="../reference/ref-filled-green-100px-square.xht" />
   <meta content="" name="flags" />
-  <meta content="Margin-top of following siblings of a block on which 'clear' has been set (to a different value than 'none') must not be subtracted when calculating clearance. When an element's own margins collapse, and that element has had clearance applied to it, its top margin collapses with the adjoining margins of following siblings." name="assert" />
+  <meta content="Margin-top of following siblings of a block on which 'clear' has been set (to a different value than 'none') must not be substracted when calculating clearance. When an element's own margins collapse, and that element has had clearance applied to it, its top margin collapses with the adjoining margins of following siblings." name="assert" />
 
   <style type="text/css"><![CDATA[
   div#overlapped-red
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/normal-flow/inline-block-005.xht b/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/normal-flow/inline-block-005.xht
index bc41ad1..e710e49 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/normal-flow/inline-block-005.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/normal-flow/inline-block-005.xht
@@ -22,7 +22,7 @@
   line is short enough, should end up wrapping onto two lines internally. In addition
   each inline-block has 5em indentation on the left and 5em padding on the right,
   which should make no difference except that the boxes should be slightly wider than
-   <a href="002.html">before</a>. They shouldn't wrap particularly more than before.</p>
+   <a href="002.html">before</a>. They shouldn't wrap particularily more than before.</p>
   <p>
    <span>this is an inline-block this is an inline-block</span>
    <span>this is an inline-block this is an inline-block</span>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/normal-flow/max-height-111.xht b/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/normal-flow/max-height-111.xht
index 13cc8f3..52af5a5ba 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/normal-flow/max-height-111.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/normal-flow/max-height-111.xht
@@ -11,7 +11,7 @@
   <link rel="help" href="http://www.w3.org/TR/CSS21/visudet.html#min-max-heights" />
   <link rel="help" href="http://www.w3.org/TR/CSS21/visufx.html#overflow" />
   <meta content="ahem" name="flags" />
-  <meta content="The content of the #test-red-overlapped generates an active horizontal scrollbar. The height of such horizontal scrollbar adds itself to the content making it exceed the max-height constraint of 200px. Therefore, such vertical space taken by the horizontal scrollbar must be subtracted from the height of the content. An active vertical scrollbar then must be generated to provide access to the equivalent of the height of the horizontal scrollbar." name="assert" />
+  <meta content="The content of the #test-red-overlapped generates an active horizontal scrollbar. The height of such horizontal scrollbar adds itself to the content making it exceed the max-height constraint of 200px. Therefore, such vertical space taken by the horizontal scrollbar must be substracted from the height of the content. An active vertical scrollbar then must be generated to provide access to the equivalent of the height of the horizontal scrollbar." name="assert" />
 
   <style type="text/css"><![CDATA[
   div
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-grid-1/grid-definition/fr-unit-with-percentage.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-grid-1/grid-definition/fr-unit-with-percentage.html
index 82c97ab..9e6ec9f 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-grid-1/grid-definition/fr-unit-with-percentage.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-grid-1/grid-definition/fr-unit-with-percentage.html
@@ -1,7 +1,7 @@
 <!DOCTYPE HTML>
 <html>
 <head>
-  <title>CSS3 Grid Layout: Flexible Length</title>
+  <title>CSS3 Grid Layout: Flexible Lenght</title>
   <link rel="author" title="swain" href="mailto:swainet@126.com"/>
   <link rel="reviewer" title="Dayang Shen" href="mailto:shendayang@baidu.com"/> <!-- 2013-09-22 -->
   <link rel="help" href="http://www.w3.org/TR/css-grid-1/#fr-unit"/>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-rhythm-1/line-height-step-parsing-001.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-rhythm-1/line-height-step-parsing-001.html
index 7d45d598..3febf057 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-rhythm-1/line-height-step-parsing-001.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-rhythm-1/line-height-step-parsing-001.html
@@ -17,7 +17,7 @@
 
   <div title="'0' should be a valid length" style="line-height-step: 0" data-expected="0px"></div>
 
-  <div title="Integer should be invalid" style="line-height-step: 1" data-expected="20px"></div>
+  <div title="Interger should be invalid" style="line-height-step: 1" data-expected="20px"></div>
   <div title="Negative length should be invalid" style="line-height-step: -1px" data-expected="20px"></div>
 </div>
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-shapes-1/shape-outside/supported-shapes/polygon/shape-outside-polygon-015.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-shapes-1/shape-outside/supported-shapes/polygon/shape-outside-polygon-015.html
index 8338c6c..084f060 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-shapes-1/shape-outside/supported-shapes/polygon/shape-outside-polygon-015.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-shapes-1/shape-outside/supported-shapes/polygon/shape-outside-polygon-015.html
@@ -10,7 +10,7 @@
         <meta name="flags" content="ahem" />
         <meta name="assert" content="The test verifies that text wraps around a
                                      right float with a shape-outside defined as
-                                     an polygon from the content box with a shape margin.">
+                                     an polygon from the content box wtih a shape margin.">
     </head>
     <style>
         body {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-shapes-1/shape-outside/values/shape-outside-shape-none-000.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-shapes-1/shape-outside/values/shape-outside-shape-none-000.html
index b3a5e15..9f7ffce2 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-shapes-1/shape-outside/values/shape-outside-shape-none-000.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-shapes-1/shape-outside/values/shape-outside-shape-none-000.html
@@ -6,7 +6,7 @@
         <link rel="author" title="Rebecca Hauck" href="mailto:rhauck@adobe.com">
         <link rel="reviewer" title="Alan Stearns" href="mailto:stearns@adobe.com"> <!-- 2014-03-04 -->
         <link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property">
-        <meta name="assert" content="shape-outside can be explicitly assigned the default value of none.">
+        <meta name="assert" content="shape-outside can be explictly assigned the default value of none.">
         <meta name="flags" content="dom">
         <script src="/resources/testharness.js"></script>
         <script src="/resources/testharnessreport.js"></script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-transforms-2/rotateY.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-transforms-2/rotateY.html
index a90b7499..593969a7 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-transforms-2/rotateY.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-transforms-2/rotateY.html
@@ -6,7 +6,7 @@
     <link rel="reviewer" title="Dayang Shen" href="mailto:shendayang@baidu.com"> <!-- 2013-09-05 -->
     <link rel="help" href="http://www.w3.org/TR/css-transforms-2/#funcdef-rotatey">
     <link rel="match" href="reference/rotateY-ref.html">
-    <meta name="assert" content="When the value of transform is 'rotateY(90deg)', the forward side of a transformed element disappears.">
+    <meta name="assert" content="When the value of transform is 'rotateY(90deg)', the foward side of a transformed element disappears.">
     <style type="text/css">
         .greenSquare {
             position: absolute;
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/box-sizing-007.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/box-sizing-007.html
index 74fee24..c51f431 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/box-sizing-007.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/box-sizing-007.html
@@ -7,7 +7,7 @@
 <link rel="help" href="http://www.w3.org/TR/CSS21/visudet.html#inline-replaced-height">
 <meta name="flags" content="svg">
 <link rel="match" href="reference/box-sizing-007-ref.html">
-<meta name="assert" content="Exercises the sizing rules in CSS2.1 10.3.2 and 10.6.2 with box-sizing:border-box for replaced elements with either both intrisic dimensions or an intrinsic ratio, to check that they work correctly in terms of the content width height.">
+<meta name="assert" content="Exercises the sizing rules in CSS2.1 10.3.2 and 10.6.2 with box-sizing:border-box for replaced elements with either both intrisic dimentions or an intrinsic ratio, to check that they work correctly in terms of the content width height.">
 <style>
 img {
 	box-sizing: border-box;
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/nav-dir-missing-1.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/nav-dir-missing-1.html
index 52d396b..7d2b760 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/nav-dir-missing-1.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/nav-dir-missing-1.html
@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<title>CSS Basic User Interface Test: Directional Focus Navigation - unknown element id</title>
+<title>CSS Basic User Interface Test: Directional Focus Navigation - unkown element id</title>
 <link rel="author" title="Florian Rivoal" href="mailto:florian@rivoal.net">
 <link rel="help" href="http://www.w3.org/TR/css3-ui/#nav-dir">
 <meta name="flags" content="interact">
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/nav-dir-missing-2.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/nav-dir-missing-2.html
index b827ccb..a4cd062 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/nav-dir-missing-2.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/nav-dir-missing-2.html
@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<title>CSS Basic User Interface Test: Directional Focus Navigation - unknown element id</title>
+<title>CSS Basic User Interface Test: Directional Focus Navigation - unkown element id</title>
 <link rel="author" title="Florian Rivoal" href="mailto:florian@rivoal.net">
 <link rel="help" href="http://www.w3.org/TR/css3-ui/#nav-dir">
 <meta name="flags" content="interact">
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/nav-dir-missing-3.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/nav-dir-missing-3.html
index 7e6c1c9..ea424c1 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/nav-dir-missing-3.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/nav-dir-missing-3.html
@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<title>CSS Basic User Interface Test: Directional Focus Navigation - unknown element id</title>
+<title>CSS Basic User Interface Test: Directional Focus Navigation - unkown element id</title>
 <link rel="author" title="Florian Rivoal" href="mailto:florian@rivoal.net">
 <link rel="help" href="http://www.w3.org/TR/css3-ui/#nav-dir">
 <meta name="flags" content="interact">
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/nav-dir-missing-4.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/nav-dir-missing-4.html
index 933adfc1..25d2552 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/nav-dir-missing-4.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/nav-dir-missing-4.html
@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<title>CSS Basic User Interface Test: Directional Focus Navigation - unknown element id</title>
+<title>CSS Basic User Interface Test: Directional Focus Navigation - unkown element id</title>
 <link rel="author" title="Florian Rivoal" href="mailto:florian@rivoal.net">
 <link rel="help" href="http://www.w3.org/TR/css3-ui/#nav-dir">
 <meta name="flags" content="interact">
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/text-overflow-005.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/text-overflow-005.html
index aae60cf35..e50a802 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/text-overflow-005.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-ui-3/text-overflow-005.html
@@ -2,7 +2,7 @@
 <html class="reftest-wait">
 <meta charset="utf-8">
 <title>CSS-UI test: text-overflow reflow</title>
-<meta name="assert" content="Text overflow should disappear when the container becomes large enough. This test is targeted at bug #14952 in Servo's incremental reflow engine.">
+<meta name="assert" content="Text overflow should disappear when the container becomes large enough. This test is targetted at bug #14952 in Servo's incremental reflow engine.">
 <link rel="author" title="Michael Howell" href="mailto:michael@notriddle.com">
 <link rel="help" title="8.2. Overflow Ellipsis: the 'text-overflow' property" href="http://www.w3.org/TR/css3-ui/#text-overflow">
 <link rel="match" href="reference/text-overflow-005-ref.html">
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/contiguous-floated-table-vlr-007.xht b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/contiguous-floated-table-vlr-007.xht
index a02689e..34faf64 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/contiguous-floated-table-vlr-007.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/contiguous-floated-table-vlr-007.xht
@@ -16,7 +16,7 @@
   <link rel="match" href="../reference/ref-filled-green-100px-square.xht" />
 
   <meta content="image" name="flags" />
-  <meta content="This test checks that horizontal margins existing between contiguous floated tables with writing-mode set to 'vertical-lr' are not subtracted by the amount of their borders. In this test, there should be an horizontal gap of 50px separating both tables. Margins between 2 floated boxes do not collapse." name="assert" />
+  <meta content="This test checks that horizontal margins existing between contiguous floated tables with writing-mode set to 'vertical-lr' are not substracted by the amount of their borders. In this test, there should be an horizontal gap of 50px separating both tables. Margins between 2 floated boxes do not collapse." name="assert" />
 
   <style type="text/css"><![CDATA[
   table
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/contiguous-floated-table-vlr-009.xht b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/contiguous-floated-table-vlr-009.xht
index 1679dae..39fd43e0 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/contiguous-floated-table-vlr-009.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/contiguous-floated-table-vlr-009.xht
@@ -16,7 +16,7 @@
   <link rel="match" href="../reference/ref-filled-green-100px-square.xht" />
 
   <meta content="image" name="flags" />
-  <meta content="This test checks that horizontal margins existing between contiguous floated tables with writing-mode set to 'vertical-lr' are not subtracted by the amount of their horizontal padding. In this test, there should be an horizontal gap of 50px separating both tables. Margins between 2 floated boxes do not collapse." name="assert" />
+  <meta content="This test checks that horizontal margins existing between contiguous floated tables with writing-mode set to 'vertical-lr' are not substracted by the amount of their horizontal padding. In this test, there should be an horizontal gap of 50px separating both tables. Margins between 2 floated boxes do not collapse." name="assert" />
 
   <style type="text/css"><![CDATA[
   table
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/contiguous-floated-table-vrl-006.xht b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/contiguous-floated-table-vrl-006.xht
index 52ecdebc..d973517 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/contiguous-floated-table-vrl-006.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/contiguous-floated-table-vrl-006.xht
@@ -16,7 +16,7 @@
   <link rel="match" href="../reference/ref-filled-green-100px-square.xht" />
 
   <meta content="image" name="flags" />
-  <meta content="This test checks that horizontal margins existing between contiguous floated tables with writing-mode set to 'vertical-rl' are not subtracted by the amount of their borders. In this test, there should be an horizontal gap of 50px separating both tables. Margins between 2 floated boxes do not collapse." name="assert" />
+  <meta content="This test checks that horizontal margins existing between contiguous floated tables with writing-mode set to 'vertical-rl' are not substracted by the amount of their borders. In this test, there should be an horizontal gap of 50px separating both tables. Margins between 2 floated boxes do not collapse." name="assert" />
 
   <style type="text/css"><![CDATA[
   table
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/contiguous-floated-table-vrl-008.xht b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/contiguous-floated-table-vrl-008.xht
index 24c10092..625f2c4 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/contiguous-floated-table-vrl-008.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/contiguous-floated-table-vrl-008.xht
@@ -16,7 +16,7 @@
   <link rel="match" href="../reference/ref-filled-green-100px-square.xht" />
 
   <meta content="image" name="flags" />
-  <meta content="This test checks that horizontal margins existing between contiguous floated tables with writing-mode set to 'vertical-rl' are not subtracted by the amount of their horizontal padding. In this test, there should be an horizontal gap of 50px separating both tables. Margins between 2 floated boxes do not collapse." name="assert" />
+  <meta content="This test checks that horizontal margins existing between contiguous floated tables with writing-mode set to 'vertical-rl' are not substracted by the amount of their horizontal padding. In this test, there should be an horizontal gap of 50px separating both tables. Margins between 2 floated boxes do not collapse." name="assert" />
 
   <style type="text/css"><![CDATA[
   table
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-003.xht b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-003.xht
index 2dda421..263d55d 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-003.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-003.xht
@@ -17,7 +17,7 @@
   <link rel="match" href="line-box-height-vlr-003-ref.xht" />
 
   <meta content="" name="flags" />
-  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a border. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accommodate transparent 'border-left' of inline box and transparent 'border-right' of inline box." name="assert" />
+  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a border. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accomodate transparent 'border-left' of inline box and transparent 'border-right' of inline box." name="assert" />
 
   <style type="text/css"><![CDATA[
   body
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-005.xht b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-005.xht
index a0e8943..32e2ddca 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-005.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-005.xht
@@ -17,7 +17,7 @@
   <link rel="match" href="line-box-height-vlr-003-ref.xht" />
 
   <meta content="" name="flags" />
-  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a padding. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accommodate transparent 'padding-left' of inline box and transparent 'padding-right' of inline box." name="assert" />
+  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a padding. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accomodate transparent 'padding-left' of inline box and transparent 'padding-right' of inline box." name="assert" />
 
   <style type="text/css"><![CDATA[
   body
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-007.xht b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-007.xht
index c43d7d2..c1f3e12 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-007.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-007.xht
@@ -17,7 +17,7 @@
   <link rel="match" href="line-box-height-vlr-007-ref.xht" />
 
   <meta content="" name="flags" />
-  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a border. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accommodate transparent 'border-left' of inline box and transparent 'border-right' of inline box." name="assert" />
+  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a border. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accomodate transparent 'border-left' of inline box and transparent 'border-right' of inline box." name="assert" />
 
   <style type="text/css"><![CDATA[
   body
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-009.xht b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-009.xht
index 746fab5..1c63169 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-009.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-009.xht
@@ -17,7 +17,7 @@
   <link rel="match" href="line-box-height-vlr-007-ref.xht" />
 
   <meta content="" name="flags" />
-  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a padding. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accommodate transparent 'padding-left' of inline box and transparent 'padding-right' of inline box." name="assert" />
+  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a padding. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accomodate transparent 'padding-left' of inline box and transparent 'padding-right' of inline box." name="assert" />
 
   <style type="text/css"><![CDATA[
   body
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-011.xht b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-011.xht
index db8eb01..953c7f0 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-011.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-011.xht
@@ -17,7 +17,7 @@
   <link rel="match" href="line-box-height-vlr-011-ref.xht" />
 
   <meta content="" name="flags" />
-  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a border. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accommodate transparent 'border-left' of inline box and transparent 'border-right' of inline box." name="assert" />
+  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a border. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accomodate transparent 'border-left' of inline box and transparent 'border-right' of inline box." name="assert" />
 
   <style type="text/css"><![CDATA[
   body
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-013.xht b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-013.xht
index 7721919..b4c56d52 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-013.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-013.xht
@@ -17,7 +17,7 @@
   <link rel="match" href="line-box-height-vlr-011-ref.xht" />
 
   <meta content="" name="flags" />
-  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a padding. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accommodate transparent 'padding-left' of inline box and transparent 'padding-right' of inline box." name="assert" />
+  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a padding. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accomodate transparent 'padding-left' of inline box and transparent 'padding-right' of inline box." name="assert" />
 
   <style type="text/css"><![CDATA[
   body
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-021.xht b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-021.xht
index 4da9a5f..8f1b60c7 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-021.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-021.xht
@@ -17,7 +17,7 @@
   <link rel="match" href="line-box-height-vlr-021-ref.xht" />
 
   <meta content="" name="flags" />
-  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a border. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accommodate transparent 'border-left' of inline box and transparent 'border-right' of inline box." name="assert" />
+  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a border. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accomodate transparent 'border-left' of inline box and transparent 'border-right' of inline box." name="assert" />
   <meta name="DC.date.created" content="2015-07-22T09:54:03+11:00" scheme=
   "W3CDTF" />
   <meta name="DC.date.modified" content="2016-07-22T09:54:03+11:00" scheme=
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-023.xht b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-023.xht
index ae5a2f53..802a3fd 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-023.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vlr-023.xht
@@ -17,7 +17,7 @@
   <link rel="match" href="line-box-height-vlr-023-ref.xht" />
 
   <meta content="" name="flags" />
-  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a border. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accommodate transparent 'border-left' of inline box and transparent 'border-right' of inline box." name="assert" />
+  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a border. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accomodate transparent 'border-left' of inline box and transparent 'border-right' of inline box." name="assert" />
   <meta name="DC.date.created" content="2015-07-22T09:54:03+11:00" scheme="W3CDTF" />
   <meta name="DC.date.modified" content="2016-07-22T09:54:03+11:00" scheme="W3CDTF" />
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-002.xht b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-002.xht
index 422f7d7..1c57284 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-002.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-002.xht
@@ -17,7 +17,7 @@
   <link rel="match" href="line-box-height-vrl-002-ref.xht" />
 
   <meta content="" name="flags" />
-  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a border. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accommodate transparent 'border-left' of inline box and transparent 'border-right' of inline box." name="assert" />
+  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a border. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accomodate transparent 'border-left' of inline box and transparent 'border-right' of inline box." name="assert" />
 
   <style type="text/css"><![CDATA[
   body
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-004.xht b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-004.xht
index 4f3f06f..558d7a2 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-004.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-004.xht
@@ -17,7 +17,7 @@
   <link rel="match" href="line-box-height-vrl-002-ref.xht" />
 
   <meta content="" name="flags" />
-  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a padding. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accommodate transparent 'padding-left' of inline box and transparent 'padding-right' of inline box." name="assert" />
+  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a padding. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accomodate transparent 'padding-left' of inline box and transparent 'padding-right' of inline box." name="assert" />
 
   <style type="text/css"><![CDATA[
   body
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-006.xht b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-006.xht
index 0776ff92..f49258d 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-006.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-006.xht
@@ -17,7 +17,7 @@
   <link rel="match" href="line-box-height-vrl-006-ref.xht" />
 
   <meta content="" name="flags" />
-  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a border. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accommodate transparent 'border-left' of inline box and transparent 'border-right' of inline box." name="assert" />
+  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a border. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accomodate transparent 'border-left' of inline box and transparent 'border-right' of inline box." name="assert" />
 
   <style type="text/css"><![CDATA[
   body
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-008.xht b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-008.xht
index 69485d7..c273333 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-008.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-008.xht
@@ -17,7 +17,7 @@
   <link rel="match" href="line-box-height-vrl-006-ref.xht" />
 
   <meta content="" name="flags" />
-  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a padding. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accommodate transparent 'padding-left' of inline box and transparent 'padding-right' of inline box." name="assert" />
+  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a padding. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accomodate transparent 'padding-left' of inline box and transparent 'padding-right' of inline box." name="assert" />
 
   <style type="text/css"><![CDATA[
   body
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-010.xht b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-010.xht
index ca6f77d8..db85b1d3 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-010.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-010.xht
@@ -17,7 +17,7 @@
   <link rel="match" href="line-box-height-vrl-010-ref.xht" />
 
   <meta content="" name="flags" />
-  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a border. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accommodate transparent 'border-left' of inline box and transparent 'border-right' of inline box." name="assert" />
+  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a border. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accomodate transparent 'border-left' of inline box and transparent 'border-right' of inline box." name="assert" />
 
   <style type="text/css"><![CDATA[
   body
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-012.xht b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-012.xht
index 9376aee0..dac6eef 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-012.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes-3/line-box-height-vrl-012.xht
@@ -17,7 +17,7 @@
   <link rel="match" href="line-box-height-vrl-010-ref.xht" />
 
   <meta content="" name="flags" />
-  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a padding. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accommodate transparent 'padding-left' of inline box and transparent 'padding-right' of inline box." name="assert" />
+  <meta content="This test checks that a line box height does not increase because an inline non-replaced box has a padding. In this test, the '34' inline box and the '56' inline box should be lined up with its inline '12' sibling. The line box height, enclosed by the blue border should not grow to accomodate transparent 'padding-left' of inline box and transparent 'padding-right' of inline box." name="assert" />
 
   <style type="text/css"><![CDATA[
   body
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-13.html b/third_party/WebKit/LayoutTests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-13.html
index 83b3d04b..d6e8a68 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-13.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-13.html
@@ -9,7 +9,7 @@
 <link rel="match" href="support/color-green-ref.html">
 <style>
 body { color: red; }
-@supports (color: something 3px url(wherever) calc(var(--a) + 1px)) {
+@supports (color: something 3px url(whereever) calc(var(--a) + 1px)) {
   p { color: green; }
 }
 </style>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-45.html b/third_party/WebKit/LayoutTests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-45.html
index 9f913f3d..24b1eec 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-45.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-45.html
@@ -9,7 +9,7 @@
 <link rel="match" href="support/color-green-ref.html">
 <style>
 body { color: red; }
-@supports (--a: something 3px url(wherever) calc(var(--b) + 1px)) {
+@supports (--a: something 3px url(whereever) calc(var(--b) + 1px)) {
   p { color: green; }
 }
 </style>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/Element-matches-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/Element-matches-expected.txt
index f40f76ac..cbd97ed 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/Element-matches-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/Element-matches-expected.txt
@@ -83,7 +83,7 @@
 PASS In-document Element.matches: Universal selector, matching all descendants of element with specified ID (with no refNodes): #universal * 
 PASS In-document Element.matches: Attribute presence selector, matching align attribute with value (with no refNodes): .attr-presence-div1[align] 
 PASS In-document Element.matches: Attribute presence selector, matching align attribute with empty value (with no refNodes): .attr-presence-div2[align] 
-PASS In-document Element.matches: Attribute presence selector, matching title attribute, case insensitivity (with no refNodes): #attr-presence [TiTlE] 
+PASS In-document Element.matches: Attribute presence selector, matching title attribute, case insensitivity (with no refNodes): #attr-presence [*|TiTlE] 
 PASS In-document Element.matches: Attribute presence selector, matching custom data-* attribute (with no refNodes): [data-attr-presence] 
 PASS In-document Element.matches: Attribute presence selector, matching attribute with non-ASCII characters (with no refNodes): ul[data-中文] 
 PASS In-document Element.matches: Attribute presence selector, matching option with selected attribute (with no refNodes): #attr-presence-select2 option[selected] 
@@ -251,7 +251,7 @@
 PASS Detached Element.matches: Universal selector, matching all descendants of element with specified ID (with no refNodes): #universal * 
 PASS Detached Element.matches: Attribute presence selector, matching align attribute with value (with no refNodes): .attr-presence-div1[align] 
 PASS Detached Element.matches: Attribute presence selector, matching align attribute with empty value (with no refNodes): .attr-presence-div2[align] 
-PASS Detached Element.matches: Attribute presence selector, matching title attribute, case insensitivity (with no refNodes): #attr-presence [TiTlE] 
+PASS Detached Element.matches: Attribute presence selector, matching title attribute, case insensitivity (with no refNodes): #attr-presence [*|TiTlE] 
 PASS Detached Element.matches: Attribute presence selector, matching custom data-* attribute (with no refNodes): [data-attr-presence] 
 PASS Detached Element.matches: Attribute presence selector, matching attribute with non-ASCII characters (with no refNodes): ul[data-中文] 
 PASS Detached Element.matches: Attribute presence selector, matching option with selected attribute (with no refNodes): #attr-presence-select2 option[selected] 
@@ -413,7 +413,7 @@
 PASS Fragment Element.matches: Universal selector, matching all descendants of element with specified ID (with no refNodes): #universal * 
 PASS Fragment Element.matches: Attribute presence selector, matching align attribute with value (with no refNodes): .attr-presence-div1[align] 
 PASS Fragment Element.matches: Attribute presence selector, matching align attribute with empty value (with no refNodes): .attr-presence-div2[align] 
-PASS Fragment Element.matches: Attribute presence selector, matching title attribute, case insensitivity (with no refNodes): #attr-presence [TiTlE] 
+PASS Fragment Element.matches: Attribute presence selector, matching title attribute, case insensitivity (with no refNodes): #attr-presence [*|TiTlE] 
 PASS Fragment Element.matches: Attribute presence selector, matching custom data-* attribute (with no refNodes): [data-attr-presence] 
 PASS Fragment Element.matches: Attribute presence selector, matching attribute with non-ASCII characters (with no refNodes): ul[data-中文] 
 PASS Fragment Element.matches: Attribute presence selector, matching option with selected attribute (with no refNodes): #attr-presence-select2 option[selected] 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/Element-webkitMatchesSelector-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/Element-webkitMatchesSelector-expected.txt
index c6ecaa3..913e46aa 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/Element-webkitMatchesSelector-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/Element-webkitMatchesSelector-expected.txt
@@ -83,7 +83,7 @@
 PASS In-document Element.webkitMatchesSelector: Universal selector, matching all descendants of element with specified ID (with no refNodes): #universal * 
 PASS In-document Element.webkitMatchesSelector: Attribute presence selector, matching align attribute with value (with no refNodes): .attr-presence-div1[align] 
 PASS In-document Element.webkitMatchesSelector: Attribute presence selector, matching align attribute with empty value (with no refNodes): .attr-presence-div2[align] 
-PASS In-document Element.webkitMatchesSelector: Attribute presence selector, matching title attribute, case insensitivity (with no refNodes): #attr-presence [TiTlE] 
+PASS In-document Element.webkitMatchesSelector: Attribute presence selector, matching title attribute, case insensitivity (with no refNodes): #attr-presence [*|TiTlE] 
 PASS In-document Element.webkitMatchesSelector: Attribute presence selector, matching custom data-* attribute (with no refNodes): [data-attr-presence] 
 PASS In-document Element.webkitMatchesSelector: Attribute presence selector, matching attribute with non-ASCII characters (with no refNodes): ul[data-中文] 
 PASS In-document Element.webkitMatchesSelector: Attribute presence selector, matching option with selected attribute (with no refNodes): #attr-presence-select2 option[selected] 
@@ -251,7 +251,7 @@
 PASS Detached Element.webkitMatchesSelector: Universal selector, matching all descendants of element with specified ID (with no refNodes): #universal * 
 PASS Detached Element.webkitMatchesSelector: Attribute presence selector, matching align attribute with value (with no refNodes): .attr-presence-div1[align] 
 PASS Detached Element.webkitMatchesSelector: Attribute presence selector, matching align attribute with empty value (with no refNodes): .attr-presence-div2[align] 
-PASS Detached Element.webkitMatchesSelector: Attribute presence selector, matching title attribute, case insensitivity (with no refNodes): #attr-presence [TiTlE] 
+PASS Detached Element.webkitMatchesSelector: Attribute presence selector, matching title attribute, case insensitivity (with no refNodes): #attr-presence [*|TiTlE] 
 PASS Detached Element.webkitMatchesSelector: Attribute presence selector, matching custom data-* attribute (with no refNodes): [data-attr-presence] 
 PASS Detached Element.webkitMatchesSelector: Attribute presence selector, matching attribute with non-ASCII characters (with no refNodes): ul[data-中文] 
 PASS Detached Element.webkitMatchesSelector: Attribute presence selector, matching option with selected attribute (with no refNodes): #attr-presence-select2 option[selected] 
@@ -413,7 +413,7 @@
 PASS Fragment Element.webkitMatchesSelector: Universal selector, matching all descendants of element with specified ID (with no refNodes): #universal * 
 PASS Fragment Element.webkitMatchesSelector: Attribute presence selector, matching align attribute with value (with no refNodes): .attr-presence-div1[align] 
 PASS Fragment Element.webkitMatchesSelector: Attribute presence selector, matching align attribute with empty value (with no refNodes): .attr-presence-div2[align] 
-PASS Fragment Element.webkitMatchesSelector: Attribute presence selector, matching title attribute, case insensitivity (with no refNodes): #attr-presence [TiTlE] 
+PASS Fragment Element.webkitMatchesSelector: Attribute presence selector, matching title attribute, case insensitivity (with no refNodes): #attr-presence [*|TiTlE] 
 PASS Fragment Element.webkitMatchesSelector: Attribute presence selector, matching custom data-* attribute (with no refNodes): [data-attr-presence] 
 PASS Fragment Element.webkitMatchesSelector: Attribute presence selector, matching attribute with non-ASCII characters (with no refNodes): ul[data-中文] 
 PASS Fragment Element.webkitMatchesSelector: Attribute presence selector, matching option with selected attribute (with no refNodes): #attr-presence-select2 option[selected] 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/ParentNode-querySelector-All-content.html b/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/ParentNode-querySelector-All-content.html
index affb00a..8dc1354 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/ParentNode-querySelector-All-content.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/ParentNode-querySelector-All-content.html
@@ -30,7 +30,7 @@
     <div class="attr-presence-div2" id="attr-presence-div2" align=""></div>
     <div class="attr-presence-div3" id="attr-presence-div3" valign="center"></div>
     <div class="attr-presence-div4" id="attr-presence-div4" alignv="center"></div>
-    <p id="attr-presence-p1"><a  id="attr-presence-a1" tItLe=""></a><span id="attr-presence-span1" TITLE="attr-presence-span1"></span></p>
+    <p id="attr-presence-p1"><a  id="attr-presence-a1" tItLe=""></a><span id="attr-presence-span1" TITLE="attr-presence-span1"></span><i id="attr-presence-i1"></i></p>
     <pre id="attr-presence-pre1" data-attr-presence="pre1"></pre>
     <blockquote id="attr-presence-blockquote1" data-attr-presence="blockquote1"></blockquote>
     <ul id="attr-presence-ul1" data-中文=""></ul>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/ParentNode-querySelector-All-content.xht b/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/ParentNode-querySelector-All-content.xht
index 14faa9b..0e9b925 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/ParentNode-querySelector-All-content.xht
+++ b/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/ParentNode-querySelector-All-content.xht
@@ -25,7 +25,7 @@
     <div class="attr-presence-div2" id="attr-presence-div2" align=""></div>
     <div class="attr-presence-div3" id="attr-presence-div3" valign="center"></div>
     <div class="attr-presence-div4" id="attr-presence-div4" alignv="center"></div>
-    <p id="attr-presence-p1"><a  id="attr-presence-a1" tItLe=""></a><span id="attr-presence-span1" TITLE="attr-presence-span1"></span></p>
+    <p id="attr-presence-p1"><a  id="attr-presence-a1" tItLe=""></a><span id="attr-presence-span1" TITLE="attr-presence-span1"></span><i id="attr-presence-i1"></i></p>
     <pre id="attr-presence-pre1" data-attr-presence="pre1"></pre>
     <blockquote id="attr-presence-blockquote1" data-attr-presence="blockquote1"></blockquote>
     <ul id="attr-presence-ul1" data-中文=""></ul>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/ParentNode-querySelector-All-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/ParentNode-querySelector-All-expected.txt
index e739af16..8b40c8b9 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/ParentNode-querySelector-All-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/ParentNode-querySelector-All-expected.txt
@@ -313,8 +313,8 @@
 PASS Document.querySelector: Attribute presence selector, matching align attribute with value: .attr-presence-div1[align] 
 PASS Document.querySelectorAll: Attribute presence selector, matching align attribute with empty value: .attr-presence-div2[align] 
 PASS Document.querySelector: Attribute presence selector, matching align attribute with empty value: .attr-presence-div2[align] 
-PASS Document.querySelectorAll: Attribute presence selector, matching title attribute, case insensitivity: #attr-presence [TiTlE] 
-PASS Document.querySelector: Attribute presence selector, matching title attribute, case insensitivity: #attr-presence [TiTlE] 
+PASS Document.querySelectorAll: Attribute presence selector, matching title attribute, case insensitivity: #attr-presence [*|TiTlE] 
+PASS Document.querySelector: Attribute presence selector, matching title attribute, case insensitivity: #attr-presence [*|TiTlE] 
 PASS Document.querySelectorAll: Attribute presence selector, matching custom data-* attribute: [data-attr-presence] 
 PASS Document.querySelector: Attribute presence selector, matching custom data-* attribute: [data-attr-presence] 
 PASS Document.querySelectorAll: Attribute presence selector, not matching attribute with similar name: .attr-presence-div3[align], .attr-presence-div4[align] 
@@ -739,8 +739,8 @@
 PASS Detached Element.querySelector: Attribute presence selector, matching align attribute with value: .attr-presence-div1[align] 
 PASS Detached Element.querySelectorAll: Attribute presence selector, matching align attribute with empty value: .attr-presence-div2[align] 
 PASS Detached Element.querySelector: Attribute presence selector, matching align attribute with empty value: .attr-presence-div2[align] 
-PASS Detached Element.querySelectorAll: Attribute presence selector, matching title attribute, case insensitivity: #attr-presence [TiTlE] 
-PASS Detached Element.querySelector: Attribute presence selector, matching title attribute, case insensitivity: #attr-presence [TiTlE] 
+PASS Detached Element.querySelectorAll: Attribute presence selector, matching title attribute, case insensitivity: #attr-presence [*|TiTlE] 
+PASS Detached Element.querySelector: Attribute presence selector, matching title attribute, case insensitivity: #attr-presence [*|TiTlE] 
 PASS Detached Element.querySelectorAll: Attribute presence selector, matching custom data-* attribute: [data-attr-presence] 
 PASS Detached Element.querySelector: Attribute presence selector, matching custom data-* attribute: [data-attr-presence] 
 PASS Detached Element.querySelectorAll: Attribute presence selector, not matching attribute with similar name: .attr-presence-div3[align], .attr-presence-div4[align] 
@@ -1163,8 +1163,8 @@
 PASS Fragment.querySelector: Attribute presence selector, matching align attribute with value: .attr-presence-div1[align] 
 PASS Fragment.querySelectorAll: Attribute presence selector, matching align attribute with empty value: .attr-presence-div2[align] 
 PASS Fragment.querySelector: Attribute presence selector, matching align attribute with empty value: .attr-presence-div2[align] 
-PASS Fragment.querySelectorAll: Attribute presence selector, matching title attribute, case insensitivity: #attr-presence [TiTlE] 
-PASS Fragment.querySelector: Attribute presence selector, matching title attribute, case insensitivity: #attr-presence [TiTlE] 
+PASS Fragment.querySelectorAll: Attribute presence selector, matching title attribute, case insensitivity: #attr-presence [*|TiTlE] 
+PASS Fragment.querySelector: Attribute presence selector, matching title attribute, case insensitivity: #attr-presence [*|TiTlE] 
 PASS Fragment.querySelectorAll: Attribute presence selector, matching custom data-* attribute: [data-attr-presence] 
 PASS Fragment.querySelector: Attribute presence selector, matching custom data-* attribute: [data-attr-presence] 
 PASS Fragment.querySelectorAll: Attribute presence selector, not matching attribute with similar name: .attr-presence-div3[align], .attr-presence-div4[align] 
@@ -1587,8 +1587,8 @@
 PASS In-document Element.querySelector: Attribute presence selector, matching align attribute with value: .attr-presence-div1[align] 
 PASS In-document Element.querySelectorAll: Attribute presence selector, matching align attribute with empty value: .attr-presence-div2[align] 
 PASS In-document Element.querySelector: Attribute presence selector, matching align attribute with empty value: .attr-presence-div2[align] 
-PASS In-document Element.querySelectorAll: Attribute presence selector, matching title attribute, case insensitivity: #attr-presence [TiTlE] 
-PASS In-document Element.querySelector: Attribute presence selector, matching title attribute, case insensitivity: #attr-presence [TiTlE] 
+PASS In-document Element.querySelectorAll: Attribute presence selector, matching title attribute, case insensitivity: #attr-presence [*|TiTlE] 
+PASS In-document Element.querySelector: Attribute presence selector, matching title attribute, case insensitivity: #attr-presence [*|TiTlE] 
 PASS In-document Element.querySelectorAll: Attribute presence selector, matching custom data-* attribute: [data-attr-presence] 
 PASS In-document Element.querySelector: Attribute presence selector, matching custom data-* attribute: [data-attr-presence] 
 PASS In-document Element.querySelectorAll: Attribute presence selector, not matching attribute with similar name: .attr-presence-div3[align], .attr-presence-div4[align] 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/ParentNode-querySelector-All-xht-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/ParentNode-querySelector-All-xht-expected.txt
index b908833d..8f71fc09 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/ParentNode-querySelector-All-xht-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/ParentNode-querySelector-All-xht-expected.txt
@@ -313,8 +313,8 @@
 PASS Document.querySelector: Attribute presence selector, matching align attribute with value: .attr-presence-div1[align] 
 PASS Document.querySelectorAll: Attribute presence selector, matching align attribute with empty value: .attr-presence-div2[align] 
 PASS Document.querySelector: Attribute presence selector, matching align attribute with empty value: .attr-presence-div2[align] 
-PASS Document.querySelectorAll: Attribute presence selector, not matching title attribute, case sensitivity: #attr-presence [TiTlE] 
-PASS Document.querySelector: Attribute presence selector, not matching title attribute, case sensitivity: #attr-presence [TiTlE] 
+PASS Document.querySelectorAll: Attribute presence selector, not matching title attribute, case sensitivity: #attr-presence [*|TiTlE] 
+PASS Document.querySelector: Attribute presence selector, not matching title attribute, case sensitivity: #attr-presence [*|TiTlE] 
 PASS Document.querySelectorAll: Attribute presence selector, matching custom data-* attribute: [data-attr-presence] 
 PASS Document.querySelector: Attribute presence selector, matching custom data-* attribute: [data-attr-presence] 
 PASS Document.querySelectorAll: Attribute presence selector, not matching attribute with similar name: .attr-presence-div3[align], .attr-presence-div4[align] 
@@ -739,8 +739,8 @@
 PASS Detached Element.querySelector: Attribute presence selector, matching align attribute with value: .attr-presence-div1[align] 
 PASS Detached Element.querySelectorAll: Attribute presence selector, matching align attribute with empty value: .attr-presence-div2[align] 
 PASS Detached Element.querySelector: Attribute presence selector, matching align attribute with empty value: .attr-presence-div2[align] 
-PASS Detached Element.querySelectorAll: Attribute presence selector, not matching title attribute, case sensitivity: #attr-presence [TiTlE] 
-PASS Detached Element.querySelector: Attribute presence selector, not matching title attribute, case sensitivity: #attr-presence [TiTlE] 
+PASS Detached Element.querySelectorAll: Attribute presence selector, not matching title attribute, case sensitivity: #attr-presence [*|TiTlE] 
+PASS Detached Element.querySelector: Attribute presence selector, not matching title attribute, case sensitivity: #attr-presence [*|TiTlE] 
 PASS Detached Element.querySelectorAll: Attribute presence selector, matching custom data-* attribute: [data-attr-presence] 
 PASS Detached Element.querySelector: Attribute presence selector, matching custom data-* attribute: [data-attr-presence] 
 PASS Detached Element.querySelectorAll: Attribute presence selector, not matching attribute with similar name: .attr-presence-div3[align], .attr-presence-div4[align] 
@@ -1163,8 +1163,8 @@
 PASS Fragment.querySelector: Attribute presence selector, matching align attribute with value: .attr-presence-div1[align] 
 PASS Fragment.querySelectorAll: Attribute presence selector, matching align attribute with empty value: .attr-presence-div2[align] 
 PASS Fragment.querySelector: Attribute presence selector, matching align attribute with empty value: .attr-presence-div2[align] 
-PASS Fragment.querySelectorAll: Attribute presence selector, not matching title attribute, case sensitivity: #attr-presence [TiTlE] 
-PASS Fragment.querySelector: Attribute presence selector, not matching title attribute, case sensitivity: #attr-presence [TiTlE] 
+PASS Fragment.querySelectorAll: Attribute presence selector, not matching title attribute, case sensitivity: #attr-presence [*|TiTlE] 
+PASS Fragment.querySelector: Attribute presence selector, not matching title attribute, case sensitivity: #attr-presence [*|TiTlE] 
 PASS Fragment.querySelectorAll: Attribute presence selector, matching custom data-* attribute: [data-attr-presence] 
 PASS Fragment.querySelector: Attribute presence selector, matching custom data-* attribute: [data-attr-presence] 
 PASS Fragment.querySelectorAll: Attribute presence selector, not matching attribute with similar name: .attr-presence-div3[align], .attr-presence-div4[align] 
@@ -1587,8 +1587,8 @@
 PASS In-document Element.querySelector: Attribute presence selector, matching align attribute with value: .attr-presence-div1[align] 
 PASS In-document Element.querySelectorAll: Attribute presence selector, matching align attribute with empty value: .attr-presence-div2[align] 
 PASS In-document Element.querySelector: Attribute presence selector, matching align attribute with empty value: .attr-presence-div2[align] 
-PASS In-document Element.querySelectorAll: Attribute presence selector, not matching title attribute, case sensitivity: #attr-presence [TiTlE] 
-PASS In-document Element.querySelector: Attribute presence selector, not matching title attribute, case sensitivity: #attr-presence [TiTlE] 
+PASS In-document Element.querySelectorAll: Attribute presence selector, not matching title attribute, case sensitivity: #attr-presence [*|TiTlE] 
+PASS In-document Element.querySelector: Attribute presence selector, not matching title attribute, case sensitivity: #attr-presence [*|TiTlE] 
 PASS In-document Element.querySelectorAll: Attribute presence selector, matching custom data-* attribute: [data-attr-presence] 
 PASS In-document Element.querySelector: Attribute presence selector, matching custom data-* attribute: [data-attr-presence] 
 PASS In-document Element.querySelectorAll: Attribute presence selector, not matching attribute with similar name: .attr-presence-div3[align], .attr-presence-div4[align] 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/ParentNode-querySelector-All.js b/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/ParentNode-querySelector-All.js
index 4e6bef6..dd789b7 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/ParentNode-querySelector-All.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/ParentNode-querySelector-All.js
@@ -45,6 +45,9 @@
 
   parent.appendChild(anyNS);
   parent.appendChild(noNS);
+
+  var span = doc.getElementById("attr-presence-i1");
+  span.setAttributeNS("http://www.example.org/ns", "title", "");
 }
 
 /*
diff --git a/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/selectors.js b/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/selectors.js
index 36b497a9..51a206de 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/selectors.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/dom/nodes/selectors.js
@@ -75,16 +75,16 @@
 
   // Attribute Selectors
   // - presence                  [att]
-  {name: "Attribute presence selector, matching align attribute with value",                    selector: ".attr-presence-div1[align]",                             expect: ["attr-presence-div1"],                                             level: 2, testType: TEST_QSA | TEST_MATCH},
-  {name: "Attribute presence selector, matching align attribute with empty value",              selector: ".attr-presence-div2[align]",                             expect: ["attr-presence-div2"],                                             level: 2, testType: TEST_QSA | TEST_MATCH},
-  {name: "Attribute presence selector, matching title attribute, case insensitivity",           selector: "#attr-presence [TiTlE]",                                 expect: ["attr-presence-a1", "attr-presence-span1"], exclude: ["xhtml"],    level: 2, testType: TEST_QSA | TEST_MATCH},
-  {name: "Attribute presence selector, not matching title attribute, case sensitivity",         selector: "#attr-presence [TiTlE]",                                 expect: [],                                          exclude: ["html"],     level: 2, testType: TEST_QSA | TEST_MATCH},
-  {name: "Attribute presence selector, matching custom data-* attribute",                       selector: "[data-attr-presence]",                                   expect: ["attr-presence-pre1", "attr-presence-blockquote1"],                level: 2, testType: TEST_QSA | TEST_MATCH},
-  {name: "Attribute presence selector, not matching attribute with similar name",               selector: ".attr-presence-div3[align], .attr-presence-div4[align]", expect: [] /*no matches*/,                                                  level: 2, testType: TEST_QSA},
-  {name: "Attribute presence selector, matching attribute with non-ASCII characters",           selector: "ul[data-中文]",                                            expect: ["attr-presence-ul1"],                                              level: 2, testType: TEST_QSA | TEST_MATCH},
-  {name: "Attribute presence selector, not matching default option without selected attribute", selector: "#attr-presence-select1 option[selected]",                expect: [] /* no matches */,                                                level: 2, testType: TEST_QSA},
-  {name: "Attribute presence selector, matching option with selected attribute",                selector: "#attr-presence-select2 option[selected]",                expect: ["attr-presence-select2-option4"],                                  level: 2, testType: TEST_QSA | TEST_MATCH},
-  {name: "Attribute presence selector, matching multiple options with selected attributes",     selector: "#attr-presence-select3 option[selected]",                expect: ["attr-presence-select3-option2", "attr-presence-select3-option3"], level: 2, testType: TEST_QSA | TEST_MATCH},
+  {name: "Attribute presence selector, matching align attribute with value",                    selector: ".attr-presence-div1[align]",                             expect: ["attr-presence-div1"],                                                                   level: 2, testType: TEST_QSA | TEST_MATCH},
+  {name: "Attribute presence selector, matching align attribute with empty value",              selector: ".attr-presence-div2[align]",                             expect: ["attr-presence-div2"],                                                                   level: 2, testType: TEST_QSA | TEST_MATCH},
+  {name: "Attribute presence selector, matching title attribute, case insensitivity",           selector: "#attr-presence [*|TiTlE]",                                 expect: ["attr-presence-a1", "attr-presence-span1", "attr-presence-i1"], exclude: ["xhtml"],    level: 2, testType: TEST_QSA | TEST_MATCH},
+  {name: "Attribute presence selector, not matching title attribute, case sensitivity",         selector: "#attr-presence [*|TiTlE]",                                 expect: [],                                                              exclude: ["html"],     level: 2, testType: TEST_QSA | TEST_MATCH},
+  {name: "Attribute presence selector, matching custom data-* attribute",                       selector: "[data-attr-presence]",                                   expect: ["attr-presence-pre1", "attr-presence-blockquote1"],                                      level: 2, testType: TEST_QSA | TEST_MATCH},
+  {name: "Attribute presence selector, not matching attribute with similar name",               selector: ".attr-presence-div3[align], .attr-presence-div4[align]", expect: [] /*no matches*/,                                                                        level: 2, testType: TEST_QSA},
+  {name: "Attribute presence selector, matching attribute with non-ASCII characters",           selector: "ul[data-中文]",                                            expect: ["attr-presence-ul1"],                                                                    level: 2, testType: TEST_QSA | TEST_MATCH},
+  {name: "Attribute presence selector, not matching default option without selected attribute", selector: "#attr-presence-select1 option[selected]",                expect: [] /* no matches */,                                                                      level: 2, testType: TEST_QSA},
+  {name: "Attribute presence selector, matching option with selected attribute",                selector: "#attr-presence-select2 option[selected]",                expect: ["attr-presence-select2-option4"],                                                        level: 2, testType: TEST_QSA | TEST_MATCH},
+  {name: "Attribute presence selector, matching multiple options with selected attributes",     selector: "#attr-presence-select3 option[selected]",                expect: ["attr-presence-select3-option2", "attr-presence-select3-option3"],                       level: 2, testType: TEST_QSA | TEST_MATCH},
 
   // - value                     [att=val]
   {name: "Attribute value selector, matching align attribute with value",                                    selector: "#attr-value [align=\"center\"]",                                     expect: ["attr-value-div1"], level: 2, testType: TEST_QSA | TEST_MATCH},
diff --git a/third_party/WebKit/LayoutTests/external/wpt/domparsing/DOMParser-parseFromString-xml-doctype.html b/third_party/WebKit/LayoutTests/external/wpt/domparsing/DOMParser-parseFromString-xml-doctype.html
index 2711308..cd655acf 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/domparsing/DOMParser-parseFromString-xml-doctype.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/domparsing/DOMParser-parseFromString-xml-doctype.html
@@ -10,7 +10,7 @@
     var doc = new DOMParser().parseFromString('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"><html><div id="test"/></html>', 'application/xhtml+xml');
     var div = doc.getElementById('test');
     assert_equals(div, null); // If null, then this was a an error document (didn't parse the input successfully)
-  }, "Doctype parsing of System Id must fail on omitted value");
+  }, "Doctype parsing of System Id must fail on ommitted value");
 
   test(function () {
     var doc = new DOMParser().parseFromString('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ""><html><div id="test"/></html>', 'application/xhtml+xml');
diff --git a/third_party/WebKit/LayoutTests/external/wpt/encrypted-media/idlharness.html b/third_party/WebKit/LayoutTests/external/wpt/encrypted-media/idlharness.html
index fd74d1ab..e65ad5e3 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/encrypted-media/idlharness.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/encrypted-media/idlharness.html
@@ -2,7 +2,7 @@
 <html>
   <head>
     <meta charset=utf-8>
-    <title>Encrypted Media Extensions IDL test</title>
+    <title>Encrypted Media Extentions IDL test</title>
     <link rel="help" href="https://w3c.github.io/encrypted-media/">
 
     <script src=/resources/testharness.js></script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fetch/api/response/response-clone-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/fetch/api/response/response-clone-expected.txt
index 8982a2b..ec49708 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/fetch/api/response/response-clone-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/fetch/api/response/response-clone-expected.txt
@@ -1,7 +1,7 @@
 This is a testharness.js-based test.
 PASS Check Response's clone with default values, without body 
 PASS Check Response's clone has the expected attribute values 
-PASS Check original response's body after cloning 
+PASS Check orginal response's body after cloning 
 PASS Check cloned response's body 
 PASS Cannot clone a disturbed response 
 PASS Cloned responses should provide the same data 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fetch/api/response/response-clone.html b/third_party/WebKit/LayoutTests/external/wpt/fetch/api/response/response-clone.html
index c4248e76..2eeb78c4 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/fetch/api/response/response-clone.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/fetch/api/response/response-clone.html
@@ -47,7 +47,7 @@
 
       promise_test(function(test) {
         return validateStreamFromString(response.body.getReader(), body);
-      }, "Check original response's body after cloning");
+      }, "Check orginal response's body after cloning");
 
       promise_test(function(test) {
         return validateStreamFromString(clonedResponse.body.getReader(), body);
diff --git a/third_party/WebKit/LayoutTests/external/wpt/geolocation-API/support.js b/third_party/WebKit/LayoutTests/external/wpt/geolocation-API/support.js
index 0f0e7f65..960b572 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/geolocation-API/support.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/geolocation-API/support.js
@@ -5,7 +5,7 @@
 });
 
 // The spec states that an implementation SHOULD acquire user permission before
-// beginning the position acquisition steps. If an implementation follows this
+// beggining the position acquisition steps. If an implementation follows this
 // advice, set the following flag to aid debugging.
 var isUsingPreemptivePermission = false;
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/dom/elements-metadata.js b/third_party/WebKit/LayoutTests/external/wpt/html/dom/elements-metadata.js
index 7c4d373..1b23a27 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/dom/elements-metadata.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/dom/elements-metadata.js
@@ -14,7 +14,7 @@
     relList: {type: "tokenlist", domAttrName: "rel"},
     as: {
       type: "enum",
-      keywords: ["", "audio", "document", "embed", "font", "image", "manifest", "object", "report", "script", "serviceworker", "sharedworker", "style", "track", "video", "worker", "xslt"],
+      keywords: ["fetch", "audio", "document", "embed", "font", "image", "manifest", "object", "report", "script", "serviceworker", "sharedworker", "style", "track", "video", "worker", "xslt"],
       defaultVal: "",
       invalidVal: ""
     },
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/dom/reflection-metadata-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/html/dom/reflection-metadata-expected.txt
index 4ec7d04..33d334581 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/dom/reflection-metadata-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/dom/reflection-metadata-expected.txt
@@ -31,7 +31,7 @@
 PASS link.href: 38 tests
 PASS link.crossOrigin: 52 tests
 PASS link.rel: 32 tests
-PASS link.as: 22 tests
+PASS link.as: 27 tests
 FAIL link.as: setAttribute() to "document" assert_equals: IDL get expected "document" but got ""
 PASS link.as: 3 tests
 FAIL link.as: setAttribute() to "DOCUMENT" assert_equals: IDL get expected "document" but got ""
@@ -64,7 +64,7 @@
 FAIL link.as: setAttribute() to "xslt" assert_equals: IDL get expected "xslt" but got ""
 PASS link.as: 3 tests
 FAIL link.as: setAttribute() to "XSLT" assert_equals: IDL get expected "xslt" but got ""
-PASS link.as: 20 tests
+PASS link.as: 25 tests
 FAIL link.as: IDL set to "document" assert_equals: IDL get expected "document" but got ""
 PASS link.as: 3 tests
 FAIL link.as: IDL set to "DOCUMENT" assert_equals: IDL get expected "document" but got ""
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/editing/dnd/README b/third_party/WebKit/LayoutTests/external/wpt/html/editing/dnd/README
index 451e8ac4..aeda217 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/editing/dnd/README
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/editing/dnd/README
@@ -4,14 +4,14 @@
   http://dev.w3.org/html5/spec/dnd.html#dnd
 
 Tests in target-origin/ relate to a proposed spec extension and are not covered
-by HTML5 drafts at the time of writing. Contact Opera Software for details, and
+by HTML5 drafts at the time of writing. Contact Opera Sofware for details, and
 mention CT-1656.
 
 Tests in synthetic/ relate to incomplete parts of the HTML5 specification,
 which allows synthetic events to be created. For compatibility with others,
 the dataTransfer parameter allows null, undefined and other objects. Objects
 will create a synthetic dataTransfer. To provide maximum functionality,
-synthetic dataTransfer will have its own synthetic data store, detached from
+synthetic dataTransfer will have its own synthetic data store, detatched from
 the real data store used by real drag events (actual user interaction). For
 security, real dataTransfer objects will remember the real event's protection
 status inside synthetic events (the spec bases their protection only on the
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/editing/dnd/events/events-cross-document-suite-HELPER-1.html b/third_party/WebKit/LayoutTests/external/wpt/html/editing/dnd/events/events-cross-document-suite-HELPER-1.html
index 6b6c80a..efd53c6b 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/editing/dnd/events/events-cross-document-suite-HELPER-1.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/editing/dnd/events/events-cross-document-suite-HELPER-1.html
@@ -70,7 +70,7 @@
 		/*
 			Normalise; reduce repeating event sequences to only 2 occurrences.
 			This makes the final event sequence predictable, no matter how many times the drag->dragover sequences repeat.
-			Two occurrences are kept in each case to allow testing to make sure the sequence really is repeating.
+			Two occurrances are kept in each case to allow testing to make sure the sequence really is repeating.
 		*/
 		//spec compliant - div dragenter is not cancelled, so body dragenter fires and body becomes current target
 		//repeats while drag is over orange or fuchsia or the body
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/editing/dnd/remove/022-1.html b/third_party/WebKit/LayoutTests/external/wpt/html/editing/dnd/remove/022-1.html
index d2d954f0..fe65c604 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/editing/dnd/remove/022-1.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/editing/dnd/remove/022-1.html
@@ -1,5 +1,5 @@
 <!doctype html>
-<title>Removing drop targeted document before the queue is processed</title>
+<title>Removing drop targetted document before the queue is processed</title>
 <style>
   html, body, div {
     height: 100%;
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/editing/dnd/remove/022.html b/third_party/WebKit/LayoutTests/external/wpt/html/editing/dnd/remove/022.html
index 05661a1..6bb8e6a 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/editing/dnd/remove/022.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/editing/dnd/remove/022.html
@@ -1,5 +1,5 @@
 <!doctype html>
-<title>Removing drop targeted document before the queue is processed</title>
+<title>Removing drop targetted document before the queue is processed</title>
 <style>
   span, iframe {
     height: 200px;
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/the-hr-element-0/hr-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/the-hr-element-0/hr-expected.txt
new file mode 100644
index 0000000..86ddb325
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/the-hr-element-0/hr-expected.txt
@@ -0,0 +1,21 @@
+This is a testharness.js-based test.
+PASS display 
+FAIL unicodeBidi assert_equals: expected "isolate" but got "normal"
+FAIL color assert_equals: expected "rgb(128, 128, 128)" but got "rgb(0, 0, 0)"
+PASS borderTopStyle 
+PASS borderRightStyle 
+PASS borderBottomStyle 
+PASS borderLeftStyle 
+PASS borderTopWidth 
+PASS borderRightWidth 
+PASS borderBottomWidth 
+PASS borderLeftWidth 
+PASS marginTop 
+PASS marginRight 
+PASS marginBottom 
+PASS marginLeft 
+FAIL overflow assert_equals: expected "hidden" but got "visible"
+PASS height 
+PASS box-sizing 
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/the-hr-element-0/hr.html b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/the-hr-element-0/hr.html
new file mode 100644
index 0000000..e8522164
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/the-hr-element-0/hr.html
@@ -0,0 +1,55 @@
+<!doctype html>
+<title>The hr element</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<style>
+#ref {
+  display: block;
+  unicode-bidi: isolate;
+  color: gray;
+  border-style: inset;
+  border-width: 1px;
+  margin: 0.5em auto;
+  /* TODO: uncomment this when these properties are widely supported
+  margin-block-start: 0.5em;
+  margin-inline-end: auto;
+  margin-block-end: 0.5em;
+  margin-inline-start: auto;
+  */
+  overflow: hidden;
+}
+</style>
+
+<hr id=test>
+<div id=ref></div>
+
+<script>
+setup(() => {
+  self.testStyle = getComputedStyle(document.getElementById('test'));
+  self.refStyle = getComputedStyle(document.getElementById('ref'));
+});
+['display',
+ 'unicodeBidi',
+ 'color',
+ 'borderTopStyle',
+ 'borderRightStyle',
+ 'borderBottomStyle',
+ 'borderLeftStyle',
+ 'borderTopWidth',
+ 'borderRightWidth',
+ 'borderBottomWidth',
+ 'borderLeftWidth',
+ 'marginTop',
+ 'marginRight',
+ 'marginBottom',
+ 'marginLeft',
+ 'overflow',
+ // Extra tests
+ 'height',
+ 'box-sizing',
+].forEach(prop => {
+  test(() => {
+    assert_equals(testStyle[prop], refStyle[prop]);
+  }, prop);
+});
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/the-hr-element-0/setting-overflow-visible-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/the-hr-element-0/setting-overflow-visible-expected.txt
new file mode 100644
index 0000000..221ccd4
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/the-hr-element-0/setting-overflow-visible-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+PASS control 
+FAIL overflow: visible assert_equals: hr.offsetLeft expected 0 but got 50
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/the-hr-element-0/setting-overflow-visible.html b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/the-hr-element-0/setting-overflow-visible.html
new file mode 100644
index 0000000..11e3d63
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/the-hr-element-0/setting-overflow-visible.html
@@ -0,0 +1,64 @@
+<!doctype html>
+<title>The hr element: setting 'overflow: visible'</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<style>
+/* Use 0 margin for hr instead of default 0.5em to make things simpler */
+hr {
+  margin-top: 0;
+  margin-bottom: 0;
+}
+.wrapper {
+  height: 150px;
+  position: relative;
+}
+#test-visible {
+  overflow: visible;
+}
+.float, hr::before {
+  content: "";
+  float: left;
+  width: 50px;
+  height: 50px;
+  background-color: orange;
+}
+.clear {
+  clear: left;
+}
+</style>
+
+<div class=wrapper>
+ <div class=float></div>
+ <hr id=test-control>
+ <div class=float></div>
+</div>
+
+<div class=wrapper>
+ <div class=float></div>
+ <hr id=test-visible>
+ <div class=float></div>
+</div>
+
+<script>
+
+test(() => {
+  const hr = document.getElementById('test-control');
+  assert_equals(hr.offsetLeft, 50, 'hr.offsetLeft');
+  assert_equals(hr.offsetTop, 0, 'hr.offsetTop');
+  assert_equals(hr.clientHeight, 50, 'hr.clientHeight');
+  const divAfter = hr.nextElementSibling;
+  assert_equals(divAfter.offsetLeft, 0, 'divAfter.offsetLeft');
+  assert_equals(divAfter.offsetTop, 50 + 1 + 1 /* hr border */, 'divAfter.offsetTop');
+}, 'control');
+
+test(() => {
+  const hr = document.getElementById('test-visible');
+  assert_equals(hr.offsetLeft, 0, 'hr.offsetLeft');
+  assert_equals(hr.offsetTop, 0, 'hr.offsetTop');
+  assert_equals(hr.clientHeight, 0, 'hr.clientHeight');
+  const divAfter = hr.nextElementSibling;
+  assert_equals(divAfter.offsetLeft, 50 + 50, 'divAfter.offsetLeft');
+  assert_equals(divAfter.offsetTop, 1 + 1 /* hr border */, 'divAfter.offsetTop');
+}, 'overflow: visible');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/audio_volume_check.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/audio_volume_check.html
index 4d7a6fe..b467c702 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/audio_volume_check.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/audio_volume_check.html
@@ -24,7 +24,7 @@
 
         test(function() {
             assert_false(media.volume < VOLUME.SILENT || media.volume > VOLUME.LOUDEST, "media.volume outside the range 0.0 to 1.0 inclusive");
-        }, "Check if the initial value of the audio.volume is in the range 0.0 to 1.0 inclusive");
+        }, "Check if the intial value of the audio.volume is in the range 0.0 to 1.0 inclusive");
 
         function volume_setting(vol, name)
         {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/video_volume_check.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/video_volume_check.html
index 12deed9..1a45358a 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/video_volume_check.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/embedded-content/media-elements/video_volume_check.html
@@ -24,7 +24,7 @@
 
         test(function() {
             assert_false(media.volume < VOLUME.SILENT || media.volume > VOLUME.LOUDEST, "media.volume outside the range 0.0 to 1.0 inclusive");
-        }, "Check if the initial value of the video.volume is in the range 0.0 to 1.0 inclusive");
+        }, "Check if the intial value of the video.volume is in the range 0.0 to 1.0 inclusive");
 
         function volume_setting(vol, name)
         {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/forms/the-label-element/label-attributes.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/forms/the-label-element/label-attributes.html
index 7bf5df3..826533e 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/forms/the-label-element/label-attributes.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/forms/the-label-element/label-attributes.html
@@ -116,7 +116,7 @@
     assert_equals(document.getElementById("test5").labels.length, 0,
                   "The number of labels should be 0 if the form control has an ancestor label element that the for attribute points to another control.");
     assert_equals(document.getElementById("lbl2").control, null,
-                  "The labeled control should be associated with the control whose ID is equal to the value of the 'for' attribute.");
+                  "The labeled cotrol should be associated with the control whose ID is equal to the value of the 'for' attribute.");
   }, "A form control has no label 2.");
 
   // form attribute
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator-pluginarray.html b/third_party/WebKit/LayoutTests/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator-pluginarray.html
deleted file mode 100644
index 8798b263..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator-pluginarray.html
+++ /dev/null
@@ -1,55 +0,0 @@
-<!doctype html>
-<html>
-<body>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script type="text/javascript">
-test(function () {
-  for (var i = 0; i < navigator.plugins.length; i++) {
-    var plugin = navigator.plugins[i];
-    var name = plugin.name;
-    assert_equals(plugin, navigator.plugins[i]);
-    assert_equals(plugin, navigator.plugins[name]);
-  }
-  for (var i = 0; i < navigator.mimeTypes.length; i++) {
-    var mime_type = navigator.mimeTypes[i];
-    var type = mime_type.type;
-    assert_equals(mime_type, navigator.mimeTypes[i]);
-    assert_equals(mime_type, navigator.mimeTypes[type]);
-    assert_equals(mime_type.enabledPlugin, navigator.plugins[mime_type.enabledPlugin.name]);
-  }
-}, "Tests that navigator.plugins and navigator.mimeTypes returns the same object when queried multiple times.");
-
-test(function () {
-  var iframe = document.createElement("iframe");
-  iframe.src = "about:blank";
-  document.body.appendChild(iframe);
-  assert_equals(navigator.plugins.length, iframe.contentWindow.navigator.plugins.length);
-  assert_equals(navigator.mimeTypes.length, iframe.contentWindow.navigator.mimeTypes.length);
-  for (var i = 0; i < navigator.plugins.length; i++) {
-    var plugin = navigator.plugins[i];
-    var name = plugin.name;
-    assert_not_equals(plugin, iframe.contentWindow.navigator.plugins[i]);
-    assert_not_equals(plugin, iframe.contentWindow.navigator.plugins[name]);
-  }
-  for (var i = 0; i < navigator.mimeTypes.length; i++) {
-    var mime_type = navigator.mimeTypes[i];
-    var type = mime_type.type;
-    assert_not_equals(mime_type, iframe.contentWindow.navigator.mimeTypes[i]);
-    assert_not_equals(mime_type, iframe.contentWindow.navigator.mimeTypes[type]);
-    assert_not_equals(mime_type.enabledPlugin, iframe.contentWindow.navigator.plugins[mime_type.enabledPlugin.name]);
-  }
-  iframe.remove();
-}, "Tests that navigator.plugins and navigator.mimeTypes does not return the same object on different frames.");
-
-test(function () {
-  for (var i = 1; i < navigator.plugins.length; i++) {
-    assert_less_than_equal(navigator.plugins[i-1].name.localeCompare(navigator.plugins[i].name), 0);
-  }
-  for (var i = 1; i < navigator.mimeTypes.length; i++) {
-    assert_less_than_equal(navigator.mimeTypes[i-1].type.localeCompare(navigator.mimeTypes[i].type), 0);
-  }
-}, "Tests that navigator.plugins and navigator.mimeTypes returns plugins sorted in alphabetical order by plugin name.");
-</script>
-</body>
-</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/interfaces/mediacapture-main.idl b/third_party/WebKit/LayoutTests/external/wpt/interfaces/mediacapture-main.idl
new file mode 100644
index 0000000..13593c7
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/interfaces/mediacapture-main.idl
@@ -0,0 +1,254 @@
+[Exposed=Window,
+ Constructor,
+ Constructor(MediaStream stream),
+ Constructor(sequence<MediaStreamTrack> tracks)]
+interface MediaStream : EventTarget {
+    readonly attribute DOMString    id;
+    sequence<MediaStreamTrack> getAudioTracks();
+    sequence<MediaStreamTrack> getVideoTracks();
+    sequence<MediaStreamTrack> getTracks();
+    MediaStreamTrack?          getTrackById(DOMString trackId);
+    void                       addTrack(MediaStreamTrack track);
+    void                       removeTrack(MediaStreamTrack track);
+    MediaStream                clone();
+    readonly attribute boolean      active;
+             attribute EventHandler onaddtrack;
+             attribute EventHandler onremovetrack;
+};
+
+[Exposed=Window]
+interface MediaStreamTrack : EventTarget {
+    readonly attribute DOMString             kind;
+    readonly attribute DOMString             id;
+    readonly attribute DOMString             label;
+             attribute boolean               enabled;
+    readonly attribute boolean               muted;
+             attribute EventHandler          onmute;
+             attribute EventHandler          onunmute;
+    readonly attribute MediaStreamTrackState readyState;
+             attribute EventHandler          onended;
+    MediaStreamTrack       clone();
+    void                   stop();
+    MediaTrackCapabilities getCapabilities();
+    MediaTrackConstraints  getConstraints();
+    MediaTrackSettings     getSettings();
+    Promise<void>          applyConstraints(optional MediaTrackConstraints constraints);
+             attribute EventHandler          onoverconstrained;
+};
+
+enum MediaStreamTrackState {
+    "live",
+    "ended"
+};
+
+dictionary MediaTrackSupportedConstraints {
+    boolean width = true;
+    boolean height = true;
+    boolean aspectRatio = true;
+    boolean frameRate = true;
+    boolean facingMode = true;
+    boolean volume = true;
+    boolean sampleRate = true;
+    boolean sampleSize = true;
+    boolean echoCancellation = true;
+    boolean latency = true;
+    boolean channelCount = true;
+    boolean deviceId = true;
+    boolean groupId = true;
+};
+
+dictionary MediaTrackCapabilities {
+    LongRange           width;
+    LongRange           height;
+    DoubleRange         aspectRatio;
+    DoubleRange         frameRate;
+    sequence<DOMString> facingMode;
+    DoubleRange         volume;
+    LongRange           sampleRate;
+    LongRange           sampleSize;
+    sequence<boolean>   echoCancellation;
+    DoubleRange         latency;
+    LongRange           channelCount;
+    DOMString           deviceId;
+    DOMString           groupId;
+};
+
+dictionary MediaTrackConstraints : MediaTrackConstraintSet {
+    sequence<MediaTrackConstraintSet> advanced;
+};
+
+dictionary MediaTrackConstraintSet {
+    ConstrainLong      width;
+    ConstrainLong      height;
+    ConstrainDouble    aspectRatio;
+    ConstrainDouble    frameRate;
+    ConstrainDOMString facingMode;
+    ConstrainDouble    volume;
+    ConstrainLong      sampleRate;
+    ConstrainLong      sampleSize;
+    ConstrainBoolean   echoCancellation;
+    ConstrainDouble    latency;
+    ConstrainLong      channelCount;
+    ConstrainDOMString deviceId;
+    ConstrainDOMString groupId;
+};
+
+dictionary MediaTrackSettings {
+    long      width;
+    long      height;
+    double    aspectRatio;
+    double    frameRate;
+    DOMString facingMode;
+    double    volume;
+    long      sampleRate;
+    long      sampleSize;
+    boolean   echoCancellation;
+    double    latency;
+    long      channelCount;
+    DOMString deviceId;
+    DOMString groupId;
+};
+
+enum VideoFacingModeEnum {
+    "user",
+    "environment",
+    "left",
+    "right"
+};
+
+[Exposed=Window,
+ Constructor(DOMString type, MediaStreamTrackEventInit eventInitDict)]
+interface MediaStreamTrackEvent : Event {
+    [SameObject]
+    readonly attribute MediaStreamTrack track;
+};
+
+dictionary MediaStreamTrackEventInit : EventInit {
+    required MediaStreamTrack track;
+};
+
+[Exposed=Window,
+ Constructor(DOMString type, OverconstrainedErrorEventInit eventInitDict)]
+interface OverconstrainedErrorEvent : Event {
+    readonly attribute OverconstrainedError? error;
+};
+
+dictionary OverconstrainedErrorEventInit : EventInit {
+    OverconstrainedError? error = null;
+};
+
+[Exposed=Window,
+ NoInterfaceObject]
+interface NavigatorUserMedia {
+    [SameObject]
+    readonly attribute MediaDevices mediaDevices;
+};
+
+Navigator implements NavigatorUserMedia;
+
+[Exposed=Window]
+interface MediaDevices : EventTarget {
+    attribute EventHandler ondevicechange;
+    Promise<sequence<MediaDeviceInfo>> enumerateDevices();
+};
+
+[Exposed=Window]
+interface MediaDeviceInfo {
+    readonly attribute DOMString       deviceId;
+    readonly attribute MediaDeviceKind kind;
+    readonly attribute DOMString       label;
+    readonly attribute DOMString       groupId;
+    serializer = {attribute};
+};
+
+enum MediaDeviceKind {
+    "audioinput",
+    "audiooutput",
+    "videoinput"
+};
+
+interface InputDeviceInfo : MediaDeviceInfo {
+    MediaTrackCapabilities getCapabilities();
+};
+
+partial interface NavigatorUserMedia {
+    void getUserMedia(MediaStreamConstraints constraints,
+                      NavigatorUserMediaSuccessCallback successCallback,
+                      NavigatorUserMediaErrorCallback errorCallback);
+};
+
+partial interface MediaDevices {
+    MediaTrackSupportedConstraints getSupportedConstraints();
+    Promise<MediaStream>           getUserMedia(optional MediaStreamConstraints constraints);
+};
+
+dictionary MediaStreamConstraints {
+    (boolean or MediaTrackConstraints) video = false;
+    (boolean or MediaTrackConstraints) audio = false;
+};
+
+callback NavigatorUserMediaSuccessCallback = void (MediaStream stream);
+
+callback NavigatorUserMediaErrorCallback = void (MediaStreamError error);
+
+typedef object MediaStreamError;
+
+[NoInterfaceObject]
+interface ConstrainablePattern {
+    Capabilities  getCapabilities();
+    Constraints   getConstraints();
+    Settings      getSettings();
+    Promise<void> applyConstraints(optional Constraints constraints);
+    attribute EventHandler onoverconstrained;
+};
+
+dictionary DoubleRange {
+    double max;
+    double min;
+};
+
+dictionary ConstrainDoubleRange : DoubleRange {
+    double exact;
+    double ideal;
+};
+
+dictionary LongRange {
+    long max;
+    long min;
+};
+
+dictionary ConstrainLongRange : LongRange {
+    long exact;
+    long ideal;
+};
+
+dictionary ConstrainBooleanParameters {
+    boolean exact;
+    boolean ideal;
+};
+
+dictionary ConstrainDOMStringParameters {
+    (DOMString or sequence<DOMString>) exact;
+    (DOMString or sequence<DOMString>) ideal;
+};
+
+typedef (long or ConstrainLongRange) ConstrainLong;
+
+typedef (double or ConstrainDoubleRange) ConstrainDouble;
+
+typedef (boolean or ConstrainBooleanParameters) ConstrainBoolean;
+
+typedef (DOMString or sequence<DOMString> or ConstrainDOMStringParameters) ConstrainDOMString;
+
+dictionary Capabilities {
+};
+
+dictionary Settings {
+};
+
+dictionary ConstraintSet {
+};
+
+dictionary Constraints : ConstraintSet {
+    sequence<ConstraintSet> advanced;
+};
diff --git a/third_party/WebKit/LayoutTests/external/wpt/media-source/mediasource-duration.html b/third_party/WebKit/LayoutTests/external/wpt/media-source/mediasource-duration.html
index a7760b66..4bc0fb2 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/media-source/mediasource-duration.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/media-source/mediasource-duration.html
@@ -44,7 +44,7 @@
                   test.waitForExpectedEvents(function()
                   {
                       assert_greater_than_equal(mediaElement.currentTime, seekTo, 'Playback time has reached seekTo');
-                      assert_false(mediaElement.seeking, 'mediaElement.seeking after sought to seekTo');
+                      assert_false(mediaElement.seeking, 'mediaElement.seeking after seeked to seekTo');
 
                       assert_false(sourceBuffer.updating, 'sourceBuffer.updating');
 
@@ -133,7 +133,7 @@
                                        'mediaElement duration increased by new append');
                   assert_equals(mediaSource.duration, mediaElement.duration,
                                 'mediaSource duration increased by new append');
-                  assert_false(mediaElement.seeking, 'mediaElement.seeking after sought to truncatedDuration');
+                  assert_false(mediaElement.seeking, 'mediaElement.seeking after seeked to truncatedDuration');
 
                   test.done();
               });
@@ -162,7 +162,7 @@
                                 'mediaElement truncatedDuration after seek to it');
                   assert_equals(mediaSource.duration, truncatedDuration,
                                 'mediaSource truncatedDuration after seek to it');
-                  assert_false(mediaElement.seeking, 'mediaElement.seeking after sought to truncatedDuration');
+                  assert_false(mediaElement.seeking, 'mediaElement.seeking after seeked to truncatedDuration');
 
                   test.done();
               });
diff --git a/third_party/WebKit/LayoutTests/external/wpt/media-source/mediasource-redundant-seek.html b/third_party/WebKit/LayoutTests/external/wpt/media-source/mediasource-redundant-seek.html
index ab11b25..05eae9714 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/media-source/mediasource-redundant-seek.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/media-source/mediasource-redundant-seek.html
@@ -49,7 +49,7 @@
 
                 test.waitForExpectedEvents(function()
                 {
-                    // No more seeking or sought events should occur.
+                    // No more seeking or seeked events should occur.
                     mediaElement.addEventListener('seeking', test.unreached_func("Unexpected event 'seeking'"));
                     mediaElement.addEventListener('seeked', test.unreached_func("Unexpected event 'seeked'"));
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/media-source/mediasource-seek-during-pending-seek.html b/third_party/WebKit/LayoutTests/external/wpt/media-source/mediasource-seek-during-pending-seek.html
index bd8bcea..60c5eec 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/media-source/mediasource-seek-during-pending-seek.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/media-source/mediasource-seek-during-pending-seek.html
@@ -153,7 +153,7 @@
 
                   if (sourceBuffer.updating)
                   {
-                      // The event sought was fired prior to the appendBuffer completing.
+                      // The event seeked was fired prior to the appendBuffer completing.
                       test.expectEvent(sourceBuffer, 'updateend', 'sourceBuffer');
                       test.waitForExpectedEvents(function()
                       {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/GUM-api.https.html b/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/GUM-api.https.html
index 6e662dde..176f458 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/GUM-api.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/GUM-api.https.html
@@ -16,8 +16,8 @@
 <script src="/common/vendor-prefix.js" data-prefixed-objects='[{"ancestors":["navigator"], "name":"getUserMedia"}]'></script>
 <script>
 test(function () {
-          assert_true(undefined !== navigator.getUserMedia, "navigator.getUserMedia exists");
-      }, "getUserMedia() is present on navigator");
+  assert_true(undefined !== navigator.getUserMedia, "navigator.getUserMedia exists");
+}, "getUserMedia() is present on navigator");
 </script>
 </body>
 </html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/GUM-deny.https.html b/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/GUM-deny.https.html
index cce0e1c1..f61d6233 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/GUM-deny.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/GUM-deny.https.html
@@ -2,37 +2,36 @@
 <html>
 <head>
   <title>getUserMedia() triggers error callback when auth is denied</title>
-<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
-<link rel="help" href="http://w3c.github.io/mediacapture-main/getusermedia.html#error-names">
-<link rel="help" href="http://w3c.github.io/mediacapture-main/getusermedia.html#idl-def-MediaStreamError">
+  <link rel="author" title="Dr. A. Gouaillard" href="mailto:agouaillard@gmail.com"/>
+  <link rel="help" href="http://w3c.github.io/mediacapture-main/getusermedia.html#methods-5">
+  <link rel="help" href="http://w3c.github.io/mediacapture-main/getusermedia.html#navigatorusermediaerrorcallback">
 </head>
 <body>
-<p class="instructions">When prompted, <strong>please deny</strong> access to
-the video stream.</p>
-<h1 class="instructions">Description</h1>
-<p class="instructions">This test checks that the error callback is triggered
-when user denies access to the video stream.</p>
-
-<div id='log'></div>
-<script src=/resources/testharness.js></script>
-<script src=/resources/testharnessreport.js></script>
-<script src="/common/vendor-prefix.js" data-prefixed-objects='[{"ancestors":["navigator"], "name":"getUserMedia"}]'></script>
-<script>
-var t = async_test("Tests that the error callback is triggered when permission is denied", {timeout:10000});
-t.step(function() {
-  navigator.getUserMedia(
-    {video: true},
-    t.step_func(function (stream) {
-      assert_unreached("The success callback should not be triggered since access is to be denied");
-      t.done();
-    }),
-    t.step_func(function (error) {
-      assert_equals(error.name, "securityError", "securityError returned");
-      assert_equals(error.constraintName, undefined, "constraintName attribute not set for permission denied");
-      t.done();
-    })
-  );
-});
-</script>
+  <p class="instructions">When prompted, <strong>please deny</strong> access to
+    the video stream.</p>
+  <h1 class="instructions">Description</h1>
+  <p class="instructions">This test checks that the error callback is triggered
+    when user denies access to the video stream.</p>
+  <div id='log'></div>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src="/common/vendor-prefix.js" data-prefixed-objects='[{"ancestors":["navigator"], "name":"getUserMedia"}]'></script>
+  <script>
+    var t = async_test("Tests that the error callback is triggered when permission is denied", {timeout:10000});
+    t.step(function() {
+      navigator.getUserMedia(
+        {video: true},
+        t.step_func(function (stream) {
+        assert_unreached("The success callback should not be triggered since access is to be denied");
+        t.done();
+        }),
+        t.step_func(function (error) {
+          assert_equals(error.name, "securityError", "securityError returned as expected");
+          assert_equals(error.constraintName, undefined, "constraintName attribute not set as expected");
+          t.done();
+        })
+      );
+    });
+  </script>
 </body>
 </html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/MediaDevices-IDL-all.html b/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/MediaDevices-IDL-all.html
new file mode 100644
index 0000000..8c8fe221
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/MediaDevices-IDL-all.html
@@ -0,0 +1,45 @@
+<!doctype html>
+<html>
+  <head>
+    <title>getUserMedia: Non-Interactive test for mediaDevices APIs</title>
+    <link rel="author" title="Dr Alex Gouaillard" href="mailto:agouaillard@gmail.com"/>
+    <link rel="help" href="http://w3c.github.io/mediacapture-main/getusermedia.html#mediadevices">
+    <link rel="help" href="http://w3c.github.io/mediacapture-main/getusermedia.html#mediadevices-interface-extensions">
+    <meta name='assert' content='Check the mediaDevices APIs.'/>
+  </head>
+  <body>
+    <h1 class="instructions">Description</h1>
+    <p class="instructions">This test checks for the presence of the
+    <code>navigator.mediaDevices.getUserMedia</code> method.</p>
+    <div id='log'></div>
+    <script src=/resources/testharness.js></script>
+    <script src=/resources/testharnessreport.js></script>
+    <script src=/resources/WebIDLParser.js></script>
+    <script src=/resources/idlharness.js></script>
+    <script>
+      'use strict';
+
+      function doIdlTest(idlText) {
+        var idl_array = new IdlArray();
+
+        // dummies
+        idl_array.add_untested_idls("interface Navigator {};");
+        idl_array.add_untested_idls("interface EventTarget {};");
+        idl_array.add_untested_idls("interface EventHandler {};");
+
+        idl_array.add_untested_idls(idlText);
+
+        idl_array.add_objects({"Navigator": ["navigator"]});
+        idl_array.add_objects({"MediaDevices":["navigator.mediaDevices"]});
+        idl_array.test();
+      }
+
+      promise_test(() => {
+        return fetch('/interfaces/mediacapture-main.idl')
+          .then(response => response.text())
+          .then(doIdlTest);
+
+      }, 'Test driver')
+    </script>
+  </body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/MediaDevices-IDL-enumerateDevices-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/MediaDevices-IDL-enumerateDevices-expected.txt
new file mode 100644
index 0000000..b20b8894
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/MediaDevices-IDL-enumerateDevices-expected.txt
@@ -0,0 +1,84 @@
+This is a testharness.js-based test.
+Found 80 tests; 59 PASS, 21 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS Test MediaDevices.enumerateDevices call and result. Types only. 
+PASS Navigator interface: attribute mediaDevices 
+PASS Navigator interface: operation getUserMedia(MediaStreamConstraints,NavigatorUserMediaSuccessCallback,NavigatorUserMediaErrorCallback) 
+FAIL MediaStream interface: existence and properties of interface object Cannot read property 'has_extended_attribute' of undefined
+PASS MediaStream interface object length 
+PASS MediaStream interface object name 
+FAIL MediaStream interface: existence and properties of interface prototype object Cannot read property 'has_extended_attribute' of undefined
+PASS MediaStream interface: existence and properties of interface prototype object's "constructor" property 
+PASS MediaStream interface: attribute id 
+PASS MediaStream interface: operation getAudioTracks() 
+PASS MediaStream interface: operation getVideoTracks() 
+PASS MediaStream interface: operation getTracks() 
+PASS MediaStream interface: operation getTrackById(DOMString) 
+PASS MediaStream interface: operation addTrack(MediaStreamTrack) 
+PASS MediaStream interface: operation removeTrack(MediaStreamTrack) 
+PASS MediaStream interface: operation clone() 
+PASS MediaStream interface: attribute active 
+PASS MediaStream interface: attribute onaddtrack 
+PASS MediaStream interface: attribute onremovetrack 
+FAIL MediaStreamTrack interface: existence and properties of interface object Cannot read property 'has_extended_attribute' of undefined
+PASS MediaStreamTrack interface object length 
+PASS MediaStreamTrack interface object name 
+FAIL MediaStreamTrack interface: existence and properties of interface prototype object Cannot read property 'has_extended_attribute' of undefined
+PASS MediaStreamTrack interface: existence and properties of interface prototype object's "constructor" property 
+PASS MediaStreamTrack interface: attribute kind 
+PASS MediaStreamTrack interface: attribute id 
+PASS MediaStreamTrack interface: attribute label 
+PASS MediaStreamTrack interface: attribute enabled 
+PASS MediaStreamTrack interface: attribute muted 
+PASS MediaStreamTrack interface: attribute onmute 
+PASS MediaStreamTrack interface: attribute onunmute 
+PASS MediaStreamTrack interface: attribute readyState 
+PASS MediaStreamTrack interface: attribute onended 
+PASS MediaStreamTrack interface: operation clone() 
+PASS MediaStreamTrack interface: operation stop() 
+PASS MediaStreamTrack interface: operation getCapabilities() 
+PASS MediaStreamTrack interface: operation getConstraints() 
+PASS MediaStreamTrack interface: operation getSettings() 
+PASS MediaStreamTrack interface: operation applyConstraints(MediaTrackConstraints) 
+FAIL MediaStreamTrack interface: attribute onoverconstrained assert_true: The prototype object must have a property "onoverconstrained" expected true got false
+FAIL MediaStreamTrackEvent interface: existence and properties of interface object Cannot read property 'has_extended_attribute' of undefined
+PASS MediaStreamTrackEvent interface object length 
+PASS MediaStreamTrackEvent interface object name 
+FAIL MediaStreamTrackEvent interface: existence and properties of interface prototype object Cannot read property 'has_extended_attribute' of undefined
+PASS MediaStreamTrackEvent interface: existence and properties of interface prototype object's "constructor" property 
+PASS MediaStreamTrackEvent interface: attribute track 
+FAIL OverconstrainedErrorEvent interface: existence and properties of interface object assert_own_property: self does not have own property "OverconstrainedErrorEvent" expected property "OverconstrainedErrorEvent" missing
+FAIL OverconstrainedErrorEvent interface object length assert_own_property: self does not have own property "OverconstrainedErrorEvent" expected property "OverconstrainedErrorEvent" missing
+FAIL OverconstrainedErrorEvent interface object name assert_own_property: self does not have own property "OverconstrainedErrorEvent" expected property "OverconstrainedErrorEvent" missing
+FAIL OverconstrainedErrorEvent interface: existence and properties of interface prototype object assert_own_property: self does not have own property "OverconstrainedErrorEvent" expected property "OverconstrainedErrorEvent" missing
+FAIL OverconstrainedErrorEvent interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "OverconstrainedErrorEvent" expected property "OverconstrainedErrorEvent" missing
+FAIL OverconstrainedErrorEvent interface: attribute error assert_own_property: self does not have own property "OverconstrainedErrorEvent" expected property "OverconstrainedErrorEvent" missing
+FAIL MediaDevices interface: existence and properties of interface object Cannot read property 'has_extended_attribute' of undefined
+PASS MediaDevices interface object length 
+PASS MediaDevices interface object name 
+FAIL MediaDevices interface: existence and properties of interface prototype object Cannot read property 'has_extended_attribute' of undefined
+PASS MediaDevices interface: existence and properties of interface prototype object's "constructor" property 
+PASS MediaDevices interface: attribute ondevicechange 
+PASS MediaDevices interface: operation enumerateDevices() 
+PASS MediaDevices interface: operation getSupportedConstraints() 
+PASS MediaDevices interface: operation getUserMedia(MediaStreamConstraints) 
+PASS MediaDeviceInfo interface: existence and properties of interface object 
+PASS MediaDeviceInfo interface object length 
+PASS MediaDeviceInfo interface object name 
+PASS MediaDeviceInfo interface: existence and properties of interface prototype object 
+PASS MediaDeviceInfo interface: existence and properties of interface prototype object's "constructor" property 
+PASS MediaDeviceInfo interface: attribute deviceId 
+PASS MediaDeviceInfo interface: attribute kind 
+PASS MediaDeviceInfo interface: attribute label 
+PASS MediaDeviceInfo interface: attribute groupId 
+PASS MediaDeviceInfo interface: _mediaInfo must inherit property "deviceId" with the proper type (0) 
+PASS MediaDeviceInfo interface: _mediaInfo must inherit property "kind" with the proper type (1) 
+PASS MediaDeviceInfo interface: _mediaInfo must inherit property "label" with the proper type (2) 
+PASS MediaDeviceInfo interface: _mediaInfo must inherit property "groupId" with the proper type (3) 
+FAIL InputDeviceInfo interface: existence and properties of interface object assert_own_property: self does not have own property "InputDeviceInfo" expected property "InputDeviceInfo" missing
+FAIL InputDeviceInfo interface object length assert_own_property: self does not have own property "InputDeviceInfo" expected property "InputDeviceInfo" missing
+FAIL InputDeviceInfo interface object name assert_own_property: self does not have own property "InputDeviceInfo" expected property "InputDeviceInfo" missing
+FAIL InputDeviceInfo interface: existence and properties of interface prototype object assert_own_property: self does not have own property "InputDeviceInfo" expected property "InputDeviceInfo" missing
+FAIL InputDeviceInfo interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "InputDeviceInfo" expected property "InputDeviceInfo" missing
+FAIL InputDeviceInfo interface: operation getCapabilities() assert_own_property: self does not have own property "InputDeviceInfo" expected property "InputDeviceInfo" missing
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/MediaDevices-IDL-enumerateDevices.html b/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/MediaDevices-IDL-enumerateDevices.html
new file mode 100644
index 0000000..4dc1c60
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/MediaDevices-IDL-enumerateDevices.html
@@ -0,0 +1,62 @@
+<!doctype html>
+<html>
+<head>
+<title>enumerateDevices: test that enumerateDevices is present</title>
+<link rel="author" title="Dr Alex Gouaillard" href="mailto:agouaillard@gmail.com"/>
+<link rel="help" href="http://w3c.github.io/mediacapture-main/getusermedia.html#methods-2">
+<link rel="help" href="http://w3c.github.io/mediacapture-main/getusermedia.html#device-info">
+<link rel="help" href="http://w3c.github.io/mediacapture-main/getusermedia.html#idl-def-MediaDeviceKind">
+<meta name='assert' content='Check that the enumerateDevices() method is present.'/>
+</head>
+<body>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks for the presence of the
+<code>navigator.mediaDevices.enumerateDevices()</code> method.</p>
+<div id='log'></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/resources/WebIDLParser.js></script>
+<script src=/resources/idlharness.js></script>
+<script>
+  "use strict";
+
+  function doIdlTest(idlText) {
+    const MDI_idl = new IdlArray();
+
+    MDI_idl.add_untested_idls("interface Navigator {};");
+    MDI_idl.add_idls(idlText);
+
+    assert_true(undefined !== navigator.mediaDevices.enumerateDevices,
+      "navigator.mediaDevices.enumerateDevices exists");
+
+    return navigator.mediaDevices.enumerateDevices()
+    .then(function(list) {
+      if( list.length > 0 ) {
+        window._mediaInfo = list[0];
+        MDI_idl.add_objects({MediaDeviceInfo: ["_mediaInfo"]});
+      }
+
+      for(const media of list) {
+        if( media.kind == "audioinput" ||
+            media.kind == "videoinput") {
+          // TODO -- Check InputDeviceInfo IDL, getCapabilities()
+        } else if ( media.kind == "audiooutput" ) {
+          // TODO -- pass
+        } else {
+          assert_unreached("media.kind should be one of 'audioinput', 'videoinput', or 'audiooutput'.")
+        }
+      }
+
+      MDI_idl.test();
+    });
+  }
+
+  promise_test(() => {
+    return fetch('/interfaces/mediacapture-main.idl')
+      .then(response => response.text())
+      .then(doIdlTest);
+
+  }, "Test MediaDevices.enumerateDevices call and result. Types only.");
+</script>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-abort-method.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-abort-method.https-expected.txt
new file mode 100644
index 0000000..4fe4e85c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-abort-method.https-expected.txt
@@ -0,0 +1,6 @@
+This is a testharness.js-based test.
+PASS Throws if the promise [[state]] is not "interactive" 
+FAIL Calling abort must not change the [[state]] until after "interactive" assert_true: Unexpected promise rejection: Request failed expected true got false
+FAIL calling .abort() causes acceptPromise to reject and closes the request. assert_true: Unexpected promise rejection: Request failed expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-abort-method.https.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-abort-method.https.html
new file mode 100644
index 0000000..f596800
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-abort-method.https.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<!-- Copyright © 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
+<meta charset="utf-8">
+<title>Test for PaymentRequest.abort() method</title>
+<link rel="help" href="https://w3c.github.io/browser-payment-api/#abort-method">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+"use strict";
+setup(() => {}, {
+  // Ignore unhandled rejections resulting from .show()'s acceptPromise
+  // not being explicitly handled.
+  allow_uncaught_exception: true,
+});
+const basicCard = Object.freeze({ supportedMethods: ["basic-card"] });
+const defaultMethods = Object.freeze([basicCard]);
+const defaultDetails = Object.freeze({
+  total: {
+    label: "Total",
+    amount: {
+      currency: "USD",
+      value: "1.00",
+    },
+  },
+});
+
+promise_test(async t => {
+  // request is in "created" state
+  const request = new PaymentRequest(defaultMethods, defaultDetails);
+  await promise_rejects(t, "InvalidStateError", request.abort());
+}, `Throws if the promise [[state]] is not "interactive"`);
+
+promise_test(async t => {
+  // request is in "created" state.
+  const request = new PaymentRequest(defaultMethods, defaultDetails);
+  await promise_rejects(t, "InvalidStateError", request.abort());
+  // Call it again, for good measure.
+  await promise_rejects(t, "InvalidStateError", request.abort());
+  // The request's state is "created", so let's show it
+  // which changes the state to "interactive.".
+  request.show();
+  // Let's set request the state to "closed" by calling .abort()
+  try {
+    await request.abort();
+  } catch (err) {
+    assert_true(false, "Unexpected promise rejection: " + err.message);
+  }
+  // The request is now "closed", so...
+  await promise_rejects(t, "InvalidStateError", request.abort());
+}, `Calling abort must not change the [[state]] until after "interactive"`);
+
+promise_test(async t => {
+  const request = new PaymentRequest(defaultMethods, defaultDetails);
+  const acceptPromise = request.show();
+  try {
+    await request.abort();
+  } catch (err) {
+    assert_true(false, "Unexpected promise rejection: " + err.message);
+  }
+  await promise_rejects(t, "AbortError", acceptPromise);
+  // As request is now "closed", trying to show it will fail
+  await promise_rejects(t, "InvalidStateError", request.show());
+}, "calling .abort() causes acceptPromise to reject and closes the request.");
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-canmakepayment-method-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-canmakepayment-method-expected.txt
new file mode 100644
index 0000000..bfcbf39
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-canmakepayment-method-expected.txt
@@ -0,0 +1,9 @@
+This is a testharness.js-based test.
+FAIL If request.[[state]] is "created", then return a promise that resolves to true for known method. promise_test: Unhandled rejection with value: object "SecurityError: Failed to construct 'PaymentRequest': Must be in a secure context"
+FAIL If request.[[state]] is "interactive", then return a promise rejected with an "InvalidStateError" DOMException. promise_test: Unhandled rejection with value: object "SecurityError: Failed to construct 'PaymentRequest': Must be in a secure context"
+FAIL If request.[[state]] is "closed", then return a promise rejected with an "InvalidStateError" DOMException. promise_test: Unhandled rejection with value: object "SecurityError: Failed to construct 'PaymentRequest': Must be in a secure context"
+FAIL If payment method identifier and serialized parts are supported, resolve promise with true. promise_test: Unhandled rejection with value: object "SecurityError: Failed to construct 'PaymentRequest': Must be in a secure context"
+FAIL If payment method identifier is unknown, resolve promise with false. assert_true: Unexpected exception testing method this-is-not-supported, expected false. See error console. expected true got false
+FAIL Optionally, at the user agent's discretion, return a promise rejected with a "NotAllowedError" DOMException. promise_test: Unhandled rejection with value: object "SecurityError: Failed to construct 'PaymentRequest': Must be in a secure context"
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-canmakepayment-method.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-canmakepayment-method.html
new file mode 100644
index 0000000..86fae2e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-canmakepayment-method.html
@@ -0,0 +1,148 @@
+<!DOCTYPE html>
+<!-- Copyright © 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
+<meta charset="utf-8">
+<title>Tests for PaymentRequest.canMakePayment() method</title>
+<link rel="help" href="https://w3c.github.io/browser-payment-api/#show-method">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+const basicCard = Object.freeze({ supportedMethods: ["basic-card"] });
+const defaultMethods = Object.freeze([basicCard]);
+const defaultDetails = Object.freeze({
+  total: {
+    label: "Total",
+    amount: {
+      currency: "USD",
+      value: "1.00",
+    },
+  },
+});
+
+promise_test(async t => {
+  const request = new PaymentRequest(defaultMethods, defaultDetails);
+  try {
+    assert_true(
+      await request.canMakePayment(),
+      `canMakePaymentPromise should be true`
+    );
+    assert_true(
+      await request.canMakePayment(),
+      `canMakePaymentPromise should be true`
+    );
+  } catch (err) {
+    assert_equal(
+      err.name,
+      "NotAllowedError",
+      "if it throws, then it must be a NotAllowedError."
+    );
+  }
+}, `If request.[[state]] is "created", then return a promise that resolves to true for known method.`);
+
+promise_test(async t => {
+  const request = new PaymentRequest(defaultMethods, defaultDetails);
+  const acceptPromise = request.show(); // Sets state to "interactive"
+  const canMakePaymentPromise = request.canMakePayment();
+  try {
+    const result = await canMakePaymentPromise;
+    assert_true(
+      false,
+      `canMakePaymentPromise should have thrown InvalidStateError`
+    );
+  } catch (err) {
+    await promise_rejects(t, "InvalidStateError", canMakePaymentPromise);
+  } finally {
+    await request.abort();
+    await promise_rejects(t, "AbortError", acceptPromise);
+  }
+  // The state should be "closed"
+  await promise_rejects(t, "InvalidStateError", request.canMakePayment());
+}, `If request.[[state]] is "interactive", then return a promise rejected with an "InvalidStateError" DOMException.`);
+
+promise_test(async t => {
+  const request = new PaymentRequest(defaultMethods, defaultDetails);
+  const acceptPromise = request.show(); // The state is now "interactive"
+  acceptPromise.catch(() => {}); // no-op, just to silence unhandled rejection in devtools.
+  await request.abort(); // The state is now "closed"
+  await promise_rejects(t, "InvalidStateError", request.canMakePayment());
+  try {
+    const result = await request.canMakePayment();
+    assert_true(
+      false,
+      `should have thrown InvalidStateError, but instead returned "${result}"`
+    );
+  } catch (err) {
+    assert_equal(
+      err.name,
+      "InvalidStateError",
+      "must be an InvalidStateError."
+    );
+  }
+}, `If request.[[state]] is "closed", then return a promise rejected with an "InvalidStateError" DOMException.`);
+
+promise_test(async t => {
+  const request = new PaymentRequest(defaultMethods, defaultDetails);
+  assert_true(await request.canMakePayment(), "basic-card should be supported");
+}, `If payment method identifier and serialized parts are supported, resolve promise with true.`);
+
+promise_test(async t => {
+  const unsupportedMethods = [
+    "this-is-not-supported",
+    "https://not.supported",
+    "basic-card?not-really",
+    "basic-card://not-ok",
+    "basic card",
+    "/basic card/",
+    "BaSicCarD",
+    "BASIC-CARD",
+    " basic-card ",
+    "this is not supported",
+    " ",
+  ];
+  for (const method of unsupportedMethods) {
+    try {
+      const request = new PaymentRequest(
+        [{ supportedMethods: [method] }],
+        defaultDetails
+      );
+      assert_false(
+        await request.canMakePayment(),
+        `method "${method}" must not be supported`
+      );
+    } catch (err) {
+      assert_true(
+        false,
+        `Unexpected exception testing method ${method}, expected false. See error console.`
+      );
+    }
+  }
+}, `If payment method identifier is unknown, resolve promise with false.`);
+
+promise_test(async t => {
+  // This test might never actually hit its assertion, but that's allowed.
+  const request = new PaymentRequest(defaultMethods, defaultDetails);
+  for (let i = 0; i < 1000; i++) {
+    try {
+      await request.canMakePayment();
+    } catch (err) {
+      assert_equal(
+        err.name,
+        "NotAllowedError",
+        "if it throws, then it must be a NotAllowedError."
+      );
+      break;
+    }
+  }
+  for (let i = 0; i < 1000; i++) {
+    try {
+      await new PaymentRequest(defaultMethods, defaultDetails).canMakePayment();
+    } catch (err) {
+      assert_equal(
+        err.name,
+        "NotAllowedError",
+        "if it throws, then it must be a NotAllowedError."
+      );
+      break;
+    }
+  }
+}, `Optionally, at the user agent's discretion, return a promise rejected with a "NotAllowedError" DOMException.`);
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-constructor.https.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-constructor.https.html
index 0c2f687..971622e8 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-constructor.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-constructor.https.html
@@ -41,7 +41,7 @@
 
 test(() => {
   const newDetails = Object.assign({}, defaultDetails, {
-    id: "".padStart(1024, "\n test 123 \t \n "),
+    id: "".padStart(1024, "a"),
   });
   const request = new PaymentRequest(defaultMethods, newDetails);
   assert_equals(
@@ -305,6 +305,17 @@
   }, `If details.total.amount.value is not a valid decimal monetary value (in this case "${amount}"), then throw a TypeError`);
 }
 
+for (const prop in ["displayItems", "shippingOptions", "modifiers"]) {
+  test(() => {
+    try {
+      const details = Object.assign({}, defaultDetails, { [prop]: [] });
+      new PaymentRequest(defaultMethods, details);
+    } catch (err) {
+      assert_true(false, `${prop} can be zero length`);
+    }
+  }, `PaymentDetailsBase.${prop} can be 0 length`);
+}
+
 test(() => {
   assert_throws(
     {
@@ -375,7 +386,9 @@
       [
         {
           supportedMethods: ["basic-card"],
-          data: ["some-data"],
+          data: {
+            supportedTypes: ["debit"],
+          },
         },
       ],
       {
@@ -410,6 +423,49 @@
   assert_false(itThrows, "shouldn't throw when given a negative value");
 }, "Negative values are allowed for displayItems.amount.value, irrespective of total amount");
 
+test(() => {
+  let itThrows = false;
+  const largeMoney = "1".repeat(510);
+
+  try {
+    new PaymentRequest(
+      [
+        {
+          supportedMethods: ["basic-card"],
+        },
+      ],
+      {
+        total: {
+          label: "",
+          amount: {
+            currency: "USD",
+            value: `${largeMoney}.${largeMoney}`,
+          },
+        },
+        displayItems: [
+          {
+            label: "",
+            amount: {
+              currency: "USD",
+              value: `-${largeMoney}`,
+            },
+          },
+          {
+            label: "",
+            amount: {
+              currency: "AUD",
+              value: `-${largeMoney}.${largeMoney}`,
+            },
+          },
+        ],
+      }
+    );
+  } catch (err) {
+    itThrows = true;
+  }
+  assert_false(itThrows, "shouldn't throw when given absurd monetary values");
+}, "it handles high precision currency values without throwing");
+
 // Process shipping options:
 const defaultAmount = Object.freeze({
   currency: "USD",
@@ -451,7 +507,11 @@
   const shippingOptions = [defaultShippingOption];
   const details = Object.assign({}, defaultDetails, { shippingOptions });
   const request = new PaymentRequest(defaultMethods, details);
-  assert_equals(request.shippingOption, null, "request.shippingOption must be null");
+  assert_equals(
+    request.shippingOption,
+    null,
+    "request.shippingOption must be null"
+  );
 }, "If there is no selected shipping option, then PaymentRequest.shippingOption remains null");
 
 test(() => {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-show-method.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-show-method.https-expected.txt
index c7173f3f..d7e70afa 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-show-method.https-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-show-method.https-expected.txt
@@ -1,4 +1,5 @@
 This is a testharness.js-based test.
-FAIL If the user agent's "payment request is showing" boolean is true, then return a promise rejected with an "AbortError" DOMException. Test bug: need to pass exception to assert_throws()
+FAIL Throws if the promise [[state]] is not "created" promise_test: Unhandled rejection with value: object "UnknownError: Request failed"
+FAIL If the user agent's "payment request is showing" boolean is true, then return a promise rejected with an "AbortError" DOMException. assert_throws: function "function () { throw e }" threw object "UnknownError: Request failed" that is not a DOMException AbortError: property "code" is equal to 0, expected 20
 Harness: the test ran to completion.
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-show-method.https.html b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-show-method.https.html
index d170ea0..1bdbba4 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-show-method.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-request/payment-request-show-method.https.html
@@ -7,34 +7,34 @@
 <script src="/resources/testharnessreport.js"></script>
 <script>
 'use strict';
+const basicCard = Object.freeze({ supportedMethods: ["basic-card"] });
+const defaultMethods = Object.freeze([basicCard]);
+const defaultDetails = Object.freeze({
+  total: {
+    label: "Total",
+    amount: {
+      currency: "USD",
+      value: "1.00",
+    },
+  },
+});
 
-promise_test(t => {
-  const request1 = new PaymentRequest([{
-    supportedMethods: ['basic-card'],
-  }], {
-    total: {
-      label: 'request1',
-      amount: {
-        currency: 'USD',
-        value: '1.00',
-      },
-    },
-  });
-  const request2 = new PaymentRequest([{
-    supportedMethods: ['basic-card'],
-  }], {
-    total: {
-      label: 'request2',
-      amount: {
-        currency: 'USD',
-        value: '1.00',
-      },
-    },
-  });
-  const result = promise_rejects(t, null, request1.show());
-  promise_rejects(t, 'AbortError', request2.show())
-    .then(t.step_func(() => request1.abort()));
-  return result;
-}, 'If the user agent\'s "payment request is showing" boolean is true, ' +
-   'then return a promise rejected with an "AbortError" DOMException.');
+promise_test(async t => {
+  const request = new PaymentRequest(defaultMethods, defaultDetails);
+  const acceptPromise = request.show(); // Sets state to "interactive"
+  await promise_rejects(t, "InvalidStateError", request.show());
+  await request.abort();
+  await promise_rejects(t, "AbortError", acceptPromise);
+}, `Throws if the promise [[state]] is not "created"`);
+
+promise_test(async t => {
+  const request1 = new PaymentRequest(defaultMethods, defaultDetails);
+  const request2 = new PaymentRequest(defaultMethods, defaultDetails);
+  const acceptPromise1 = request1.show();
+  const acceptPromise2 = request2.show();
+  await promise_rejects(t, "AbortError", acceptPromise2);
+  await request1.abort();
+  await promise_rejects(t, "AbortError", acceptPromise1);
+}, `If the user agent's "payment request is showing" boolean is true, then return a promise rejected with an "AbortError" DOMException.`);
+
 </script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/pointerevents/pointerevent_pointerenter_does_not_bubble-manual.html b/third_party/WebKit/LayoutTests/external/wpt/pointerevents/pointerevent_pointerenter_does_not_bubble-manual.html
index c59741fe..3f05833 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/pointerevents/pointerevent_pointerenter_does_not_bubble-manual.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/pointerevents/pointerevent_pointerenter_does_not_bubble-manual.html
@@ -45,7 +45,7 @@
                 on_event(parent0, "pointerenter", function (event) {
                     detected_pointertypes[event.pointerType] = true;
                     test_pointerEvent.step(function () {
-                        assert_equals(event.target.id, "parent0", "Received " + event.type + " in parent for " + event.target.id);
+                        assert_equals(event.target.id, "parent0", "Recieved " + event.type + " in parent for " + event.target.id);
                     });
                 });
             }
diff --git a/third_party/WebKit/LayoutTests/external/wpt/pointerevents/pointerevent_pointerleave_does_not_bubble-manual.html b/third_party/WebKit/LayoutTests/external/wpt/pointerevents/pointerevent_pointerleave_does_not_bubble-manual.html
index e1c55db..c0e551cd 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/pointerevents/pointerevent_pointerleave_does_not_bubble-manual.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/pointerevents/pointerevent_pointerleave_does_not_bubble-manual.html
@@ -34,7 +34,7 @@
 
                 on_event(parent0, "pointerleave", function (event) {
                     test_pointerEvent.step(function () {
-                        assert_equals(event.target.id, "parent0", "Received " + event.type + " in parent for " + event.target.id);
+                        assert_equals(event.target.id, "parent0", "Recieved " + event.type + " in parent for " + event.target.id);
                     });
                     test_pointerEvent.done(); // complete test
                 });
diff --git a/third_party/WebKit/LayoutTests/external/wpt/pointerevents/pointerevent_releasepointercapture_events_to_original_target-manual.html b/third_party/WebKit/LayoutTests/external/wpt/pointerevents/pointerevent_releasepointercapture_events_to_original_target-manual.html
index 2e5494b..89f3d839 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/pointerevents/pointerevent_releasepointercapture_events_to_original_target-manual.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/pointerevents/pointerevent_releasepointercapture_events_to_original_target-manual.html
@@ -67,7 +67,7 @@
                         // if any other events are received after releaseCapture, then the test fails
                         test(function () {
                             assert_unreached(event.target.id + "-" + event.type + " should be handled by target element handler");
-                        }, expectedPointerType + " No other events should be received by capturing node after release");
+                        }, expectedPointerType + " No other events should be recieved by capturing node after release");
                     }
                 }
             }
@@ -78,7 +78,7 @@
                 if (f_gotPointerCapture) {
                     if(event.type != "pointerout" && event.type != "pointerleave") {
                         test(function () {
-                            assert_unreached("The Target element should not have received any events while capture is active. Event received:" + event.type + ".  ");
+                            assert_unreached("The Target element should not have received any events while capture is active. Event recieved:" + event.type + ".  ");
                         }, expectedPointerType + " The target element should not receive any events while capture is active");
                     }
                 }
diff --git a/third_party/WebKit/LayoutTests/external/wpt/pointerlock/pointerlock_fullscreen-manual.html b/third_party/WebKit/LayoutTests/external/wpt/pointerlock/pointerlock_fullscreen-manual.html
index a78762dd..7ce91ad 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/pointerlock/pointerlock_fullscreen-manual.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/pointerlock/pointerlock_fullscreen-manual.html
@@ -96,7 +96,7 @@
                 logStatus();
 
                 scriptExitFullscreenTest.step(function() {
-                    assert_true(FullscreenElement() === null, "fullscreen is successfully exited");
+                    assert_true(FullscreenElement() === null, "fullscreen is sucessfully exited");
                     assert_true(document.pointerLockElement === test_element, "pointer is still locked at the target element");
                 });
                 scriptExitFullscreenTest.done();
@@ -114,8 +114,8 @@
                 if(gestureExit_fs) {
                 // second test, fullscreen and pointer lock both exited
                     gestureExitFullscreenTest.step(function() {
-                        assert_true(document.pointerLockElement === null, "pointer is successfully exited");
-                        assert_true(FullscreenElement() === null, "fullscreen is successfully exited");
+                        assert_true(document.pointerLockElement === null, "pointer is sucessfully exited");
+                        assert_true(FullscreenElement() === null, "fullscreen is sucessfully exited");
                 });
                 gestureExitFullscreenTest.done();
                 }
@@ -132,8 +132,8 @@
             if(gestureExit_pl) {
             // second test, fullscreen and pointer lock both exited
                 gestureExitFullscreenTest.step(function() {
-                    assert_true(document.pointerLockElement === null, "pointer is successfully exited");
-                    assert_true(FullscreenElement() === null, "fullscreen is successfully exited");
+                    assert_true(document.pointerLockElement === null, "pointer is sucessfully exited");
+                    assert_true(FullscreenElement() === null, "fullscreen is sucessfully exited");
                 });
 
             gestureExitFullscreenTest.done();
diff --git a/third_party/WebKit/LayoutTests/external/wpt/referrer-policy/css-integration/README.md b/third_party/WebKit/LayoutTests/external/wpt/referrer-policy/css-integration/README.md
index b1a2f9c..2edb24f 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/referrer-policy/css-integration/README.md
+++ b/third_party/WebKit/LayoutTests/external/wpt/referrer-policy/css-integration/README.md
@@ -1,4 +1,4 @@
-These tests exercise different ways to load an image, generated via
+These tests exercise differnt ways to load an image, generated via
 ```/referrer-policy/generic/subresource/image.py?id=<UUID>``` and later
 verify the headers used to request that image.
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/resource-timing/resources/TAOResponse.py b/third_party/WebKit/LayoutTests/external/wpt/resource-timing/resources/TAOResponse.py
index 325a684a..cc8fa5f 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/resource-timing/resources/TAOResponse.py
+++ b/third_party/WebKit/LayoutTests/external/wpt/resource-timing/resources/TAOResponse.py
@@ -17,18 +17,18 @@
     # case-sensitive match for origin, pass
         response.headers.set('Timing-Allow-Origin', origin)
     elif tao == 'space':
-    # space separated list of origin and wildcard, fail
+    # space seperated list of origin and wildcard, fail
         response.headers.set('Timing-Allow-Origin', (origin + ' *'))
     elif tao == 'multi':
-    # more than one TAO values, separated by common, pass
+    # more than one TAO values, seperated by common, pass
         response.headers.set('Timing-Allow-Origin', origin)
         response.headers.append('Timing-Allow-Origin', '*')
     elif tao == 'match_origin':
-    # contains a match of origin, separated by common, pass
+    # contains a match of origin, seperated by common, pass
         response.headers.set('Timing-Allow-Origin', origin)
         response.headers.append('Timing-Allow-Origin', "fake")
     elif tao == 'match_wildcard':
-    # contains a wildcard, separated by common, pass
+    # contains a wildcard, seperated by common, pass
         response.headers.set('Timing-Allow-Origin', "fake")
         response.headers.append('Timing-Allow-Origin', '*')
     elif tao == 'uppercase':
diff --git a/third_party/WebKit/LayoutTests/external/wpt/resources/test/conftest.py b/third_party/WebKit/LayoutTests/external/wpt/resources/test/conftest.py
index 8310ecf..e667ef46 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/resources/test/conftest.py
+++ b/third_party/WebKit/LayoutTests/external/wpt/resources/test/conftest.py
@@ -117,12 +117,12 @@
     def _summarize_test(test_obj):
         del test_obj['index']
 
-        if 'phases' in test_obj:
-            for key, value in [item for item in test_obj['phases'].items()]:
-                if test_obj['phase'] == value:
-                    test_obj['phase_string'] = key
-            del test_obj['phases']
-            del test_obj['phase']
+        assert 'phase' in test_obj
+        assert 'phases' in test_obj
+        assert 'COMPLETE' in test_obj['phases']
+        assert test_obj['phase'] == test_obj['phases']['COMPLETE']
+        del test_obj['phases']
+        del test_obj['phase']
 
         return HTMLItem._expand_status(HTMLItem._scrub_stack(test_obj))
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/resources/test/tests/iframe-consolidate-tests.html b/third_party/WebKit/LayoutTests/external/wpt/resources/test/tests/iframe-consolidate-tests.html
index 085db26..90c5f8ec 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/resources/test/tests/iframe-consolidate-tests.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/resources/test/tests/iframe-consolidate-tests.html
@@ -35,23 +35,20 @@
       "status_string": "PASS",
       "name": "Promise rejection",
       "properties": {},
-      "phase_string": "COMPLETE",
-      "message": {},
-      "stack": "(implementation-defined)"
+      "message": null,
+      "stack": null
     },
     {
       "status_string": "PASS",
       "name": "Promise resolution",
       "properties": {},
-      "phase_string": "COMPLETE",
-      "message": {},
-      "stack": "(implementation-defined)"
+      "message": null,
+      "stack": null
     },
     {
       "status_string": "FAIL",
       "name": "Promises and test assertion failures (should fail)",
       "properties": {},
-      "phase_string": "COMPLETE",
       "message": "assert_true: This failure is expected expected true got false",
       "stack": "(implementation-defined)"
     },
@@ -59,17 +56,15 @@
       "status_string": "PASS",
       "name": "Promises are supported in your browser",
       "properties": {},
-      "phase_string": "COMPLETE",
-      "message": {},
-      "stack": "(implementation-defined)"
+      "message": null,
+      "stack": null
     },
     {
       "status_string": "PASS",
       "name": "Promises resolution chaining",
       "properties": {},
-      "phase_string": "COMPLETE",
-      "message": {},
-      "stack": "(implementation-defined)"
+      "message": null,
+      "stack": null
     },
     {
       "status_string": "PASS",
@@ -82,15 +77,13 @@
       "status_string": "PASS",
       "name": "Use of step_func with Promises",
       "properties": {},
-      "phase_string": "COMPLETE",
-      "message": {},
-      "stack": "(implementation-defined)"
+      "message": null,
+      "stack": null
     },
     {
       "status_string": "FAIL",
       "name": "Use of unreached_func with Promises (should fail)",
       "properties": {},
-      "phase_string": "COMPLETE",
       "message": "assert_unreached: This failure is expected Reached unreachable code",
       "stack": "(implementation-defined)"
     }
diff --git a/third_party/WebKit/LayoutTests/external/wpt/resources/test/tests/worker-dedicated.html b/third_party/WebKit/LayoutTests/external/wpt/resources/test/tests/worker-dedicated.html
index ce998e0..0ca3e4d 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/resources/test/tests/worker-dedicated.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/resources/test/tests/worker-dedicated.html
@@ -52,7 +52,6 @@
       "status_string": "FAIL",
       "name": "Untitled",
       "properties": {},
-      "phase_string": "COMPLETE",
       "message": "Error: This failure is expected.",
       "stack": "(implementation-defined)"
     },
@@ -60,31 +59,27 @@
       "status_string": "PASS",
       "name": "Worker async_test that completes successfully",
       "properties": {},
-      "phase_string": "COMPLETE",
-      "message": {},
-      "stack": "(implementation-defined)"
+      "message": null,
+      "stack": null
     },
     {
       "status_string": "PASS",
       "name": "Worker test that completes successfully",
       "properties": {},
-      "phase_string": "COMPLETE",
-      "message": {},
-      "stack": "(implementation-defined)"
+      "message": null,
+      "stack": null
     },
     {
       "status_string": "NOTRUN",
       "name": "Worker test that doesn't run ('NOT RUN')",
       "properties": {},
-      "phase_string": "COMPLETE",
-      "message": {},
-      "stack": "(implementation-defined)"
+      "message": null,
+      "stack": null
     },
     {
       "status_string": "FAIL",
       "name": "Worker test that fails ('FAIL')",
       "properties": {},
-      "phase_string": "COMPLETE",
       "message": "assert_true: Failing test expected true got false",
       "stack": "(implementation-defined)"
     },
@@ -92,9 +87,8 @@
       "status_string": "TIMEOUT",
       "name": "Worker test that times out ('TIMEOUT')",
       "properties": {},
-      "phase_string": "COMPLETE",
       "message": "Test timed out",
-      "stack": "(implementation-defined)"
+      "stack": null
     }
   ],
   "type": "complete"
diff --git a/third_party/WebKit/LayoutTests/external/wpt/resources/test/tests/worker-service.html b/third_party/WebKit/LayoutTests/external/wpt/resources/test/tests/worker-service.html
index 376dca46..418f6d7 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/resources/test/tests/worker-service.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/resources/test/tests/worker-service.html
@@ -85,31 +85,27 @@
       "status_string": "PASS",
       "name": "Worker async_test that completes successfully",
       "properties": {},
-      "phase_string": "COMPLETE",
-      "message": {},
-      "stack": "(implementation-defined)"
+      "message": null,
+      "stack": null
     },
     {
       "status_string": "PASS",
       "name": "Worker test that completes successfully",
       "properties": {},
-      "phase_string": "COMPLETE",
-      "message": {},
-      "stack": "(implementation-defined)"
+      "message": null,
+      "stack": null
     },
     {
       "status_string": "NOTRUN",
       "name": "Worker test that doesn't run ('NOT RUN')",
       "properties": {},
-      "phase_string": "COMPLETE",
-      "message": {},
-      "stack": "(implementation-defined)"
+      "message": null,
+      "stack": null
     },
     {
       "status_string": "FAIL",
       "name": "Worker test that fails ('FAIL')",
       "properties": {},
-      "phase_string": "COMPLETE",
       "message": "assert_true: Failing test expected true got false",
       "stack": "(implementation-defined)"
     },
@@ -117,9 +113,8 @@
       "status_string": "TIMEOUT",
       "name": "Worker test that times out ('TIMEOUT')",
       "properties": {},
-      "phase_string": "COMPLETE",
       "message": "Test timed out",
-      "stack": "(implementation-defined)"
+      "stack": null
     }
   ],
   "type": "complete"
diff --git a/third_party/WebKit/LayoutTests/external/wpt/resources/test/tests/worker-shared.html b/third_party/WebKit/LayoutTests/external/wpt/resources/test/tests/worker-shared.html
index 5e2676f..a5601de 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/resources/test/tests/worker-shared.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/resources/test/tests/worker-shared.html
@@ -39,33 +39,29 @@
       "stack": null
     },
     {
-      "message": {},
+      "message": null,
       "name": "Worker async_test that completes successfully",
-      "phase_string": "COMPLETE",
       "properties": {},
-      "stack": "(implementation-defined)",
+      "stack": null,
       "status_string": "PASS"
     },
     {
-      "message": {},
+      "message": null,
       "name": "Worker test that completes successfully",
-      "phase_string": "COMPLETE",
       "properties": {},
-      "stack": "(implementation-defined)",
+      "stack": null,
       "status_string": "PASS"
     },
     {
-      "message": {},
+      "message": null,
       "name": "Worker test that doesn't run ('NOT RUN')",
-      "phase_string": "COMPLETE",
       "properties": {},
-      "stack": "(implementation-defined)",
+      "stack": null,
       "status_string": "NOTRUN"
     },
     {
       "message": "assert_true: Failing test expected true got false",
       "name": "Worker test that fails ('FAIL')",
-      "phase_string": "COMPLETE",
       "properties": {},
       "stack": "(implementation-defined)",
       "status_string": "FAIL"
@@ -73,9 +69,8 @@
     {
       "message": "Test timed out",
       "name": "Worker test that times out ('TIMEOUT')",
-      "phase_string": "COMPLETE",
       "properties": {},
-      "stack": "(implementation-defined)",
+      "stack": null,
       "status_string": "TIMEOUT"
     }
   ],
diff --git a/third_party/WebKit/LayoutTests/external/wpt/resources/testharness.js b/third_party/WebKit/LayoutTests/external/wpt/resources/testharness.js
index a0ec94d..bb63e02c 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/resources/testharness.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/resources/testharness.js
@@ -591,7 +591,7 @@
 
         /**
          * Returns a Promise that will resolve after the specified event or
-         * series of events has occurred.
+         * series of events has occured.
          */
         this.wait_for = function(types) {
             if (waitingFor) {
@@ -971,7 +971,7 @@
     function assert_approx_equals(actual, expected, epsilon, description)
     {
         /*
-         * Test if two primitive numbers are equal within +/- epsilon
+         * Test if two primitive numbers are equal withing +/- epsilon
          */
         assert(typeof actual === "number",
                "assert_approx_equals", description,
@@ -1371,12 +1371,14 @@
             this._structured_clone = merge({
                 name:String(this.name),
                 properties:merge({}, this.properties),
+                phases:merge({}, this.phases)
             }, Test.statuses);
         }
         this._structured_clone.status = this.status;
         this._structured_clone.message = this.message;
         this._structured_clone.stack = this.stack;
         this._structured_clone.index = this.index;
+        this._structured_clone.phase = this.phase;
         return this._structured_clone;
     };
 
@@ -1547,10 +1549,12 @@
         var clone = {};
         Object.keys(this).forEach(
                 (function(key) {
-                    if (typeof(this[key]) === "object") {
-                        clone[key] = merge({}, this[key]);
+                    var value = this[key];
+
+                    if (typeof value === "object" && value !== null) {
+                        clone[key] = merge({}, value);
                     } else {
-                        clone[key] = this[key];
+                        clone[key] = value;
                     }
                 }).bind(this));
         clone.phases = merge({}, this.phases);
diff --git a/third_party/WebKit/LayoutTests/external/wpt/secure-contexts/basic-popup-and-iframe-tests.https.js b/third_party/WebKit/LayoutTests/external/wpt/secure-contexts/basic-popup-and-iframe-tests.https.js
index 93b5e2b..ebc9f2b1 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/secure-contexts/basic-popup-and-iframe-tests.https.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/secure-contexts/basic-popup-and-iframe-tests.https.js
@@ -73,7 +73,7 @@
                eResultFromPostMessage),
   new LoadType("a blob: URI",
                eLoadInEverything,
-               URL.createObjectURL(new Blob(["<script>(opener||parent).postMessage(isSecureContext, '*')</script>"])),
+               URL.createObjectURL(new Blob(["<script>(opener||parent).postMessage(isSecureContext, '*')</script>"], {type: "text/html"})),
                eSecureIfCreatorSecure,
                eResultFromPostMessage),
   new LoadType("a srcdoc",
diff --git a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-header-visibility.https.html b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-header-visibility.https.html
index 04e1677b..1ceb164 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-header-visibility.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-header-visibility.https.html
@@ -22,7 +22,7 @@
         frame.src = scope;
         document.body.appendChild(frame);
 
-        // Resolve a promise when we receive 2 success messages
+        // Resolve a promise when we recieve 2 success messages
         return new Promise(function(resolve, reject) {
           var remaining = 4;
           function onMessage(e) {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-request-fallback.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-request-fallback.https-expected.txt
deleted file mode 100644
index 3a113340..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-request-fallback.https-expected.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-This is a testharness.js-based test.
-PASS initialize global state 
-PASS The SW must intercept the request for a main resource. 
-PASS The SW must intercept the request of same origin XHR. 
-FAIL The SW must intercept the request of CORS-unsupported other origin XHR. Test bug: need to pass exception to assert_throws()
-FAIL The SW must intercept the request of CORS-supported other origin XHR. assert_object_equals: property "url" expected object "[object Object]" got object "[object Object]"
-PASS The SW must intercept only the first request of redirected XHR. 
-FAIL The SW must intercept only the first request for XHR which is redirected to CORS-unsupported other origin. Test bug: need to pass exception to assert_throws()
-FAIL The SW must intercept only the first request for XHR which is redirected to CORS-supported other origin. assert_object_equals: property "url" expected object "[object Object]" got object "[object Object]"
-PASS The SW must intercept the request for image. 
-PASS The SW must intercept the request for other origin image. 
-FAIL The SW must intercept the request for CORS-unsupported other origin image. Test bug: need to pass exception to assert_throws()
-FAIL The SW must intercept the request for CORS-supported other origin image. assert_object_equals: property "url" expected object "[object Object]" got object "[object Object]"
-PASS The SW must intercept only the first request for redirected image resource. 
-PASS The SW must intercept only the first request for image resource which is redirected to other origin. 
-FAIL The SW must intercept only the first request for image resource which is redirected to CORS-unsupported other origin. Test bug: need to pass exception to assert_throws()
-FAIL The SW must intercept only the first request for image resource which is redirected to CORS-supported other origin. assert_object_equals: property "url" expected object "[object Object]" got object "[object Object]"
-PASS restore global state 
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-request-fallback.https.html b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-request-fallback.https.html
index 400d83a..634b8d6 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-request-fallback.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-request-fallback.https.html
@@ -95,7 +95,7 @@
 promise_frame_test(function(t, frame, worker) {
     return promise_rejects(
         t,
-        null,
+        new Error(),
         frame.contentWindow.xhr(OTHER_BASE_URL),
         'SW fallbacked CORS-unsupported other origin XHR should fail.')
       .then(function() {
@@ -130,7 +130,7 @@
 promise_frame_test(function(t, frame, worker) {
     return promise_rejects(
         t,
-        null,
+        new Error(),
         frame.contentWindow.xhr(
           REDIRECT_URL + encodeURIComponent(OTHER_BASE_URL)),
         'SW fallbacked XHR which is redirected to CORS-unsupported ' +
@@ -183,7 +183,7 @@
 promise_frame_test(function(t, frame, worker) {
   return promise_rejects(
         t,
-        null,
+        new Error(),
         frame.contentWindow.load_image(OTHER_BASE_PNG_URL, 'anonymous'),
         'SW fallbacked CORS-unsupported other origin image request ' +
           'should fail.')
@@ -246,7 +246,7 @@
 promise_frame_test(function(t, frame, worker) {
   return promise_rejects(
         t,
-        null,
+        new Error(),
         frame.contentWindow.load_image(
             REDIRECT_URL + encodeURIComponent(OTHER_BASE_PNG_URL),
             'anonymous'),
diff --git a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-request-redirect.https.html b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-request-redirect.https.html
index eb148550..18bfa61e 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-request-redirect.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-request-redirect.https.html
@@ -182,7 +182,7 @@
         });
   }, 'Verify redirect mode of Fetch API and ServiceWorker FetchEvent.');
 
-// test for response.redirected
+// test for reponse.redirected
 promise_test(function(t) {
     var SCOPE = 'resources/fetch-request-redirect-iframe.html';
     var SCRIPT = 'resources/fetch-rewrite-worker.js';
@@ -276,7 +276,7 @@
         });
   }, 'Verify redirected of Response(Fetch API) and ServiceWorker FetchEvent.');
 
-// test for response.redirected after cached
+// test for reponse.redirected after cached
 promise_test(function(t) {
     var SCOPE = 'resources/fetch-request-redirect-iframe.html';
     var SCRIPT = 'resources/fetch-rewrite-worker.js';
diff --git a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-request-xhr-sync.https.html b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-request-xhr-sync.https.html
index d483d3e..f1e4fec 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-request-xhr-sync.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/fetch-request-xhr-sync.https.html
@@ -26,7 +26,7 @@
             });
 
           return new Promise(function(resolve, reject) {
-              setTimeout(function() {
+              t.step_timeout(function() {
                   var xhr;
                   try {
                     xhr = frame.contentWindow.performSyncXHR(non_existent_file);
diff --git a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/fetch-request-fallback-iframe.html b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/fetch-request-fallback-iframe.html
index 37c7c62f..d117d0f5 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/fetch-request-fallback-iframe.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/fetch-request-fallback-iframe.html
@@ -4,7 +4,7 @@
       var request = new XMLHttpRequest();
       request.addEventListener(
         'error',
-        function(event) { reject(event); });
+        function() { reject(new Error()); });
       request.addEventListener(
         'load',
         function(event) { resolve(request.response); });
@@ -21,7 +21,7 @@
         resolve();
       };
       img.onerror = function() {
-        reject();
+        reject(new Error());
       };
       if (cross_origin != '') {
         img.crossOrigin = cross_origin;
diff --git a/third_party/WebKit/LayoutTests/external/wpt/shadow-dom/untriaged/events/retargeting-focus-events/test-001.html b/third_party/WebKit/LayoutTests/external/wpt/shadow-dom/untriaged/events/retargeting-focus-events/test-001.html
index 66e8d9c0..296346b 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/shadow-dom/untriaged/events/retargeting-focus-events/test-001.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/shadow-dom/untriaged/events/retargeting-focus-events/test-001.html
@@ -106,7 +106,7 @@
 }));
 
 
-//gaining and losing focus elements are in the same tree.
+//gaining and loosing focus elements are in the same tree.
 //DOMFocusIn event should be stopped at shadow boundary
 var A_05_03_01_T03 = async_test('A_05_03_01_T03');
 
@@ -147,7 +147,7 @@
 
 
 
-//gaining and losing focus elements are in the same tree.
+//gaining and loosing focus elements are in the same tree.
 //DOMFocusOut event should be stopped at shadow boundary
 var A_05_03_01_T04 = async_test('A_05_03_01_T04');
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/shadow-dom/untriaged/styles/test-008.html b/third_party/WebKit/LayoutTests/external/wpt/shadow-dom/untriaged/styles/test-008.html
index af76fa9c..b84ca4d 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/shadow-dom/untriaged/styles/test-008.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/shadow-dom/untriaged/styles/test-008.html
@@ -48,7 +48,7 @@
     host.setAttribute('style', 'font-size:20px');
 
     assert_true(s.querySelector('#spn2').offsetHeight > oldHeight,
-        'Shadow host style must be applied to the shadow root children');
+        'Shadow host style must be aplied to the shadow root children');
 
 }), 'A_06_00_09_T01');
 </script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/uievents/resources/eventrecorder.js b/third_party/WebKit/LayoutTests/external/wpt/uievents/resources/eventrecorder.js
index 5442381a..649930b 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/uievents/resources/eventrecorder.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/uievents/resources/eventrecorder.js
@@ -70,7 +70,7 @@
 //    //<recorded property names with their values for all enumerable properties of the event object instance>
 // };
 // * EventRecordDetails
-//   * For records with 'sequentialOccurrences' > 1, only the first occurrence is recorded (subsequent event details are dropped).
+//   * For records with 'sequentialOccurrences' > 1, only the first occurence is recorded (subsequent event details are dropped).
 //   * Object reference values (e.g., event.target, event.currentTarget, etc.) are replaced with their mapped 'targetTestID' string.
 //     If no 'targetTestID' string mapping is available for a particular object, the value 'UNKNOWN_OBJECT' is returned.
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/update-built-tests.sh b/third_party/WebKit/LayoutTests/external/wpt/update-built-tests.sh
index 970a930..90da2624 100755
--- a/third_party/WebKit/LayoutTests/external/wpt/update-built-tests.sh
+++ b/third_party/WebKit/LayoutTests/external/wpt/update-built-tests.sh
@@ -2,5 +2,6 @@
 set -ex
 
 2dcontext/tools/build.sh
+assumptions/tools/build.sh
 html/tools/build.sh
 offscreen-canvas/tools/build.sh
diff --git a/third_party/WebKit/LayoutTests/external/wpt/url/toascii.json b/third_party/WebKit/LayoutTests/external/wpt/url/toascii.json
new file mode 100644
index 0000000..814f06e7
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/url/toascii.json
@@ -0,0 +1,149 @@
+[
+  "This resource is focused on highlighting issues with UTS #46 ToASCII",
+  {
+    "comment": "Label with hyphens in 3rd and 4th position",
+    "input": "aa--",
+    "output": "aa--"
+  },
+  {
+    "input": "a†--",
+    "output": "xn--a---kp0a"
+  },
+  {
+    "input": "ab--c",
+    "output": "ab--c"
+  },
+  {
+    "comment": "Label with leading hyphen",
+    "input": "-x",
+    "output": "-x"
+  },
+  {
+    "input": "-†",
+    "output": "xn----xhn"
+  },
+  {
+    "input": "-x.xn--nxa",
+    "output": "-x.xn--nxa"
+  },
+  {
+    "input": "-x.β",
+    "output": "-x.xn--nxa"
+  },
+  {
+    "comment": "Label with trailing hyphen",
+    "input": "x-.xn--nxa",
+    "output": "x-.xn--nxa"
+  },
+  {
+    "input": "x-.β",
+    "output": "x-.xn--nxa"
+  },
+  {
+    "comment": "Empty labels",
+    "input": "x..xn--nxa",
+    "output": "x..xn--nxa"
+  },
+  {
+    "input": "x..β",
+    "output": "x..xn--nxa"
+  },
+  {
+    "comment": "Invalid Punycode",
+    "input": "xn--a",
+    "output": null
+  },
+  {
+    "input": "xn--a.xn--nxa",
+    "output": null
+  },
+  {
+    "input": "xn--a.β",
+    "output": null
+  },
+  {
+    "comment": "Valid Punycode",
+    "input": "xn--nxa.xn--nxa",
+    "output": "xn--nxa.xn--nxa"
+  },
+  {
+    "comment": "Mixed",
+    "input": "xn--nxa.β",
+    "output": "xn--nxa.xn--nxa"
+  },
+  {
+    "input": "ab--c.xn--nxa",
+    "output": "ab--c.xn--nxa"
+  },
+  {
+    "input": "ab--c.β",
+    "output": "ab--c.xn--nxa"
+  },
+  {
+    "comment": "CheckJoiners is true",
+    "input": "\u200D.example",
+    "output": null
+  },
+  {
+    "input": "xn--1ug.example",
+    "output": null
+  },
+  {
+    "comment": "CheckBidi is true",
+    "input": "يa",
+    "output": null
+  },
+  {
+    "input": "xn--a-yoc",
+    "output": null
+  },
+  {
+    "comment": "processing_option is Nontransitional_Processing",
+    "input": "ශ්‍රී",
+    "output": "xn--10cl1a0b660p"
+  },
+  {
+    "input": "نامه‌ای",
+    "output": "xn--mgba3gch31f060k"
+  },
+  {
+    "comment": "U+FFFD",
+    "input": "\uFFFD.com",
+    "output": null
+  },
+  {
+    "comment": "U+FFFD character encoded in Punycode",
+    "input": "xn--zn7c.com",
+    "output": null
+  },
+  {
+    "comment": "Label longer than 63 code points",
+    "input": "x01234567890123456789012345678901234567890123456789012345678901x",
+    "output": "x01234567890123456789012345678901234567890123456789012345678901x"
+  },
+  {
+    "input": "x01234567890123456789012345678901234567890123456789012345678901†",
+    "output": "xn--x01234567890123456789012345678901234567890123456789012345678901-6963b"
+  },
+  {
+    "input": "x01234567890123456789012345678901234567890123456789012345678901x.xn--nxa",
+    "output": "x01234567890123456789012345678901234567890123456789012345678901x.xn--nxa"
+  },
+  {
+    "input": "x01234567890123456789012345678901234567890123456789012345678901x.β",
+    "output": "x01234567890123456789012345678901234567890123456789012345678901x.xn--nxa"
+  },
+  {
+    "comment": "Domain excluding TLD longer than 253 code points",
+    "input": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.x",
+    "output": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.x"
+  },
+  {
+    "input": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--nxa",
+    "output": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--nxa"
+  },
+  {
+    "input": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.β",
+    "output": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--nxa"
+  }
+]
diff --git a/third_party/WebKit/LayoutTests/external/wpt/url/toascii.window.js b/third_party/WebKit/LayoutTests/external/wpt/url/toascii.window.js
new file mode 100644
index 0000000..b49ef20
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/url/toascii.window.js
@@ -0,0 +1,62 @@
+async_test(t => {
+  const request = new XMLHttpRequest()
+  request.open("GET", "toascii.json")
+  request.send()
+  request.responseType = "json"
+  request.onload = t.step_func_done(() => {
+    runTests(request.response)
+  })
+}, "Loading data…")
+
+function makeURL(type, input) {
+  input = "https://" + input + "/x"
+  if(type === "url") {
+    return new URL(input)
+  } else {
+    const url = document.createElement(type)
+    url.href = input
+    return url
+  }
+}
+
+function runTests(tests) {
+  for(var i = 0, l = tests.length; i < l; i++) {
+    let hostTest = tests[i]
+    if (typeof hostTest === "string") {
+      continue // skip comments
+    }
+    const typeName = { "url": "URL", "a": "<a>", "area": "<area>" }
+    ;["url", "a", "area"].forEach((type) => {
+      test(() => {
+        if(hostTest.output !== null) {
+          const url = makeURL("url", hostTest.input)
+          assert_equals(url.host, hostTest.output)
+          assert_equals(url.hostname, hostTest.output)
+          assert_equals(url.pathname, "/x")
+          assert_equals(url.href, "https://" + hostTest.output + "/x")
+        } else {
+          if(type === "url") {
+            assert_throws(new TypeError, () => makeURL("url", hostTest.input))
+          } else {
+            const url = makeURL(type, hostTest.input)
+            assert_equals(url.host, "")
+            assert_equals(url.hostname, "")
+            assert_equals(url.pathname, "")
+            assert_equals(url.href, "https://" + hostTest.input + "/x")
+          }
+        }
+      }, hostTest.input + " (using " + typeName[type] + ")")
+      ;["host", "hostname"].forEach((val) => {
+        test(() => {
+          const url = makeURL(type, "x")
+          url[val] = hostTest.input
+          if(hostTest.output !== null) {
+            assert_equals(url[val], hostTest.output)
+          } else {
+            assert_equals(url[val], "x")
+          }
+        }, hostTest.input + " (using " + typeName[type] + "." + val + ")")
+      })
+    })
+  }
+}
diff --git a/third_party/WebKit/LayoutTests/external/wpt/user-timing/measure.html b/third_party/WebKit/LayoutTests/external/wpt/user-timing/measure.html
index 1916813..4175dbb4 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/user-timing/measure.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/user-timing/measure.html
@@ -76,7 +76,7 @@
 
         function onload_test()
         {
-            // test for existence of User Timing and Performance Timeline interface
+            // test for existance of User Timing and Performance Timeline interface
             if (!has_required_interfaces())
             {
                 test_true(false,
diff --git a/third_party/WebKit/LayoutTests/external/wpt/user-timing/measure_navigation_timing.html b/third_party/WebKit/LayoutTests/external/wpt/user-timing/measure_navigation_timing.html
index abd3f629..93b6dc2 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/user-timing/measure_navigation_timing.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/user-timing/measure_navigation_timing.html
@@ -73,7 +73,7 @@
 
         function onload_test()
         {
-            // test for existence of User Timing and Performance Timeline interface
+            // test for existance of User Timing and Performance Timeline interface
             if (!has_required_interfaces())
             {
                 test_true(false,
diff --git a/third_party/WebKit/LayoutTests/external/wpt/wasm/compile_worker.js b/third_party/WebKit/LayoutTests/external/wpt/wasm/compile_worker.js
deleted file mode 100644
index e89065c3..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/wasm/compile_worker.js
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-this.importScripts("resources/load_wasm.js");
-
-onmessage = function(msg) {
-    createWasmModule()
-        .then(postMessage)
-
-}
diff --git a/third_party/WebKit/LayoutTests/external/wpt/wasm/incrementer.wasm b/third_party/WebKit/LayoutTests/external/wpt/wasm/incrementer.wasm
deleted file mode 100644
index 47afcde..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/wasm/incrementer.wasm
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/external/wpt/wasm/resources/blank.html b/third_party/WebKit/LayoutTests/external/wpt/wasm/resources/blank.html
deleted file mode 100644
index a3c3a46..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/wasm/resources/blank.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<!DOCTYPE html>
-<title>Empty doc</title>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/wasm/resources/frame.html b/third_party/WebKit/LayoutTests/external/wpt/wasm/resources/frame.html
deleted file mode 100644
index 2f2b3ac9..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/wasm/resources/frame.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<script>
-function listener(event) {
-  var mod = event.data;
-  try {
-    var i = new WebAssembly.Instance(mod);    
-    var ans = i.exports.increment(42);
-    event.source.postMessage(ans, event.origin);
-  } catch (e) {
-    event.source.postMessage(e, event.origin);
-  }
-}
-
-if (window.addEventListener){
-  addEventListener("message", listener, false)
-} else {
-  attachEvent("onmessage", listener)
-}
-</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/wasm/resources/incrementer.wasm b/third_party/WebKit/LayoutTests/external/wpt/wasm/resources/incrementer.wasm
deleted file mode 100644
index 47afcde..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/wasm/resources/incrementer.wasm
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/external/wpt/wasm/resources/load_wasm.js b/third_party/WebKit/LayoutTests/external/wpt/wasm/resources/load_wasm.js
deleted file mode 100644
index 5123246..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/wasm/resources/load_wasm.js
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-function createWasmModule() {
-    return fetch('incrementer.wasm')
-        .then(response => {
-            if (!response.ok) throw new Error(response.statusText);
-            return response.arrayBuffer();
-        })
-        .then(WebAssembly.compile);
-}
diff --git a/third_party/WebKit/LayoutTests/external/wpt/wasm/resources/service-worker.js b/third_party/WebKit/LayoutTests/external/wpt/wasm/resources/service-worker.js
deleted file mode 100644
index bb0ec21..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/wasm/resources/service-worker.js
+++ /dev/null
@@ -1,26 +0,0 @@
-var port;
-
-importScripts('load_wasm.js');
-
-self.onmessage = function(e) {
-    var message = e.data;
-    if ('port' in message) {
-        port = message.port;
-    }
-};
-
-// And an event listener:
-self.addEventListener('message', function(e) {
-    var message = e.data;
-    if ("compile" in message) {
-        createWasmModule()
-            .then(m => {
-                try {
-                    port.postMessage({type:"OK", module:m});
-                } catch (e) {
-                    port.postMessage({type:"SEND ERROR"});
-                }
-            })
-            .catch(e => port.postMessage({type:"OTHER ERROR"}));
-    }
-});
diff --git a/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_indexeddb_test.html b/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_indexeddb_test.html
deleted file mode 100644
index ec91708..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_indexeddb_test.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/common/get-host-info.sub.js"></script>
-<script src="resources/load_wasm.js"></script>
-<script src="wasm_indexeddb_test.js"></script>
-</head>
-<body>
-<script>
-  if (window.location.origin != get_host_info().AUTHENTICATED_ORIGIN) {
-    window.location = get_host_info().AUTHENTICATED_ORIGIN + window.location.pathname;
-  } else {
-    promise_test(TestIndexedDBLoadStoreSecure, "serialize/deserialize to IndexedDB in secure context");
-  }
-</script>
-</body>
-</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_indexeddb_test.js b/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_indexeddb_test.js
deleted file mode 100644
index 301cbd2f..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_indexeddb_test.js
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-var db_name = 'db';
-var obj_store = 'store';
-var module_key = 'my_module';
-
-function createAndSaveToIndexedDB() {
-  return new Promise((resolve, reject) => {
-    createWasmModule()
-      .then(mod => {
-        var delete_request = indexedDB.deleteDatabase(db_name);
-        delete_request.onsuccess = function() {
-          var open_request = indexedDB.open(db_name);
-          open_request.onupgradeneeded = function() {
-            var db = open_request.result;
-            db.createObjectStore(obj_store);
-          };
-          open_request.onsuccess = function() {
-            var db = open_request.result;
-            var tx = db.transaction(obj_store, 'readwrite');
-            var store = tx.objectStore(obj_store);
-            try {
-              store.put(mod, module_key);
-            } catch(e) {
-              reject(e);
-              return;
-            }
-            tx.oncomplete = function() {
-              resolve();
-            };
-            tx.onabort = function() {
-              reject(transaction.error);
-            };
-          };
-        };
-      })
-      .catch(error => reject(error));
-  });
-}
-
-function loadFromIndexedDB(prev) {
-  return new Promise((resolve, reject) => {
-    prev.then(() => {
-      var open_request = indexedDB.open(db_name);
-      open_request.onsuccess = function() {
-        var db = open_request.result;
-        var tx = db.transaction(obj_store);
-        var store = tx.objectStore(obj_store);
-        var get_request = store.get(module_key);
-        get_request.onsuccess = function() {
-          var mod = get_request.result;
-          assert_true(mod instanceof WebAssembly.Module);
-          try {
-            var instance = new WebAssembly.Instance(mod);
-          } catch(e) {
-            reject(e);
-            return;
-          }
-          resolve(instance.exports.increment(1));
-        };
-      };
-    });
-  });
-}
-
-function TestIndexedDBLoadStoreSecure() {
-  return loadFromIndexedDB(createAndSaveToIndexedDB())
-    .then(res => assert_equals(res, 2),
-          error => assert_unreached(error));
-}
-
-function TestIndexedDBLoadStoreInsecure() {
-  return createAndSaveToIndexedDB()
-    .then(assert_unreached,
-          error => {
-            assert_true(error instanceof DOMException);
-            assert_equals(error.name, 'DataCloneError');
-          });
-}
diff --git a/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_local_iframe_test.html b/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_local_iframe_test.html
deleted file mode 100644
index fa1ab0c..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_local_iframe_test.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!DOCTYPE html>
-<script src="../../../resources/testharness.js"></script>
-<script src="../../../resources/testharnessreport.js"></script>
-<script src="resources/load_wasm.js"></script>
-<iframe src="resources/frame.html" id="iframe"></iframe>
-<script>
-  promise_test(async function() {
-    var mod = await createWasmModule();
-    assert_true(mod instanceof WebAssembly.Module);
-    var ans = await new Promise((resolve, reject) => {
-      var iframe = document.getElementById("iframe").contentWindow;
-      iframe.postMessage(mod, '*');
-      window.addEventListener("message", (reply) => resolve(reply.data), false);
-    });
-    assert_equals(ans, 43);
-  }, "send wasm module to iframe");
-</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_serialization_tests.html b/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_serialization_tests.html
deleted file mode 100644
index 49766c77..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_serialization_tests.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<!DOCTYPE html>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/load_wasm.js"></script>
-<script src="wasm_serialization_tests.js"></script>
-<script>
-  promise_test(TestInstantiateInWorker, "serialize wasm to worker");
-</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_serialization_tests.js b/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_serialization_tests.js
deleted file mode 100644
index 3cc41661..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_serialization_tests.js
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-function TestInstantiateInWorker() {
-  return createWasmModule()
-    .then((mod) => {
-      var worker = new Worker("wasm_serialization_worker.js");
-      return new Promise((resolve, reject) => {
-        worker.postMessage(mod);
-        worker.onmessage = function(event) {
-          resolve(event.data);
-        }
-      });
-    })
-    .then(data => assert_equals(data, 43))
-    .catch(error => assert_unreached(error));
-}
diff --git a/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_serialization_worker.js b/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_serialization_worker.js
deleted file mode 100644
index 3361ed7..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_serialization_worker.js
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-onmessage = function(e) {
-    var compiled_module = e.data;
-    var instance = new WebAssembly.Instance(compiled_module);
-    if (instance === undefined) {
-        postMessage("error!");
-        return;
-    }
-    var entrypoint = instance.exports["increment"];
-
-    if (typeof entrypoint !== "function") {
-        postMessage("error!");
-        return;
-    }
-
-    var ret = entrypoint(42);
-    postMessage(ret);
-}
diff --git a/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_service_worker_test.html b/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_service_worker_test.html
deleted file mode 100644
index 267d4370..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/wasm/wasm_service_worker_test.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<!DOCTYPE html>
-<title>Service Worker: postMessage with wasm</title>
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script src="../serviceworker/resources/test-helpers.js"></script>
-<script>
-  promise_test(async test => {
-    var registration = await service_worker_unregister_and_register(
-      test, 'resources/service-worker.js', 'resources/blank.html');
-      add_completion_callback(() => registration.unregister());
-      var worker = registration.installing;
-      var data = await new Promise((resolve, reject) => {
-        var messageChannel = new MessageChannel();
-        worker.postMessage({port: messageChannel.port2}, [messageChannel.port2]);
-        worker.postMessage({compile: true});
-        messageChannel.port1.onmessage = event => resolve(event.data);
-      });
-      assert_equals(data, null);
-  }, 'postMessaging wasm from a service worker should fail');
-</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webaudio/the-audio-api/the-mediaelementaudiosourcenode-interface/mediaElementAudioSourceToScriptProcessorTest.html b/third_party/WebKit/LayoutTests/external/wpt/webaudio/the-audio-api/the-mediaelementaudiosourcenode-interface/mediaElementAudioSourceToScriptProcessorTest.html
index 7e1514a..ba6eec6 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/webaudio/the-audio-api/the-mediaelementaudiosourcenode-interface/mediaElementAudioSourceToScriptProcessorTest.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/webaudio/the-audio-api/the-mediaelementaudiosourcenode-interface/mediaElementAudioSourceToScriptProcessorTest.html
@@ -7,7 +7,7 @@
 array, and after the playback has stopped, the contents are compared
 to those of a loaded AudioBuffer with the same source.
 
-Somewhat similar to a test from Mozilla:
+Somewhat similiar to a test from Mozilla:
 (http://dxr.mozilla.org/mozilla-central/source/content/media/webaudio/test/test_mediaElementAudioSourceNode.html?force=1)
 -->
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCCertificate-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCCertificate-expected.txt
new file mode 100644
index 0000000..7361ab9
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCCertificate-expected.txt
@@ -0,0 +1,11 @@
+This is a testharness.js-based test.
+PASS Constructing RTCPeerConnection with expired certificate should reject with InvalidAccessError 
+FAIL Calling setConfiguration with different set of certs should reject with InvalidModificationError assert_throws: function "() =>
+        pc.setConfiguration({
+          certificates: [cert2]
+        })" did not throw
+FAIL RTCCertificate should have at least one fingerprint assert_own_property: expected property "getFingerprints" missing
+FAIL RTCPeerConnection({ certificates }) should generate offer SDP with fingerprint of provided certificate promise_test: Unhandled rejection with value: object "OperationError: TEST_ERROR"
+FAIL RTCPeerConnection({ certificates }) should generate offer SDP with fingerprint of all provided certificates promise_test: Unhandled rejection with value: object "OperationError: TEST_ERROR"
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCCertificate.html b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCCertificate.html
new file mode 100644
index 0000000..65f3f1e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCCertificate.html
@@ -0,0 +1,283 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>RTCCertificate Tests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+  'use strict';
+
+  // Test is based on the following editor draft:
+  // https://w3c.github.io/webrtc-pc/archives/20170515/webrtc.html
+
+  /*
+    4.2.1. RTCConfiguration Dictionary
+      dictionary RTCConfiguration {
+        sequence<RTCCertificate> certificates;
+        ...
+      };
+
+      certificates of type sequence<RTCCertificate>
+        If this value is absent, then a default set of certificates is
+        generated for each RTCPeerConnection instance.
+
+        The value for this configuration option cannot change after its
+        value is initially selected.
+
+    4.10.2. RTCCertificate Interface
+      interface RTCCertificate {
+        readonly attribute DOMTimeStamp expires;
+        sequence<RTCDtlsFingerprint>    getFingerprints();
+        AlgorithmIdentifier             getAlgorithm();
+      };
+
+    5.5.1 The RTCDtlsFingerprint Dictionary
+      dictionary RTCDtlsFingerprint {
+        DOMString algorithm;
+        DOMString value;
+      };
+
+    [RFC4572] Comedia over TLS in SDP
+    5.  Fingerprint Attribute
+      Figure 2. Augmented Backus-Naur Syntax for the Fingerprint Attribute
+
+        attribute              =/ fingerprint-attribute
+
+        fingerprint-attribute  =  "fingerprint" ":" hash-func SP fingerprint
+
+        hash-func              =  "sha-1" / "sha-224" / "sha-256" /
+                                  "sha-384" / "sha-512" /
+                                  "md5" / "md2" / token
+                                  ; Additional hash functions can only come
+                                  ; from updates to RFC 3279
+
+        fingerprint            =  2UHEX *(":" 2UHEX)
+                                  ; Each byte in upper-case hex, separated
+                                  ; by colons.
+
+        UHEX                   =  DIGIT / %x41-46 ; A-F uppercase
+   */
+
+  // Helper function to generate certificate with a set of
+  // default parameters
+  function generateCertificate() {
+    return RTCPeerConnection.generateCertificate({
+      name: 'ECDSA',
+      namedCurve: 'P-256'
+    });
+  }
+
+  // Helper function that takes in an RTCDtlsFingerprint
+  // and return an a=fingerprint SDP line
+  function fingerprintToSdpLine(fingerprint) {
+    return `\r\na=fingerprint ${fingerprint.algorithm} ${fingerprint.value.toUpperCase()}\r\n`;
+  }
+
+  // Assert that an SDP string has fingerprint line for all the cert's fingerprints
+  function assert_sdp_has_cert_fingerprints(sdp, cert) {
+    for(const fingerprint of cert.getFingerprints()) {
+      const fingerprintLine = fingerprintToSdpLine(fingerprint);
+      assert_true(sdp.includes(fingerprintLine),
+        'Expect fingerprint line to be found in SDP');
+    }
+  }
+
+  /*
+    4.3.1. Operation
+      When the RTCPeerConnection() constructor is invoked
+        2.  If the certificates value in configuration is non-empty,
+            check that the expires on each value is in the future.
+            If a certificate has expired, throw an InvalidAccessError;
+            otherwise, store the certificates. If no certificates value
+            was specified, one or more new RTCCertificate instances are
+            generated for use with this RTCPeerConnection instance.
+            This may happen asynchronously and the value of certificates
+            remains undefined for the subsequent steps.
+   */
+  promise_test(t => {
+    return RTCPeerConnection.generateCertificate({
+      name: 'ECDSA',
+      namedCurve: 'P-256',
+      expires: 0
+    }).then(cert => {
+      assert_less_than_equal(cert.expires, Date.now());
+      assert_throws('InvalidAccessError', () =>
+        new RTCPeerConnection({ certificates: [cert] }));
+    });
+  }, 'Constructing RTCPeerConnection with expired certificate should reject with InvalidAccessError');
+
+  /*
+    4.3.2 Interface Definition
+      setConfiguration
+        4.  If configuration.certificates is set and the set of
+            certificates differs from the ones used when connection
+            was constructed, throw an InvalidModificationError.
+   */
+  promise_test(t => {
+    return Promise.all([
+      generateCertificate(),
+      generateCertificate()
+    ]).then(([cert1, cert2]) => {
+      const pc = new RTCPeerConnection({
+        certificates: [cert1]
+      });
+
+      // should not throw
+      pc.setConfiguration({
+        certificates: [cert1]
+      });
+
+      assert_throws('InvalidModificationError', () =>
+        pc.setConfiguration({
+          certificates: [cert2]
+        }));
+
+      assert_throws('InvalidModificationError', () =>
+        pc.setConfiguration({
+          certificates: [cert1, cert2]
+        }));
+    });
+  }, 'Calling setConfiguration with different set of certs should reject with InvalidModificationError');
+
+  /*
+    4.10.2. RTCCertificate Interface
+      getFingerprints
+        Returns the list of certificate fingerprints, one of which is
+        computed with the digest algorithm used in the certificate signature.
+
+    5.5.1 The RTCDtlsFingerprint Dictionary
+      algorithm of type DOMString
+        One of the the hash function algorithms defined in the 'Hash function
+        Textual Names' registry, initially specified in [RFC4572] Section 8.
+        As noted in [JSEP] Section 5.2.1, the digest algorithm used for the
+        fingerprint matches that used in the certificate signature.
+
+      value of type DOMString
+        The value of the certificate fingerprint in lowercase hex string as
+        expressed utilizing the syntax of 'fingerprint' in [ RFC4572] Section 5.
+
+   */
+  promise_test(t => {
+    return generateCertificate()
+    .then(cert => {
+      assert_own_property(cert, 'getFingerprints');
+
+      const fingerprints = cert.getFingerprints();
+      assert_true(Array.isArray(fingerprints),
+        'Expect fingerprints to return an array');
+
+      assert_greater_than_equal(fingerprints.length, 1,
+        'Expect at last one fingerprint in array');
+
+      for(const fingerprint of fingerprints[0]) {
+        assert_true(fingerprint instanceof RTCDtlsFingerprint,
+          'Expect fingerprint to be instance of RTCDtlsFingerprint');
+
+        // Can only do simple test as the allowed values may be extended
+        assert_true(/^[a-zA-Z\-]+$/.test(fingerprint.algorithm),
+          'Expect fingerprint.algorithm to be string of algorithm identifier');
+
+        assert_true(/^([0-9a-f]{2}\:)+[0-9a-f]{2}$/.test(fingerprint.value),
+          'Expect fingerprint.value to be lowercase hexadecimal separated by colon');
+      }
+    });
+  }, 'RTCCertificate should have at least one fingerprint');
+
+  /*
+    4.3.2 Interface Definition
+      createOffer
+        The value for certificates in the RTCConfiguration for the
+        RTCPeerConnection is used to produce a set of certificate
+        fingerprints. These certificate fingerprints are used in the
+        construction of SDP and as input to requests for identity
+        assertions.
+
+    [JSEP]
+    5.2.1.  Initial Offers
+      For DTLS, all m= sections MUST use all the certificate(s) that have
+      been specified for the PeerConnection; as a result, they MUST all
+      have the same [I-D.ietf-mmusic-4572-update] fingerprint value(s), or
+      these value(s) MUST be session-level attributes.
+
+      The following attributes, which are of category IDENTICAL or
+      TRANSPORT, MUST appear only in "m=" sections which either have a
+      unique address or which are associated with the bundle-tag.  (In
+      initial offers, this means those "m=" sections which do not contain
+      an "a=bundle-only" attribute.)
+
+        - An "a=fingerprint" line for each of the endpoint's certificates,
+          as specified in [RFC4572], Section 5; the digest algorithm used
+          for the fingerprint MUST match that used in the certificate
+          signature.
+
+      Each m= section which is not bundled into another m= section, MUST
+      contain the following attributes (which are of category IDENTICAL or
+      TRANSPORT):
+
+        - An "a=fingerprint" line for each of the endpoint's certificates,
+          as specified in [RFC4572], Section 5; the digest algorithm used
+          for the fingerprint MUST match that used in the certificate
+          signature.
+   */
+  promise_test(t => {
+    return generateCertificate()
+    .then(cert => {
+      const pc = new RTCPeerConnection({
+        certificates: [cert]
+      });
+      pc.createDataChannel('test');
+
+      return pc.createOffer()
+      .then(offer => {
+        assert_sdp_has_cert_fingerprints(offer.sdp, cert);
+      });
+    });
+  }, 'RTCPeerConnection({ certificates }) should generate offer SDP with fingerprint of provided certificate');
+
+  promise_test(t => {
+    return Promise.all([
+      generateCertificate(),
+      generateCertificate()
+    ]).then(certs => {
+      const pc = new RTCPeerConnection({
+        certificates: certs
+      });
+      pc.createDataChannel('test');
+
+      return pc.createOffer()
+      .then(offer => {
+        for(const cert of certs) {
+          assert_sdp_has_cert_fingerprints(offer.sdp, cert);
+        }
+      });
+    });
+  }, 'RTCPeerConnection({ certificates }) should generate offer SDP with fingerprint of all provided certificates');
+
+  /*
+    TODO
+
+    4.10.2. RTCCertificate Interface
+      getAlgorithm
+        Returns the result of the WebCrypto algorithm normalization process
+        [WebCryptoAPI] that occurred when this certificate was generated
+        with generateCertificate().
+
+      The RTCCertificate object can be stored and retrieved from persistent
+      storage by an application. When a user agent is required to obtain a
+      structured clone [HTML5] of a RTCCertificate object, it performs the
+      following steps:
+        1.  Let input and memory be the corresponding inputs defined by the
+            internal structured cloning algorithm, where input represents a
+            RTCCertificate object to be cloned.
+        2.  Let output be a newly constructed RTCCertificate object.
+        3.  Copy the value of the expires attribute from input to output.
+        4.  Let the [[certificate]] internal slot of output be set to the
+            result of invoking the internal structured clone algorithm
+            recursively on the corresponding internal slots of input, with
+            the slot contents as the new " input" argument and memory as
+            the new " memory" argument.
+        5.  Let the [[handle]] internal slot of output refer to the same
+            private keying material represented by the [[handle]] internal
+            slot of input.
+   */
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-addTransceiver-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-addTransceiver-expected.txt
new file mode 100644
index 0000000..c6bfee4
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-addTransceiver-expected.txt
@@ -0,0 +1,6 @@
+This is a testharness.js-based test.
+FAIL addTransceiver('audio') should return an audio transceiver assert_own_property: expected property "addTransceiver" missing
+FAIL addTransceiver('video') should return a video transceiver assert_own_property: expected property "addTransceiver" missing
+FAIL addTransceiver() with string argument as invalid kind should throw TypeError assert_own_property: expected property "addTransceiver" missing
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-addTransceiver.html b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-addTransceiver.html
new file mode 100644
index 0000000..0c69652
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-addTransceiver.html
@@ -0,0 +1,157 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCPeerConnection.prototype.addTransceiver</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+  'use strict';
+
+  // Test is based on the following editor draft:
+  // https://w3c.github.io/webrtc-pc/archives/20170515/webrtc.html
+
+  /*
+   *  5.1. RTCPeerConnection Interface Extensions
+   *  partial interface RTCPeerConnection {
+   *      sequence<RTCRtpSender>      getSenders();
+   *      sequence<RTCRtpReceiver>    getReceivers();
+   *      sequence<RTCRtpTransceiver> getTransceivers();
+   *      RTCRtpTransceiver           addTransceiver((MediaStreamTrack or DOMString) trackOrKind,
+   *                                                 optional RTCRtpTransceiverInit init);
+   *      ...
+   *  };
+   *
+   *  dictionary RTCRtpTransceiverInit {
+   *      RTCRtpTransceiverDirection         direction = "sendrecv";
+   *      sequence<MediaStream>              streams;
+   *      sequence<RTCRtpEncodingParameters> sendEncodings;
+   *  };
+   */
+
+  /*
+   *  5.1.  addTransceiver
+   *        The initial value of mid is null.
+   *
+   *        3.  If the first argument is a string, let it be kind and run the following steps:
+   *            2.  Let track be null.
+   *        5.  Create an RTCRtpSender with track, streams and sendEncodings and let sender
+   *            be the result.
+   *        6.  Create an RTCRtpReceiver with kind and let receiver be the result.
+   *        7.  Create an RTCRtpTransceiver with sender and receiver and let transceiver
+   *            be the result.
+   *        8.  Add transceiver to connection's set of transceivers.
+   *
+   *  5.3.  RTCRtpReceiver Interface
+   *        Create an RTCRtpReceiver
+   *        2.  Let track be a new MediaStreamTrack object [GETUSERMEDIA]. The source of
+   *            track is a remote source provided by receiver.
+   *        3.  Initialize track.kind to kind.
+   *        5.  Initialize track.label to the result of concatenating the string "remote "
+   *            with kind.
+   *        6.  Initialize track.readyState to live.
+   *        7.  Initialize track.muted to true.
+   *
+   *  5.4.  RTCRtpTransceiver Interface
+   *        Create an RTCRtpTransceiver
+   *        2.  Set transceiver.sender to sender.
+   *        3.  Set transceiver.receiver to receiver.
+   *        4.  Set transceiver.stopped to false.
+   */
+  test(t => {
+    const pc = new RTCPeerConnection();
+
+    assert_own_property(pc, 'addTransceiver');
+
+    const transceiver = pc.addTransceiver('audio');
+    assert_true(transceiver instanceof RTCRtpTransceiver,
+      'Expect transceiver to be instance of RTCRtpTransceiver');
+
+    assert_equals(transceiver.mid, null);
+    assert_equals(transceiver.stopped, false);
+    assert_equals(transceiver.direction, 'sendrecv');
+
+    assert_array_equals([transceiver], pc.getTransceivers(),
+      `Expect added transceiver to be the only element in connection's list of transceivers`);
+
+    const sender = transceiver.sender;
+
+    assert_true(sender instanceof RTCRtpSender,
+      'Expect sender to be instance of RTCRtpSender');
+
+    assert_equals(sender.track, null);
+
+    assert_array_equals([sender], pc.getSenders(),
+      `Expect added sender to be the only element in connection's list of senders`);
+
+    const receiver = transceiver.receiver;
+    assert_true(receiver instanceof RTCRtpReceiver,
+      'Expect receiver to be instance of RTCRtpReceiver');
+
+    const track = receiver.track
+    assert_true(track instanceof MediaStreamTrack,
+      'Expect receiver.track to be instance of MediaStreamTrack');
+
+    assert_equals(track.kind, 'audio');
+    assert_equals(track.label, 'remote audio');
+    assert_equals(track.readyState, 'live');
+    assert_equals(track.muted, true);
+
+    assert_array_equals([receiver], pc.getReceivers(),
+      `Expect added receiver to be the only element in connection's list of receivers`);
+
+  }, `addTransceiver('audio') should return an audio transceiver`);
+
+  test(t => {
+    const pc = new RTCPeerConnection();
+
+    assert_own_property(pc, 'addTransceiver');
+
+    const transceiver = pc.addTransceiver('video');
+    assert_true(transceiver instanceof RTCRtpTransceiver,
+      'Expect transceiver to be instance of RTCRtpTransceiver');
+
+    assert_equals(transceiver.mid, null);
+    assert_equals(transceiver.stopped, false);
+    assert_equals(transceiver.direction, 'sendrecv');
+
+    assert_array_equals([transceiver], pc.getTransceivers(),
+      `Expect added transceiver to be the only element in connection's list of transceivers`);
+
+    const sender = transceiver.sender;
+
+    assert_true(sender instanceof RTCRtpSender,
+      'Expect sender to be instance of RTCRtpSender');
+
+    assert_equals(sender.track, null);
+
+    assert_array_equals([sender], pc.getSenders(),
+      `Expect added sender to be the only element in connection's list of senders`);
+
+    const receiver = transceiver.receiver;
+    assert_true(receiver instanceof RTCRtpReceiver,
+      'Expect receiver to be instance of RTCRtpReceiver');
+
+    const track = receiver.track
+    assert_true(track instanceof MediaStreamTrack,
+      'Expect receiver.track to be instance of MediaStreamTrack');
+
+    assert_equals(track.kind, 'video');
+    assert_equals(track.label, 'remote video');
+    assert_equals(track.readyState, 'live');
+    assert_equals(track.muted, true);
+
+    assert_array_equals([receiver], pc.getReceivers(),
+      `Expect added receiver to be the only element in connection's list of receivers`);
+
+  }, `addTransceiver('video') should return a video transceiver`);
+
+  /*
+   *  5.1.  addTransceiver
+   *        3.1.  If kind is not a legal MediaStreamTrack kind, throw a TypeError.
+   */
+  test(t => {
+    const pc = new RTCPeerConnection();
+    assert_own_property(pc, 'addTransceiver');
+    assert_throws(new TypeError(), () => pc.addTransceiver('invalid'));
+  }, 'addTransceiver() with string argument as invalid kind should throw TypeError');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-constructor-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-constructor-expected.txt
index 3aa3377..e5236ea 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-constructor-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-constructor-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 73 tests; 62 PASS, 11 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 71 tests; 60 PASS, 11 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS RTCPeerConnection.length 
 PASS new RTCPeerConnection() 
 PASS new RTCPeerConnection(null) 
@@ -66,8 +66,6 @@
 PASS new RTCPeerConnection({ certificates: [null] }) 
 PASS new RTCPeerConnection({ certificates: [undefined] }) 
 PASS new RTCPeerConnection({ iceCandidatePoolSize: toNumberThrows }) 
-PASS new RTCPeerConnection({ certificates: [certificate] }) 
-PASS new RTCPeerConnection({ certificates: [expiredCertificate] }) 
 PASS localDescription initial value 
 FAIL currentLocalDescription initial value assert_equals: expected (object) null but got (undefined) undefined
 FAIL pendingLocalDescription initial value assert_equals: expected (object) null but got (undefined) undefined
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-constructor.html b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-constructor.html
index ee352ac..97cc200b 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-constructor.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-constructor.html
@@ -113,21 +113,6 @@
   }, expr);
 }
 
-promise_test(function() {
-  return RTCPeerConnection.generateCertificate({ name: "ECDSA", namedCurve: "P-256" })
-      .then(certificate => new RTCPeerConnection({ certificates: [certificate] }));
-}, 'new RTCPeerConnection({ certificates: [certificate] })');
-
-promise_test(function() {
-  return RTCPeerConnection.generateCertificate({ name: "ECDSA", namedCurve: "P-256", expires: 0 })
-      .then(certificate => {
-        assert_less_than_equal(certificate.expires, Date.now());
-        assert_throws('InvalidAccessError', function() {
-          new RTCPeerConnection({ certificates: [certificate] });
-        });
-      });
-}, 'new RTCPeerConnection({ certificates: [expiredCertificate] })');
-
 // The initial values of attributes of RTCPeerConnection.
 const initialState = {
   'localDescription': null,
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-createAnswer-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-createAnswer-expected.txt
new file mode 100644
index 0000000..d0e36e49
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-createAnswer-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+FAIL createAnswer() with null remoteDescription should reject with InvalidStateError assert_unreached: Should have rejected: undefined Reached unreachable code
+FAIL createAnswer() after setting remote description should succeed promise_test: Unhandled rejection with value: object "OperationError: TEST_ERROR"
+FAIL createAnswer() when connection is closed reject with InvalidStateError promise_test: Unhandled rejection with value: object "OperationError: TEST_ERROR"
+FAIL createAnswer() when connection is closed in parallel should never resolve assert_unreached: Pending promise should never be resolved. Instead it is rejected with: OperationError: TEST_ERROR Reached unreachable code
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-createAnswer.html b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-createAnswer.html
new file mode 100644
index 0000000..63860b2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-createAnswer.html
@@ -0,0 +1,110 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCPeerConnection.prototype.createAnswer</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script>
+  'use strict';
+
+  // Test is based on the following editor draft:
+  // https://w3c.github.io/webrtc-pc/archives/20170515/webrtc.html
+
+  /*
+   * 4.3.2. createAnswer()
+   */
+
+  /*
+   *  4.1.  If connection's remoteDescription is null return a promise rejected with a
+   *        newly created InvalidStateError.
+   */
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+    return promise_rejects(t, 'InvalidStateError',
+      pc.createAnswer());
+  }, 'createAnswer() with null remoteDescription should reject with InvalidStateError');
+
+  /*
+   *  Final steps to create an answer
+   *  4. Let answer be a newly created RTCSessionDescriptionInit dictionary with its
+   *     type member initialized to the string "answer" and its sdp member initialized to sdpString.
+   */
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+
+    return generateOffer({ video: true })
+    .then(offer => pc.setRemoteDescription(offer))
+    .then(() => pc.createAnswer())
+    .then(answer => {
+      assert_equals(typeof answer, 'object',
+        'Expect answer to be plain object dictionary RTCSessionDescriptionInit');
+
+      assert_false(answer instanceof RTCSessionDescription,
+        'Expect answer to not be instance of RTCSessionDescription')
+    });
+  }, 'createAnswer() after setting remote description should succeed');
+
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+
+    return generateOffer({ data: true })
+    .then(offer => pc.setRemoteDescription(offer))
+    .then(() => {
+      pc.close();
+      return promise_rejects(t, 'InvalidStateError',
+        pc.createAnswer());
+    });
+  }, 'createAnswer() when connection is closed reject with InvalidStateError');
+
+  test_never_resolve(t => {
+    const pc = new RTCPeerConnection();
+
+    return generateOffer({ data: true })
+    .then(offer => pc.setRemoteDescription(offer))
+    .then(() => {
+      const promise = pc.createAnswer();
+      pc.close();
+      return promise;
+    });
+  }, 'createAnswer() when connection is closed in parallel should never resolve');
+
+  /*
+   *  TODO
+   *  4.3.2 createAnswer
+   *    3.  If connection is configured with an identity provider, and an
+   *        identity assertion has not yet been generated using said
+   *        identity provider, then begin the identity assertion request
+   *        process if it has not already begun.
+   *
+   *    Steps to create an answer
+   *      1.  If the need for an identity assertion was identified when
+   *          createAnswer was invoked, wait for the identity assertion
+   *          request process to complete.
+   *      2.  If the identity provider was unable to produce an identity
+   *          assertion, reject p with a newly created NotReadableError
+   *          and abort these steps.
+   *      3.  If connection was not constructed with a set of certificates,
+   *          and one has not yet been generated, wait for it to be generated.
+   *
+   *    Final steps to create an answer
+   *      2.  If connection was modified in such a way that additional
+   *          inspection of the system state is necessary, then in
+   *          parallel begin the steps to create an answer again, given p,
+   *          and abort these steps.
+   *
+   *  Non-Testable
+   *  4.3.2 createAnswer
+   *      Steps to create an answer
+   *        4.  Inspect the system state to determine the currently
+   *            available resources as necessary for generating the answer,
+   *            as described in [JSEP] (section 4.1.7.).
+   *        5.  If this inspection failed for any reason, reject p with a
+   *            newly created OperationError and abort these steps.
+   *
+   *      Final steps to create an answer
+   *        3.  Given the information that was obtained from previous inspection
+   *            and the current state of connection and its RTCRtpTransceivers,
+   *            generate an SDP answer, sdpString, as described in [JSEP] (section 5.3.).
+   */
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-createOffer.html b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-createOffer.html
new file mode 100644
index 0000000..deab23ce
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-createOffer.html
@@ -0,0 +1,192 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCPeerConnection.prototype.createOffer</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script>
+  'use strict';
+
+  // Test is based on the following editor draft:
+  // https://w3c.github.io/webrtc-pc/archives/20170515/webrtc.html
+
+  /*
+   *  4.3.2.  createOffer()
+   */
+
+  /*
+   *  Final steps to create an offer
+   *    4.  Let offer be a newly created RTCSessionDescriptionInit dictionary
+   *        with its type member initialized to the string "offer" and its sdp member
+   *        initialized to sdpString.
+   */
+  promise_test(t => {
+    const pc = new RTCPeerConnection()
+
+    return pc.createOffer()
+    .then(offer => {
+      assert_equals(typeof offer, 'object',
+        'Expect offer to be plain object dictionary RTCSessionDescriptionInit');
+
+      assert_false(offer instanceof RTCSessionDescription,
+        'Expect offer to not be instance of RTCSessionDescription')
+    });
+  }, 'createOffer() with no argument from newly created RTCPeerConnection should succeed');
+
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+    test_state_change_event(t, pc, ['have-local-offer']);
+
+    return pc.createOffer({ offerToReceiveAudio: true })
+    .then(offer =>
+      pc.setLocalDescription(offer)
+      .then(offer => {
+        assert_equals(pc.signalingState, 'have-local-offer');
+        assert_session_desc_equals(pc.localDescription, offer);
+        assert_session_desc_equals(pc.pendingLocalDescription, offer);
+        assert_equals(pc.currentLocalDescription, null);
+      }));
+  }, 'createOffer() and then setLocalDescription() should succeed');
+
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+    pc.close();
+
+    return promise_rejects(t, 'InvalidStateError',
+      pc.createOffer());
+  }, 'createOffer() after connection is closed should reject with InvalidStateError');
+
+  test_never_resolve(t => {
+    const pc = new RTCPeerConnection();
+    const promise = pc.createOffer();
+
+    pc.close();
+    return promise;
+
+  }, 'createOffer() when connection is closed halfway should never resolve');
+
+  /*
+   *  Final steps to create an offer
+   *    2.  If connection was modified in such a way that additional inspection of the
+   *        system state is necessary, then in parallel begin the steps to create an
+   *        offer again, given p, and abort these steps.
+   *
+   *  This test might hit step 2 of final steps to create an offer. But the media stream
+   *  is likely added already by the time steps to create an offer is executed, because
+   *  that is enqueued as an operation.
+   *  Either way it verifies that the media stream is included in the offer even though
+   *  the stream is added after synchronous call to createOffer.
+   */
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+    const promise = pc.createOffer();
+
+    pc.addTransceiver('audio');
+    return promise.then(offer => {
+      assert_equals(countAudioLine(offer.sdp), 1,
+        'Expect m=audio line to be found in offer SDP')
+    });
+  }, 'When media stream is added when createOffer() is running in parallel, the result offer should contain the new media stream');
+
+  /*
+   *  TODO
+   *  4.3.2 createOffer
+   *    3.  If connection is configured with an identity provider, and an identity
+   *        assertion has not yet been generated using said identity provider, then
+   *        begin the identity assertion request process if it has not already begun.
+   *    Steps to create an offer
+   *    1.  If the need for an identity assertion was identified when createOffer was
+   *        invoked, wait for the identity assertion request process to complete.
+   *
+   *  Non-Testable
+   *  4.3.2 createOffer
+   *    Steps to create an offer
+   *    4.  Inspect the system state to determine the currently available resources as
+   *    necessary for generating the offer, as described in [JSEP] (section 4.1.6.).
+   *    5.  If this inspection failed for any reason, reject p with a newly created
+   *        OperationError and abort these steps.
+   */
+
+  /*
+   *  4.3.3.2 Configuration data extensions
+   *  partial dictionary RTCOfferOptions
+   */
+
+  /*
+   *  offerToReceiveAudio of type boolean
+   *    When this is given a non-false value, no outgoing track of type
+   *    "audio" is attached to the PeerConnection, and the existing
+   *    localDescription (if any) doesn't contain any sendrecv or recv
+   *    audio media sections, createOffer() will behave as if
+   *    addTransceiver("audio") had been called once prior to the createOffer() call.
+   */
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+
+    return pc.createOffer({ offerToReceiveAudio: true })
+    .then(offer1 => {
+      assert_equals(countAudioLine(offer1.sdp), 1,
+        'Expect created offer to have audio line');
+
+      // The first createOffer implicitly calls addTransceiver('audio'),
+      // so all following offers will also have audio media section
+      // in their SDP.
+      return pc.createOffer({ offerToReceiveAudio: false })
+      .then(offer2 => {
+        assert_equals(countAudioLine(offer2.sdp), 1,
+          'Expect audio line to remain in created offer');
+      })
+    });
+  }, 'createOffer() with offerToReceiveAudio should add audio line to all subsequent created offers');
+
+  /*
+   *  offerToReceiveVideo of type boolean
+   *    When this is given a non-false value, and no outgoing track
+   *    of type "video" is attached to the PeerConnection, and the
+   *    existing localDescription (if any) doesn't contain any sendecv
+   *    or recv video media sections, createOffer() will behave as if
+   *    addTransceiver("video") had been called prior to the createOffer() call.
+   */
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+
+    return pc.createOffer({ offerToReceiveVideo: true })
+    .then(offer1 => {
+      assert_equals(countVideoLine(offer1.sdp), 1,
+      'Expect created offer to have video line');
+
+      return pc.createOffer({ offerToReceiveVideo: false })
+      .then(offer2 => {
+        assert_equals(countVideoLine(offer2.sdp), 1,
+          'Expect video line to remain in created offer');
+      })
+    });
+  }, 'createOffer() with offerToReceiveVideo should add video line to all subsequent created offers');
+
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+
+    return pc.createOffer({
+      offerToReceiveAudio: true,
+      offerToReceiveVideo: false
+    }).then(offer1 => {
+      assert_equals(countAudioLine(offer1.sdp), 1,
+        'Expect audio line to be found in created offer');
+
+      assert_equals(countVideoLine(offer1.sdp), 0,
+        'Expect video line to not found in create offer');
+
+      return pc.createOffer({
+        offerToReceiveAudio: false,
+        offerToReceiveVideo: true
+      }).then(offer2 => {
+        assert_equals(countAudioLine(offer2.sdp), 1,
+          'Expect audio line to remain in created offer');
+
+        assert_equals(countVideoLine(offer2.sdp), 1,
+          'Expect video line to be found in create offer');
+      })
+    });
+  }, 'createOffer() with offerToReceiveAudio:true then offerToReceiveVideo:true should have result offer with both audio and video line');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-generateCertificate-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-generateCertificate-expected.txt
new file mode 100644
index 0000000..ab8a0e0
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-generateCertificate-expected.txt
@@ -0,0 +1,11 @@
+This is a testharness.js-based test.
+PASS generateCertificate() with compulsary RSASSA-PKCS1-v1_5 parameters should succeed 
+PASS generateCertificate() with compulsary ECDSA parameters should succeed 
+PASS generateCertificate() with invalid string algorithm should reject with NotSupportedError 
+PASS generateCertificate() with invalid algorithm dict should reject with NotSupportedError 
+PASS generateCertificate() with valid expires parameter should succeed 
+PASS generateCertificate() with 0 expires parameter should generate expired cert 
+FAIL generateCertificate() with invalid range for expires should reject with TypeError assert_unreached: Should have rejected: undefined Reached unreachable code
+FAIL generateCertificate() with invalid type for expires should reject with TypeError assert_unreached: Should have rejected: undefined Reached unreachable code
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-generateCertificate.html b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-generateCertificate.html
new file mode 100644
index 0000000..d1a6395f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-generateCertificate.html
@@ -0,0 +1,138 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test RTCPeerConnection.generateCertificate</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+  'use strict';
+
+  // Test is based on the following editor draft:
+  // https://w3c.github.io/webrtc-pc/archives/20170515/webrtc.html
+
+  /*
+   *  4.10. Certificate Management
+   *    partial interface RTCPeerConnection {
+   *      static Promise<RTCCertificate> generateCertificate(
+   *        AlgorithmIdentifier keygenAlgorithm);
+   *    };
+   *
+   *  4.10.2. RTCCertificate Interface
+   *    interface RTCCertificate {
+   *      readonly attribute DOMTimeStamp expires;
+   *      ...
+   *    };
+   *
+   *  [WebCrypto]
+   *  11. Algorithm Dictionary
+   *    typedef (object or DOMString) AlgorithmIdentifier;
+   */
+
+  /*
+   *  4.10. The following values must be supported by a user agent:
+   *        { name: "RSASSA-PKCS1-v1_5", modulusLength: 2048,
+   *          publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" },
+   *        and { name: "ECDSA", namedCurve: "P-256" }.
+   */
+  promise_test(t =>
+    RTCPeerConnection.generateCertificate({
+      name: 'RSASSA-PKCS1-v1_5',
+      modulusLength: 2048,
+      publicExponent: new Uint8Array([1, 0, 1]),
+      hash: 'SHA-256'
+    }).then(cert => {
+      assert_true(cert instanceof RTCCertificate,
+        'Expect cert to be instance of RTCCertificate');
+
+      assert_greater_than(cert.expires, Date.now(),
+        'Expect generated certificate to expire reasonably long after current time');
+    }),
+    'generateCertificate() with compulsary RSASSA-PKCS1-v1_5 parameters should succeed');
+
+  promise_test(t =>
+    RTCPeerConnection.generateCertificate({
+      name: 'ECDSA',
+      namedCurve: 'P-256'
+    }).then(cert => {
+      assert_true(cert instanceof RTCCertificate,
+        'Expect cert to be instance of RTCCertificate');
+
+      assert_greater_than(cert.expires, Date.now(),
+        'Expect generated certificate to expire reasonably long after current time');
+    }),
+    'generateCertificate() with compulsary ECDSA parameters should succeed');
+
+  /*
+   *  4.10. A user agent must reject a call to generateCertificate() with a
+   *        DOMException of type NotSupportedError if the keygenAlgorithm
+   *        parameter identifies an algorithm that the user agent cannot or
+   *        will not use to generate a certificate for RTCPeerConnection.
+   */
+  promise_test(t =>
+    promise_rejects(t, 'NotSupportedError',
+      RTCPeerConnection.generateCertificate('invalid-algo')),
+    'generateCertificate() with invalid string algorithm should reject with NotSupportedError');
+
+  promise_test(t =>
+    promise_rejects(t, 'NotSupportedError',
+      RTCPeerConnection.generateCertificate({
+        name: 'invalid-algo'
+      })),
+    'generateCertificate() with invalid algorithm dict should reject with NotSupportedError');
+
+  /*
+   *  4.10.1. Dictionary RTCCertificateExpiration
+   *    dictionary RTCCertificateExpiration {
+   *      [EnforceRange]
+   *      DOMTimeStamp expires;
+   *    };
+   *
+   *    If this parameter is present it indicates the maximum time that
+   *    the RTCCertificate is valid for relative to the current time.
+   *
+   *    When generateCertificate is called with an object argument,
+   *    the user agent attempts to convert the object into a
+   *    RTCCertificateExpiration. If this is unsuccessful, immediately
+   *    return a promise that is rejected with a newly created TypeError
+   *    and abort processing.
+   */
+
+  promise_test(t => {
+    const start = Date.now();
+    return RTCPeerConnection.generateCertificate({
+      name: 'ECDSA',
+      namedCurve: 'P-256',
+      expires: 2000
+    }).then(cert => {
+      assert_approx_equals(cert.expires, start+2000, 1000);
+    })
+  }, 'generateCertificate() with valid expires parameter should succeed');
+
+  promise_test(t => {
+    return RTCPeerConnection.generateCertificate({
+      name: 'ECDSA',
+      namedCurve: 'P-256',
+      expires: 0
+    }).then(cert => {
+      assert_less_than_equal(cert.expires, Date.now());
+    })
+  }, 'generateCertificate() with 0 expires parameter should generate expired cert');
+
+  promise_test(t => {
+    return promise_rejects(t, new TypeError(),
+      RTCPeerConnection.generateCertificate({
+        name: 'ECDSA',
+        namedCurve: 'P-256',
+        expires: -1
+      }))
+  }, 'generateCertificate() with invalid range for expires should reject with TypeError');
+
+  promise_test(t => {
+    return promise_rejects(t, new TypeError(),
+      RTCPeerConnection.generateCertificate({
+        name: 'ECDSA',
+        namedCurve: 'P-256',
+        expires: 'invalid'
+      }))
+  }, 'generateCertificate() with invalid type for expires should reject with TypeError');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-getTransceivers-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-getTransceivers-expected.txt
new file mode 100644
index 0000000..0e7de33
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-getTransceivers-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Initial peer connection should have list of zero senders, receivers and transceivers assert_own_property: expected property "getSenders" missing
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-getTransceivers.html b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-getTransceivers.html
new file mode 100644
index 0000000..1dfcf4e1
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-getTransceivers.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCPeerConnection.prototype.getTransceivers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+  'use strict';
+
+  // Test is based on the following editor draft:
+  // https://w3c.github.io/webrtc-pc/archives/20170515/webrtc.html
+
+  /*
+   *  5.1. RTCPeerConnection Interface Extensions
+   *  partial interface RTCPeerConnection {
+   *      sequence<RTCRtpSender>      getSenders();
+   *      sequence<RTCRtpReceiver>    getReceivers();
+   *      sequence<RTCRtpTransceiver> getTransceivers();
+   *      ...
+   *  };
+   */
+
+  test(t => {
+    const pc = new RTCPeerConnection();
+
+    assert_own_property(pc, 'getSenders');
+    const senders = pc.getSenders();
+    assert_array_equals([], senders, 'Expect senders to be empty array');
+
+    assert_own_property(pc, 'getReceivers');
+    const receivers = pc.getReceivers();
+    assert_array_equals([], receivers, 'Expect receivers to be empty array');
+
+    assert_own_property(pc, 'getTransceivers');
+    const transceivers = pc.getTransceivers();
+    assert_array_equals([], transceivers, 'Expect transceivers to be empty array');
+
+  }, 'Initial peer connection should have list of zero senders, receivers and transceivers');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-helper.js b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-helper.js
new file mode 100644
index 0000000..6ad85dd
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-helper.js
@@ -0,0 +1,212 @@
+'use strict'
+
+/*
+ *  Helper Methods for testing the following methods in RTCPeerConnection:
+ *    createOffer
+ *    createAnswer
+ *    setLocalDescription
+ *    setRemoteDescription
+ *
+ *  This file offers the following features:
+ *    SDP similarity comparison
+ *    Generating offer/answer using anonymous peer connection
+ *    Test signalingstatechange event
+ *    Test promise that never resolve
+ */
+
+const audioLineRegex = /\r\nm=audio.+\r\n/g;
+const videoLineRegex = /\r\nm=video.+\r\n/g;
+const applicationLineRegex = /\r\nm=application.+\r\n/g;
+
+function countLine(sdp, regex) {
+  const matches = sdp.match(regex);
+  if(matches === null) {
+    return 0;
+  } else {
+    return matches.length;
+  }
+}
+
+function countAudioLine(sdp) {
+  return countLine(sdp, audioLineRegex);
+}
+
+function countVideoLine(sdp) {
+  return countLine(sdp, videoLineRegex);
+}
+
+function countApplicationLine(sdp) {
+  return countLine(sdp, applicationLineRegex);
+}
+
+function similarMediaDescriptions(sdp1, sdp2) {
+  if(sdp1 === sdp2) {
+    return true;
+  } else if(
+    countAudioLine(sdp1) !== countAudioLine(sdp2) ||
+    countVideoLine(sdp1) !== countVideoLine(sdp2) ||
+    countApplicationLine(sdp1) !== countApplicationLine(sdp2))
+  {
+    return false;
+  } else {
+    return true;
+  }
+}
+
+// Assert that given object is either an
+// RTCSessionDescription or RTCSessionDescriptionInit
+function assert_is_session_description(sessionDesc) {
+  if(sessionDesc instanceof RTCSessionDescription) {
+    return;
+  }
+
+  assert_not_equals(sessionDesc, undefined,
+    'Expect session description to be defined, but got undefined');
+
+  assert_true(typeof(sessionDesc) === 'object',
+    'Expect sessionDescription to be either a RTCSessionDescription or an object');
+
+  assert_true(typeof(sessionDesc.type) === 'string',
+    'Expect sessionDescription.type to be a string');
+
+  assert_true(typeof(sessionDesc.type) === 'string',
+    'Expect sessionDescription.sdp to be a string');
+}
+
+
+// We can't do string comparison to the SDP content,
+// because RTCPeerConnection may return SDP that is
+// slightly modified or reordered from what is given
+// to it due to ICE candidate events or serialization.
+// Instead, we create SDP with different number of media
+// lines, and if the SDP strings are not the same, we
+// simply count the media description lines and if they
+// are the same, we assume it is the same.
+function isSimilarSessionDescription(sessionDesc1, sessionDesc2) {
+  assert_is_session_description(sessionDesc1);
+  assert_is_session_description(sessionDesc2);
+
+  if(sessionDesc1.type !== sessionDesc2.type) {
+    return false;
+  } else {
+    return similarMediaDescriptions(sessionDesc1.sdp, sessionDesc2.sdp);
+  }
+}
+
+function assert_session_desc_equals(sessionDesc1, sessionDesc2) {
+  assert_true(isSimilarSessionDescription(sessionDesc1, sessionDesc2),
+    'Expect both session descriptions to have the same count of media lines');
+}
+
+function assert_session_desc_not_equals(sessionDesc1, sessionDesc2) {
+  assert_false(isSimilarSessionDescription(sessionDesc1, sessionDesc2),
+    'Expect both session descriptions to have different count of media lines');
+}
+
+// Helper function to generate offer using a freshly created RTCPeerConnection
+// object with any audio, video, data media lines present
+function generateOffer(options={}) {
+  const {
+    audio=false,
+    video=false,
+    data=false
+  } = options;
+
+  const pc = new RTCPeerConnection();
+
+  if(data) {
+    pc.createDataChannel('test');
+  }
+
+  return pc.createOffer({
+    offerToReceiveAudio: audio,
+    offerToReceiveVideo: video
+  }).then(offer => {
+    // Guard here to ensure that the generated offer really
+    // contain the number of media lines we want
+    const { sdp } = offer;
+
+    if(audio) {
+      assert_equals(countAudioLine(sdp), 1,
+        'Expect m=audio line to be present in generated SDP');
+    } else {
+      assert_equals(countAudioLine(sdp), 0,
+        'Expect m=audio line to be present in generated SDP');
+    }
+
+    if(video) {
+      assert_equals(countVideoLine(sdp), 1,
+        'Expect m=video line to be present in generated SDP');
+    } else {
+      assert_equals(countVideoLine(sdp), 0,
+        'Expect m=video line to not present in generated SDP');
+    }
+
+    if(data) {
+      assert_equals(countApplicationLine(sdp), 1,
+        'Expect m=application line to be present in generated SDP');
+    } else {
+      assert_equals(countApplicationLine(sdp), 0,
+        'Expect m=application line to not present in generated SDP');
+    }
+
+    return offer;
+  });
+}
+
+// Helper function to generate answer based on given offer using a freshly
+// created RTCPeerConnection object
+function generateAnswer(offer) {
+  const pc = new RTCPeerConnection();
+  return pc.setRemoteDescription(offer)
+  .then(() => pc.createAnswer());
+}
+
+// Wait for peer connection to fire onsignalingstatechange
+// event, compare and make sure the new state is the same
+// as expected state. It accepts an RTCPeerConnection object
+// and an array of expected state changes. The test passes
+// if all expected state change events have been fired, and
+// fail if the new state is different from the expected state.
+//
+// Note that the promise is never resolved if no change
+// event is fired. To avoid confusion with the main test
+// getting timed out, this is done in parallel as a separate
+// test
+function test_state_change_event(parentTest, pc, expectedStates) {
+  return async_test(t => {
+    pc.onsignalingstatechange = t.step_func(() => {
+      if(expectedStates.length === 0) {
+        return;
+      }
+
+      const newState = pc.signalingState;
+      const expectedState = expectedStates.shift();
+
+      assert_equals(newState, expectedState, 'New signaling state is different from expected.');
+
+      if(expectedStates.length === 0) {
+        t.done();
+      }
+    });
+  }, `Test onsignalingstatechange event for ${parentTest.name}`);
+}
+
+// Run a test function that return a promise that should
+// never be resolved. For lack of better options,
+// we wait for a time out and pass the test if the
+// promise doesn't resolve within that time.
+function test_never_resolve(testFunc, testName) {
+  async_test(t => {
+    testFunc(t)
+    .then(
+      t.step_func(result => {
+        assert_unreached(`Pending promise should never be resolved. Instead it is fulfilled with: ${result}`);
+      }),
+      t.step_func(err => {
+        assert_unreached(`Pending promise should never be resolved. Instead it is rejected with: ${err}`);
+      }));
+
+    t.step_timeout(t.step_func_done(), 100)
+  }, testName);
+}
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-onnegotiationneeded.html b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-onnegotiationneeded.html
new file mode 100644
index 0000000..6d0fe6f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-onnegotiationneeded.html
@@ -0,0 +1,298 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test RTCPeerConnection.prototype.onnegotiationneeded</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+  'use strict';
+
+  // Test is based on the following editor draft:
+  // https://w3c.github.io/webrtc-pc/archives/20170515/webrtc.html
+
+  /* Helper Functions */
+
+  // Listen to the negotiationneeded event on a peer connection
+  // Returns a promise that resolves when the first event is fired.
+  // The resolve result is a dictionary with event and nextPromise,
+  // which resolves when the next negotiationneeded event is fired.
+  // This allow us to promisify the event listening and assert whether
+  // an event is fired or not by testing whether a promise is resolved.
+  function awaitNegotiation(pc) {
+    if(pc.onnegotiationneeded) {
+      throw new Error('connection is already attached with onnegotiationneeded event handler');
+    }
+
+    function waitNextNegotiation() {
+      return new Promise(resolve => {
+        pc.onnegotiationneeded = event => {
+          const nextPromise = waitNextNegotiation();
+          resolve({ nextPromise, event });
+        }
+      });
+    }
+
+    return waitNextNegotiation();
+  }
+
+  // Return a promise that rejects if the first promise is resolved before second promise.
+  // Also rejects when either promise rejects.
+  function assert_first_promise_fulfill_after_second(promise1, promise2, message) {
+    if(!message) {
+      message = 'first promise is resolved before second promise';
+    }
+
+    return new Promise((resolve, reject) => {
+      let secondResolved = false;
+
+      promise1.then(() => {
+        if(secondResolved) {
+          resolve();
+        } else {
+          assert_unreached(message);
+        }
+      })
+      .catch(reject);
+
+      promise2.then(() => {
+        secondResolved = true;
+      }, reject);
+    });
+  }
+
+  // Run a test function that return a promise that should
+  // never be resolved. For lack of better options,
+  // we wait for a time out and pass the test if the
+  // promise doesn't resolve within that time.
+  function test_never_resolve(testFunc, testName) {
+    async_test(t => {
+      testFunc(t)
+      .then(
+        t.step_func(result => {
+          assert_unreached(`Pending promise should never be resolved. Instead it is fulfilled with: ${result}`);
+        }),
+        t.step_func(err => {
+          assert_unreached(`Pending promise should never be resolved. Instead it is rejected with: ${err}`);
+        }));
+
+      t.step_timeout(t.step_func_done(), 200)
+    }, testName);
+  }
+
+  // Helper function to generate answer based on given offer using a freshly
+  // created RTCPeerConnection object
+  function generateAnswer(offer) {
+    const pc = new RTCPeerConnection();
+    return pc.setRemoteDescription(offer)
+    .then(() => pc.createAnswer());
+  }
+
+  /*
+    4.7.3.  Updating the Negotiation-Needed flag
+
+      To update the negotiation-needed flag
+      5.  Set connection's [[needNegotiation]] slot to true.
+      6.  Queue a task that runs the following steps:
+          3.  Fire a simple event named negotiationneeded at connection.
+
+      To check if negotiation is needed
+      2.  If connection has created any RTCDataChannels, and no m= section has
+          been negotiated yet for data, return "true".
+
+    6.1.  RTCPeerConnection Interface Extensions
+
+      createDataChannel
+        14. If channel was the first RTCDataChannel created on connection,
+            update the negotiation-needed flag for connection.
+   */
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+    const promise = awaitNegotiation(pc);
+    pc.createDataChannel('test');
+    return promise;
+  }, 'Creating first data channel should fire negotiationneeded event');
+
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+    pc.createDataChannel('test');
+    // Attaching the event handler after the negotiation-needed steps
+    // are performed should still receive the event, because the event
+    // firing is queued as a task
+    return awaitNegotiation(pc);
+  }, 'task for negotiationneeded event should be enqueued for next tick');
+
+  /*
+    4.7.3.  Updating the Negotiation-Needed flag
+
+      To update the negotiation-needed flag
+      6.  Queue a task that runs the following steps:
+          1.  If connection's [[isClosed]] slot is true, abort these steps.
+   */
+  test_never_resolve(t => {
+    const pc = new RTCPeerConnection();
+    pc.createDataChannel('test');
+    pc.close();
+    return awaitNegotiation(pc);
+  }, 'negotiationneeded event should not fire if connection is closed');
+
+  test_never_resolve(t => {
+    const pc = new RTCPeerConnection();
+    pc.createDataChannel('foo');
+
+    return awaitNegotiation(pc)
+      .then(({nextPromise}) => {
+      pc.createDataChannel('bar');
+      return nextPromise;
+    });
+  }, 'calling createDataChannel twice should fire negotiationneeded event once');
+
+  /*
+    4.7.3.  Updating the Negotiation-Needed flag
+      To check if negotiation is needed
+      3.  For each transceiver t in connection's set of transceivers, perform
+          the following checks:
+          1.  If t isn't stopped and isn't yet associated with an m= section
+              according to [JSEP] (section 3.4.1.), return "true".
+
+    5.1.  RTCPeerConnection Interface Extensions
+      addTransceiver
+        9.  Update the negotiation-needed flag for connection.
+   */
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+    pc.addTransceiver('audio');
+    return awaitNegotiation(pc);
+  }, 'addTransceiver() should fire negotiationneeded event');
+
+  /*
+    4.7.3.  Updating the Negotiation-Needed flag
+      To update the negotiation-needed flag
+      4.  If connection's [[needNegotiation]] slot is already true, abort these steps.
+   */
+  test_never_resolve(t => {
+    const pc = new RTCPeerConnection();
+    pc.addTransceiver('audio');
+    return awaitNegotiation(pc)
+    .then(({nextPromise}) => {
+      pc.addTransceiver('video');
+      return nextPromise;
+    });
+  }, 'Calling addTransceiver() twice should fire negotiationneeded event once');
+
+  /*
+    4.7.3.  Updating the Negotiation-Needed flag
+      To update the negotiation-needed flag
+      4.  If connection's [[needNegotiation]] slot is already true, abort these steps.
+   */
+  test_never_resolve(t => {
+    const pc = new RTCPeerConnection();
+    pc.createDataChannel('test');
+    return awaitNegotiation(pc)
+    .then(({nextPromise}) => {
+      pc.addTransceiver('video');
+      return nextPromise;
+    });
+  }, 'Calling both addTransceiver() and createDataChannel() should fire negotiationneeded event once');
+
+  /*
+    4.7.3.  Updating the Negotiation-Needed flag
+      To update the negotiation-needed flag
+      2.  If connection's signaling state is not "stable", abort these steps.
+   */
+  test_never_resolve(t => {
+    const pc = new RTCPeerConnection();
+    const promise = awaitNegotiation(pc);
+
+    return pc.createOffer()
+    .then(offer => pc.setLocalDescription(offer))
+    .then(() => {
+      assert_equals(pc.signalingState, 'have-local-offer');
+      pc.createDataChannel('test');
+      return promise;
+    });
+  }, 'negotiationneeded event should not fire if signaling state is not stable');
+
+  /*
+    4.3.1.  RTCPeerConnection Operation
+      To set an RTCSessionDescription description
+        10. If connection's signaling state is now stable, update the
+            negotiation-needed flag. If connection's [[needNegotiation]] slot
+            was true both before and after this update, queue a task that runs
+            the following steps:
+          1.  If connection's [[isClosed]] slot is true, abort these steps.
+          2.  If connection's [[needNegotiation]] slot is false, abort these steps.
+          3.  Fire a simple event named negotiationneeded at connection.
+   */
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+
+    return assert_first_promise_fulfill_after_second(
+      awaitNegotiation(pc),
+      pc.createOffer()
+      .then(offer =>
+        pc.setLocalDescription(offer)
+        .then(() => {
+          pc.createDataChannel('test');
+          return generateAnswer(offer);
+        }))
+      .then(answer => pc.setRemoteDescription(answer)),
+      'Expect negotiationneeded promise to resolve after pc has set remote answer and go back to stable state');
+  }, 'negotiationneeded event should fire only after signaling state go back to stable');
+
+  /*
+    TODO
+    4.7.3.  Updating the Negotiation-Needed flag
+
+      To update the negotiation-needed flag
+      1.  If connection's [[isClosed]] slot is true, abort these steps.
+      3.  If the result of checking if negotiation is needed is "false",
+          clear the negotiation-needed flag by setting connection's
+          [[needNegotiation]] slot to false, and abort these steps.
+      6.  Queue a task that runs the following steps:
+          2.  If connection's [[needNegotiation]] slot is false, abort these steps.
+
+      To check if negotiation is needed
+      3.  For each transceiver t in connection's set of transceivers, perform
+          the following checks:
+          2.  If t isn't stopped and is associated with an m= section according
+              to [JSEP] (section 3.4.1.), then perform the following checks:
+              1.  If t's direction is "sendrecv" or "sendonly", and the
+                  associated m= section in connection's currentLocalDescription
+                  doesn't contain an "a=msid" line, return "true".
+              2.  If connection's currentLocalDescription if of type "offer",
+                  and the direction of the associated m= section in neither the
+                  offer nor answer matches t's direction, return "true".
+              3.  If connection's currentLocalDescription if of type "answer",
+                  and the direction of the associated m= section in the answer
+                  does not match t's direction intersected with the offered
+                  direction (as described in [JSEP] (section 5.3.1.)),
+                  return "true".
+          3.  If t is stopped and is associated with an m= section according
+              to [JSEP] (section 3.4.1.), but the associated m= section is
+              not yet rejected in connection's currentLocalDescription or
+              currentRemoteDescription , return "true".
+      4.  If all the preceding checks were performed and "true" was not returned,
+          nothing remains to be negotiated; return "false".
+
+    4.3.1.  RTCPeerConnection Operation
+
+      When the RTCPeerConnection() constructor is invoked
+        7.  Let connection have a [[needNegotiation]] internal slot, initialized to false.
+
+    5.1.  RTCPeerConnection Interface Extensions
+
+      addTrack
+        10. Update the negotiation-needed flag for connection.
+
+      removeTrack
+        12. Update the negotiation-needed flag for connection.
+
+    5.4.  RTCRtpTransceiver Interface
+
+      setDirection
+        7.  Update the negotiation-needed flag for connection.
+
+      stop
+        11. Update the negotiation-needed flag for connection.
+   */
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-setLocalDescription.html b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-setLocalDescription.html
new file mode 100644
index 0000000..5b75534
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-setLocalDescription.html
@@ -0,0 +1,261 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCPeerConnection.prototype.setLocalDescription</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script>
+  'use strict';
+
+  // Test is based on the following editor draft:
+  // https://w3c.github.io/webrtc-pc/archives/20170515/webrtc.html
+
+  /*
+   * 4.3.2. setLocalDescription(offer)
+   */
+
+  /*
+   *  5.  If description.sdp is null and description.type is offer, set description.sdp
+   *      to lastOffer.
+   */
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+    return pc.createOffer()
+    .then(offer =>
+      pc.setLocalDescription({ type: 'offer' })
+      .then(() => {
+        assert_equals(pc.signalingState, 'have-local-offer');
+        assert_session_desc_equals(pc.localDescription, offer);
+        assert_session_desc_equals(pc.pendingLocalDescription, offer);
+        assert_equals(pc.currentLocalDescription, null);
+      }));
+  }, 'setLocalDescription with type offer and null sdp should use lastOffer generated from createOffer');
+
+  /*
+   *  6.  If description.type is offer and description.sdp does not match lastOffer,
+   *      reject the promise with a newly created InvalidModificationError and abort
+   *      these steps.
+   */
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+
+    return generateOffer({ data: true })
+    .then(offer =>
+      promise_rejects(t, 'InvalidModificationError',
+        pc.setLocalDescription(offer)));
+  }, 'setLocalDescription() with offer not created by own createOffer() should reject with InvalidModificationError');
+
+  /*
+   *  6.  If description.type is offer and description.sdp does not match lastOffer,
+   *      reject the promise with a newly created InvalidModificationError and abort
+   *      these steps.
+   */
+  promise_test(t => {
+    // Create first offer with audio line, then second offer with
+    // both audio and video line. Since the second offer is the
+    // last offer, setLocalDescription would reject when setting
+    // with the first offer
+    const pc = new RTCPeerConnection();
+    return pc.createOffer({ offerToReceiveAudio: true })
+    .then(offer1 =>
+      pc.createOffer({ offerToReceiveVideo: true })
+      .then(offer2 => {
+        assert_session_desc_not_equals(offer1, offer2);
+        return promise_rejects(t, 'InvalidModificationError',
+          pc.setLocalDescription(offer1));
+      }));
+  }, 'Set created offer other than last offer should reject with InvalidModificationError');
+
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+
+    // Only one state change event should be fired
+    test_state_change_event(t, pc, ['have-local-offer']);
+
+    return pc.createOffer({ offerToReceiveAudio: true })
+    .then(offer1 =>
+      pc.setLocalDescription(offer1)
+      .then(() =>
+        pc.createOffer({ offerToReceiveVideo: true })
+        .then(offer2 =>
+          pc.setLocalDescription(offer2)
+          .then(offer2 => {
+            assert_session_desc_not_equals(offer1, offer2);
+            assert_equals(pc.signalingState, 'have-local-offer');
+            assert_session_desc_equals(pc.localDescription, offer2);
+            assert_session_desc_equals(pc.pendingLocalDescription, offer2);
+            assert_equals(pc.currentLocalDescription, null);
+          }))));
+  }, 'Creating and setting offer multiple times should succeed');
+
+  test_never_resolve(t => {
+    const pc = new RTCPeerConnection();
+
+    return pc.createOffer()
+    .then(offer => {
+      const promise = pc.setLocalDescription(offer);
+      pc.close();
+      return promise;
+    });
+  }, 'setLocalDescription(offer) should never resolve if connection is closed in parallel')
+
+  /*
+   *  TODO
+   *  4.3.1.  Setting an RTCSessionDescription
+   *    2.2.2.  If description is set as a local description, then run one of
+   *            the following steps:
+   *      - If description is of type "rollback", then this is a rollback. Set
+   *        connection.pendingLocalDescription to null and signaling state to stable.
+   *      - If description is of type "pranswer", then set connection.pendingLocalDescription
+   *        to description and signaling state to have-local-pranswer.
+   */
+
+
+  /* setLocalDescription(answer) */
+
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+    test_state_change_event(t, pc, ['have-remote-offer', 'stable']);
+
+    return generateOffer({ video: true })
+    .then(offer =>
+      pc.setRemoteDescription(offer)
+      .then(() => pc.createAnswer())
+      .then(answer =>
+        pc.setLocalDescription(answer)
+        .then(() => {
+          assert_equals(pc.signalingState, 'stable');
+          assert_session_desc_equals(pc.localDescription, answer);
+          assert_session_desc_equals(pc.remoteDescription, offer);
+
+          assert_session_desc_equals(pc.currentLocalDescription, answer);
+          assert_session_desc_equals(pc.currentRemoteDescription, offer);
+
+          assert_equals(pc.pendingLocalDescription, null);
+          assert_equals(pc.pendingRemoteDescription, null);
+        })));
+  }, 'setLocalDescription() with valid answer should succeed');
+
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+
+    return generateOffer({ video: true })
+    .then(offer =>
+      pc.setRemoteDescription(offer)
+      .then(() => pc.createAnswer())
+      .then(answer =>
+        pc.setLocalDescription({ type: 'answer' })
+        .then(() => {
+          assert_equals(pc.signalingState, 'stable');
+          assert_session_desc_equals(pc.localDescription, answer);
+          assert_session_desc_equals(pc.remoteDescription, offer);
+
+          assert_session_desc_equals(pc.currentLocalDescription, answer);
+          assert_session_desc_equals(pc.currentRemoteDescription, offer);
+
+          assert_equals(pc.pendingLocalDescription, null);
+          assert_equals(pc.pendingRemoteDescription, null);
+        })));
+  }, 'setLocalDescription() with type answer and null sdp should use lastAnswer generated from createAnswer');
+
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+
+    return generateOffer({ video: true })
+    .then(offer =>
+      pc.setRemoteDescription(offer)
+      .then(() => generateAnswer(offer))
+      .then(answer =>
+        promise_rejects(t, 'InvalidModificationError',
+          pc.setLocalDescription(answer))));
+  }, 'setLocalDescription() with answer not created by own createAnswer() should reject with InvalidModificationError');
+
+  /*
+   *  Operations after returning to stable state
+   */
+
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+    test_state_change_event(t, pc, ['have-local-offer', 'stable', 'have-local-offer']);
+
+    return pc.createOffer({ offerToReceiveAudio: true })
+    .then(offer1 =>
+      pc.setLocalDescription(offer1)
+      .then(() => generateAnswer(offer1))
+      .then(answer => pc.setRemoteDescription(answer))
+      .then(() => {
+        pc.createDataChannel('test');
+        return pc.createOffer({ offerToReceiveVideo: true });
+      })
+      .then(offer2 =>
+        pc.setLocalDescription(offer2)
+        .then(() => {
+          assert_equals(pc.signalingState, 'have-local-offer');
+          assert_session_desc_not_equals(offer1, offer2);
+          assert_session_desc_equals(pc.localDescription, offer2);
+          assert_session_desc_equals(pc.currentLocalDescription, offer1);
+          assert_session_desc_equals(pc.pendingLocalDescription, offer2);
+        })));
+  }, 'Calling createOffer() and setLocalDescription() again after one round of local-offer/remote-answer should succeed');
+
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+    test_state_change_event(t, pc,
+      ['have-remote-offer', 'stable', 'have-local-offer']);
+
+    return generateOffer({ data: true })
+    .then(offer => pc.setRemoteDescription(offer))
+    .then(() => pc.createAnswer())
+    .then(answer =>
+      pc.setLocalDescription(answer)
+      .then(() => pc.createOffer({ offerToReceiveVideo: true }))
+      .then(offer =>
+        pc.setLocalDescription(offer)
+        .then(() => {
+          assert_equals(pc.signalingState, 'have-local-offer');
+          assert_session_desc_equals(pc.localDescription, offer);
+          assert_session_desc_equals(pc.currentLocalDescription, answer);
+          assert_session_desc_equals(pc.pendingLocalDescription, offer);
+        })));
+
+  }, 'Switching role from answerer to offerer after going back to stable state should succeed');
+
+  /*
+   *  InvalidStateError
+   *    [webrtc-pc] 4.3.1. Setting the RTCSessionDescription
+   *      2.3.  If the description's type is invalid for the current signaling state
+   *            of connection, then reject p with a newly created InvalidStateError
+   *            and abort these steps.
+   */
+
+  /*
+   *  [jsep] 5.5. If the type is "pranswer" or "answer", the PeerConnection
+   *              state MUST be either "have-remote-offer" or "have-local-pranswer".
+   */
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+
+    return generateOffer()
+    .then(offer =>
+      promise_rejects(t, 'InvalidStateError',
+        pc.setLocalDescription({ type: 'answer', sdp: offer.sdp })));
+
+  }, 'Calling setLocalDescription(answer) from stable state should reject with InvalidStateError');
+
+  /*
+   *  [jsep] 5.5. If the type is "pranswer" or "answer", the PeerConnection
+   *              state MUST be either "have-remote-offer" or "have-local-pranswer".
+   */
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+
+    return pc.createOffer()
+    .then(offer =>
+      pc.setLocalDescription(offer)
+      .then(() => generateAnswer(offer)))
+    .then(answer =>
+      promise_rejects(t, 'InvalidStateError',
+        pc.setLocalDescription(answer)));
+  }, 'Calling setLocalDescription(answer) from have-local-offer state should reject with InvalidStateError');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-setRemoteDescription.html b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-setRemoteDescription.html
index 449b3013..4ea18622 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-setRemoteDescription.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnection-setRemoteDescription.html
@@ -1,88 +1,319 @@
 <!doctype html>
-<html>
-<head>
-  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-  <title>RTCPeerConnection setRemoteDescription tests</title>
-</head>
-<body>
-  <!-- These files are in place when executing on W3C. -->
-  <script src="/resources/testharness.js"></script>
-  <script src="/resources/testharnessreport.js"></script>
-  <script type="text/javascript">
+<meta charset=utf-8>
+<title>RTCPeerConnection.prototype.setRemoteDescription</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script>
   'use strict';
 
+  // Test is based on the following editor draft:
+  // https://w3c.github.io/webrtc-pc/archives/20170515/webrtc.html
+
+  /*
+   *  4.3.2.  setRemoteDescription(offer)
+   */
+
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+
+    test_state_change_event(t, pc, ['have-remote-offer']);
+
+    return generateOffer({ data: true })
+    .then(offer =>
+      pc.setRemoteDescription(offer)
+      .then(offer => {
+        assert_equals(pc.signalingState, 'have-remote-offer');
+        assert_session_desc_equals(pc.remoteDescription, offer);
+        assert_session_desc_equals(pc.pendingRemoteDescription, offer);
+        assert_equals(pc.currentRemoteDescription, null);
+      }));
+  }, 'setRemoteDescription with valid offer should succeed');
+
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+
+    // have-remote-offer event should only fire once
+    test_state_change_event(t, pc, ['have-remote-offer']);
+
+    return Promise.all([
+      generateOffer({ audio: true }),
+      generateOffer({ data: true })
+    ]).then(([offer1, offer2]) =>
+      pc.setRemoteDescription(offer1)
+      .then(() => pc.setRemoteDescription(offer2))
+      .then(() => {
+        assert_equals(pc.signalingState, 'have-remote-offer');
+        assert_session_desc_equals(pc.remoteDescription, offer2);
+        assert_session_desc_equals(pc.pendingRemoteDescription, offer2);
+        assert_equals(pc.currentRemoteDescription, null);
+      }));
+  }, 'Setting remote description multiple times with different offer should succeed');
+
+  test_never_resolve(t => {
+    const pc = new RTCPeerConnection();
+
+    return generateOffer()
+    .then(offer => {
+      const promise = pc.setRemoteDescription(offer);
+      pc.close();
+      return promise;
+    });
+  }, 'setRemoteDescription(offer) should never resolve if connection is closed in parallel')
+
+  /*
+   *  4.3.1.  Setting an RTCSessionDescription
+   *    2.4.  If the content of description is not valid SDP syntax, then reject p
+   *          with an RTCError (with errorDetail set to "sdp-syntax-error" and the
+   *          sdpLineNumber attribute set to the line number in the SDP where the
+   *          syntax error was detected) and abort these steps.
+   */
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+
+    return pc.setRemoteDescription({
+      type: 'offer',
+      sdp: 'Invalid SDP'
+    })
+    .then(() => {
+      assert_unreached('Expect promise to be rejected');
+    }, err => {
+      assert_equals(err.errorDetail, 'sdp-syntax-error',
+        'Expect error detail field to set to sdp-syntax-error');
+
+      assert_true(err instanceof RTCError,
+        'Expect err to be instance of RTCError');
+    });
+  }, 'setRemoteDescription(offer) with invalid SDP should reject with RTCError');
+
+  /*
+   *  4.6.1.  enum RTCSdpType
+   */
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+
+    // SDP is validated after WebIDL validation
+    return promise_rejects(t, new TypeError(),
+      pc.setRemoteDescription({
+        type: 'bogus',
+        sdp: 'bogus'
+      }));
+  }, 'setRemoteDescription with invalid type and invalid SDP should reject with TypeError')
+
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+
+    // SDP is validated after validating type
+    return promise_rejects(t, 'InvalidStateError',
+      pc.setRemoteDescription({
+        type: 'answer',
+        sdp: 'invalid'
+      }));
+  }, 'setRemoteDescription() with invalid SDP and stable state should reject with InvalidStateError')
+
+  /*
+   *  TODO
+   *  Setting an RTCSessionDescription
+   *    2.1.5. If the content of description is invalid, then reject p with
+   *       a newly created InvalidAccessError and abort these steps.
+   *    2.2.5-10
+   *  setRemoteDescription(rollback)
+   *  setRemoteDescription(pranswer)
+   *
+   *  Non-testable
+   *  Setting an RTCSessionDescription
+   *    6. For all other errors, for example if description cannot be
+   *       applied at the media layer, reject p with a newly created OperationError.
+   */
+
+  /*
+   *  4.3.2.  setRemoteDescription(answer)
+   */
+
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+    test_state_change_event(t, pc, ['have-local-offer', 'stable']);
+
+    return pc.createOffer({ offerToReceiveVideo: true })
+    .then(offer =>
+      pc.setLocalDescription(offer)
+      .then(() => generateAnswer(offer))
+      .then(answer =>
+        pc.setRemoteDescription(answer)
+        .then(() => {
+          assert_session_desc_equals(pc.localDescription, offer);
+          assert_session_desc_equals(pc.remoteDescription, answer);
+
+          assert_session_desc_equals(pc.currentLocalDescription, offer);
+          assert_session_desc_equals(pc.currentRemoteDescription, answer);
+
+          assert_equals(pc.pendingLocalDescription, null);
+          assert_equals(pc.pendingRemoteDescription, null);
+        })));
+  }, 'setRemoteDescription() with valid state and answer should succeed');
+
+  /*
+   *  TODO
+   *  4.3.2 setRemoteDescription
+   *    If an a=identity attribute is present in the session description,
+   *    the browser validates the identity assertion.
+   *
+   *    If the "peerIdentity" configuration is applied to the RTCPeerConnection,
+   *    this establishes a target peer identity of the provided value. Alternatively,
+   *    if the RTCPeerConnection has previously authenticated the identity of the
+   *    peer (that is, there is a current value for peerIdentity ), then this also
+   *    establishes a target peer identity.
+   *
+   *    The target peer identity cannot be changed once set. Once set,
+   *    if a different value is provided, the user agent must reject
+   *    the returned promise with a newly created InvalidModificationError
+   *    and abort this operation. The RTCPeerConnection must be closed if
+   *    the validated peer identity does not match the target peer identity.
+   */
+
+  /*
+   *  Operations after returning to stable state
+   */
+
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+    const pc2 = new RTCPeerConnection();
+
+    test_state_change_event(t, pc,
+      ['have-remote-offer', 'stable', 'have-remote-offer']);
+
+    return generateOffer({ audio: true })
+    .then(offer1 =>
+      pc.setRemoteDescription(offer1)
+      .then(() => pc.createAnswer())
+      .then(answer => pc.setLocalDescription(answer))
+      .then(() => generateOffer({ data: true }))
+      .then(offer2 =>
+        pc.setRemoteDescription(offer2)
+        .then(() => {
+          assert_equals(pc.signalingState, 'have-remote-offer');
+          assert_session_desc_not_equals(offer1, offer2);
+          assert_session_desc_equals(pc.remoteDescription, offer2);
+          assert_session_desc_equals(pc.currentRemoteDescription, offer1);
+          assert_session_desc_equals(pc.pendingRemoteDescription, offer2);
+        })));
+  }, 'Calling setRemoteDescription() again after one round of remote-offer/local-answer should succeed');
+
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+
+    test_state_change_event(t, pc,
+       ['have-local-offer', 'stable', 'have-remote-offer']);
+
+    return pc.createOffer({ offerToReceiveAudio: true })
+    .then(offer =>
+      pc.setLocalDescription(offer)
+      .then(() => generateAnswer(offer)))
+    .then(answer =>
+      pc.setRemoteDescription(answer)
+      .then(() => generateOffer({ data: true }))
+      .then(offer =>
+        pc.setRemoteDescription(offer)
+        .then(() => {
+          assert_equals(pc.signalingState, 'have-remote-offer');
+          assert_session_desc_equals(pc.remoteDescription, offer);
+          assert_session_desc_equals(pc.currentRemoteDescription, answer);
+          assert_session_desc_equals(pc.pendingRemoteDescription, offer);
+        })));
+  }, 'Switching role from offerer to answerer after going back to stable state should succeed');
+
+  /*
+   *  InvalidStateError
+   *    [webrtc-pc] 4.3.1. Setting the RTCSessionDescription
+   *      2.3.  If the description's type is invalid for the current signaling state
+   *            of connection, then reject p with a newly created InvalidStateError
+   *            and abort these steps.
+   */
+
+  /*
+   *  [jsep] 5.6. If the type is "pranswer" or "answer", the PeerConnection
+   *              state MUST be either "have-local-offer" or "have-remote-pranswer".
+   */
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+
+    return generateOffer()
+    .then(offer =>
+      promise_rejects(t, 'InvalidStateError',
+        pc.setRemoteDescription({ type: 'answer', sdp: offer.sdp })));
+  }, 'Calling setRemoteDescription(answer) from stable state should reject with InvalidStateError');
+
+
+  /*
+   *  [jsep] 5.6. If the type is "offer", the PeerConnection state
+   *              MUST be either "stable" or "have-remote-offer".
+   */
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+
+    return pc.createOffer()
+    .then(offer => pc.setLocalDescription(offer))
+    .then(() => generateOffer())
+    .then(offer =>
+      promise_rejects(t, 'InvalidStateError',
+        pc.setRemoteDescription(offer)));
+  }, 'Calling setRemoteDescription(offer) from have-local-offer state should reject with InvalidStateError');
+
+  /*
+   *  [jsep] 5.6. If the type is "pranswer" or "answer", the PeerConnection
+   *              state MUST be either "have-local-offer" or "have-remote-pranswer".
+   */
+  promise_test(t => {
+    const pc = new RTCPeerConnection();
+
+    return generateOffer()
+    .then(offer =>
+      pc.setRemoteDescription(offer)
+      .then(() => generateAnswer(offer)))
+    .then(answer =>
+      promise_rejects(t, 'InvalidStateError',
+        pc.setRemoteDescription(answer)));
+
+  }, 'Calling setRemoteDescription(answer) from have-remote-offer state should reject with InvalidStateError');
+
   // tests that ontrack is called and parses the msid information from the SDP and creates
   // the streams with matching identifiers.
-  async_test(function(test) {
-    const sdp = 'v=0\r\n' +
-        'o=- 166855176514521964 2 IN IP4 127.0.0.1\r\n' +
-        's=-\r\n' +
-        't=0 0\r\n' +
-        'a=msid-semantic:WMS *\r\n' +
-        'm=audio 9 UDP/TLS/RTP/SAVPF 111\r\n' +
-        'c=IN IP4 0.0.0.0\r\n' +
-        'a=rtcp:9 IN IP4 0.0.0.0\r\n' +
-        'a=ice-ufrag:someufrag\r\n' +
-        'a=ice-pwd:somelongpwdwithenoughrandomness\r\n' +
-        'a=fingerprint:sha-256 8C:71:B3:8D:A5:38:FD:8F:A4:2E:A2:65:6C:86:52:BC:E0:6E:94:F2:9F:7C:4D:B5:DF:AF:AA:6F:44:90:8D:F4\r\n' +
-        'a=setup:actpass\r\n' +
-        'a=rtcp-mux\r\n' +
-        'a=mid:mid1\r\n' +
-        'a=sendonly\r\n' +
-        'a=rtpmap:111 opus/48000/2\r\n' +
-        'a=msid:stream1 track1\r\n' +
-        'a=ssrc:1001 cname:some\r\n';
+  async_test(t => {
+    const pc = new RTCPeerConnection();
 
-    var pc = new RTCPeerConnection(null);
+    // Fail the test if the ontrack event handler is not implemented
+    assert_own_property(pc, 'ontrack', 'Expect pc to have ontrack event handler attribute');
 
-    pc.ontrack = test.step_func(function(event) {
+    const sdp = `v=0
+o=- 166855176514521964 2 IN IP4 127.0.0.1
+s=-
+t=0 0
+a=msid-semantic:WMS *
+m=audio 9 UDP/TLS/RTP/SAVPF 111
+c=IN IP4 0.0.0.0
+a=rtcp:9 IN IP4 0.0.0.0
+a=ice-ufrag:someufrag
+a=ice-pwd:somelongpwdwithenoughrandomness
+a=fingerprint:sha-256 8C:71:B3:8D:A5:38:FD:8F:A4:2E:A2:65:6C:86:52:BC:E0:6E:94:F2:9F:7C:4D:B5:DF:AF:AA:6F:44:90:8D:F4
+a=setup:actpass
+a=rtcp-mux
+a=mid:mid1
+a=sendonly
+a=rtpmap:111 opus/48000/2
+a=msid:stream1 track1
+a=ssrc:1001 cname:some
+`;
+
+    pc.ontrack = t.step_func(event => {
       assert_equals(event.streams.length, 1, 'the track belongs to one MediaStream');
       assert_equals(event.streams[0].id, 'stream1', 'the stream name is parsed from the MSID line');
-      test.done();
+      t.done();
     });
 
     pc.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp}))
-    .catch(test.step_func(function(e) {
-      assert_unreached('Error ' + e.name + ': ' + e.message);
+    .catch(t.step_func(err => {
+      assert_unreached('Error ' + err.name + ': ' + err.message);
     }));
-  }, 'Triggers ontrack when called with a remote description and the MSID of the stream is is parsed.');
-
-  promise_test(t => {
-    const pc = new RTCPeerConnection();
-    return promise_rejects(t, new RTCError(),
-      pc.setRemoteDescription({
-        // valid SDP type
-        type: 'offer',
-        // malformed SDP description
-        sdp: 'bogus'
-      }));
-  }, 'Malformed SDP description should be rejected with RTCError');
-
-  promise_test(t => {
-    const pc = new RTCPeerConnection();
-    return promise_rejects(t, new TypeError(),
-      pc.setRemoteDescription({
-        // invalid enum value is caught at IDL level before
-        // method is executed
-        type: 'bogus',
-        // bogus SDP should never be validated before type
-        sdp: 'bogus'
-      }));
-  }, 'Invalid SDP type should be rejected with TypeError');
-
-  promise_test(t => {
-    const pc = new RTCPeerConnection();
-
-    return promise_rejects(t, new InvalidStateError(),
-      pc.setRemoteDescription({
-        // a new connection with stable state cannot accept answer type SDP
-        type: 'answer',
-        // bogus SDP should never be validated before validating type
-        sdp: 'bogus'
-      }));
-  }, 'SDP type that is invalid for current signaling state should be rejected with InvalidStateError');
+  }, 'setRemoteDescription should trigger ontrack event when the MSID of the stream is is parsed.');
 
 </script>
-
-</body>
-</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnectionIceEvent-constructor-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnectionIceEvent-constructor-expected.txt
new file mode 100644
index 0000000..daec596
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnectionIceEvent-constructor-expected.txt
@@ -0,0 +1,11 @@
+This is a testharness.js-based test.
+PASS RTCPeerConnectionIceEvent with no arguments throws TypeError 
+FAIL RTCPeerConnectionIceEvent with no eventInitDict (default) assert_equals: expected (object) null but got (undefined) undefined
+FAIL RTCPeerConnectionIceEvent with empty object as eventInitDict (default) assert_equals: expected (object) null but got (undefined) undefined
+PASS RTCPeerConnectionIceEvent.candidate is null when constructed with { candidate: null } 
+PASS RTCPeerConnectionIceEvent.candidate is null when constructed with { candidate: undefined } 
+FAIL RTCPeerConnectionIceEvent with RTCIceCandidate Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty.
+PASS RTCPeerConnectionIceEvent with non RTCIceCandidate object throws 
+PASS RTCPeerConnectionIceEvent bubbles and cancelable 
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnectionIceEvent-constructor.html b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnectionIceEvent-constructor.html
index 22d9d4b..07e9736 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnectionIceEvent-constructor.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCPeerConnectionIceEvent-constructor.html
@@ -1,34 +1,148 @@
-<!DOCTYPE html>
+<!doctype html>
 <meta charset="utf-8">
-<title>RTCPeerConnectionIceEvent constructor</title>
+<!--
+4.8.2 RTCPeerConnectionIceEvent
+
+  The icecandidate event of the RTCPeerConnection uses the RTCPeerConnectionIceEvent interface.
+
+-->
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script>
-test(function() {
-  assert_equals(RTCPeerConnectionIceEvent.length, 1);
-  var e = new RTCPeerConnectionIceEvent('type');
-  assert_equals(e.candidate, null);
-  assert_false(e.bubbles);
-  assert_false(e.cancelable);
-}, 'RTCPeerConnectionIceEvent constructor with no candidate provided.');
+/*
+RTCPeerConnectionIceEvent
 
-test(function() {
-  var e = new RTCPeerConnectionIceEvent('type', { candidate: null });
-  assert_equals(e.candidate, null);
-}, 'RTCPeerConnectionIceEvent constructor with candidate passed as null.');
+[Constructor(DOMString type, optional RTCPeerConnectionIceEventInit eventInitDict)]
 
-test(function() {
-  var e = new RTCPeerConnectionIceEvent('type', { candidate: undefined });
-  assert_equals(e.candidate, null);
-}, 'RTCPeerConnectionIceEvent constructor with candidate passed as undefined.');
+interface RTCPeerConnectionIceEvent : Event {
+    readonly attribute RTCIceCandidate? candidate;
+    readonly attribute DOMString?       url;
+};
+ */
+test(() => {
+  assert_throws(new TypeError(), () => {
+    new RTCPeerConnectionIceEvent();
+  });
+}, "RTCPeerConnectionIceEvent with no arguments throws TypeError");
 
-test(function() {
-  var c = new RTCIceCandidate({ candidate: 'candidate', sdpMid: 'sdpMid', sdpMLineIndex: 1 });
-  var e = new RTCPeerConnectionIceEvent('type', { candidate: c, url: 'url', bubbles: true, cancelable: true});
-  assert_equals(e.type, 'type');
-  assert_equals(e.candidate, c);
-  // assert_equals(e.url, 'url');
-  assert_true(e.bubbles);
-  assert_true(e.cancelable);
-}, 'RTCPeerConnectionIceEvent constructor with full arguments.');
+test(() => {
+  const event = new RTCPeerConnectionIceEvent("type");
+  /*
+  candidate of type RTCIceCandidate, readonly, nullable
+  url of type DOMString, readonly, nullable
+  */
+  assert_equals(event.candidate, null);
+  assert_equals(event.url, null);
+
+  /*
+  Firing an RTCPeerConnectionIceEvent event named e with an RTCIceCandidate
+  candidate means that an event with the name e, which does not bubble
+  (except where otherwise stated) and is not cancelable
+  (except where otherwise stated),
+  */
+  assert_false(event.bubbles);
+  assert_false(event.cancelable);
+
+}, "RTCPeerConnectionIceEvent with no eventInitDict (default)");
+
+test(() => {
+  const event = new RTCPeerConnectionIceEvent("type", {});
+
+  /*
+  candidate of type RTCIceCandidate, readonly, nullable
+  url of type DOMString, readonly, nullable
+  */
+  assert_equals(event.candidate, null);
+  assert_equals(event.url, null);
+
+  /*
+  Firing an RTCPeerConnectionIceEvent event named e with an RTCIceCandidate
+  candidate means that an event with the name e, which does not bubble
+  (except where otherwise stated) and is not cancelable
+  (except where otherwise stated),
+  */
+  assert_false(event.bubbles);
+  assert_false(event.cancelable);
+
+}, "RTCPeerConnectionIceEvent with empty object as eventInitDict (default)");
+
+test(() => {
+  const event = new RTCPeerConnectionIceEvent("type", {
+    candidate: null
+  });
+  assert_equals(event.candidate, null);
+}, "RTCPeerConnectionIceEvent.candidate is null when constructed with { candidate: null }");
+
+test(() => {
+  const event = new RTCPeerConnectionIceEvent("type", {
+    candidate: undefined
+  });
+  assert_equals(event.candidate, null);
+}, "RTCPeerConnectionIceEvent.candidate is null when constructed with { candidate: undefined }");
+
+
+/*
+
+4.8.1 RTCIceCandidate Interface
+
+The RTCIceCandidate() constructor takes a dictionary argument, candidateInitDict,
+whose content is used to initialize the new RTCIceCandidate object. When run, if
+both the sdpMid and sdpMLineIndex dictionary members are null, throw a TypeError.
+*/
+const candidate = "";
+const sdpMid = "sdpMid";
+const sdpMLineIndex = 1;
+const ufrag = "";
+const url = "foo.bar";
+
+test(() => {
+  const iceCandidate = new RTCIceCandidate({ candidate, sdpMid, sdpMLineIndex, ufrag });
+  const event = new RTCPeerConnectionIceEvent("type", {
+    candidate: iceCandidate,
+    url,
+  });
+
+  assert_equals(event.candidate, iceCandidate);
+  assert_false(event.bubbles);
+  assert_false(event.cancelable);
+}, "RTCPeerConnectionIceEvent with RTCIceCandidate");
+
+test(() => {
+  const plain = { candidate, sdpMid, sdpMLineIndex, ufrag };
+  assert_throws(new TypeError(), () => new RTCPeerConnectionIceEvent("type", { candidate: plain }));
+}, "RTCPeerConnectionIceEvent with non RTCIceCandidate object throws");
+
+/*
+This will remain commented out until https://github.com/w3c/webrtc-pc/issues/1232
+is resolved.
+
+test(() => {
+  // When firing an RTCPeerConnectionIceEvent event that contains a RTCIceCandidate
+  // object, it must include values for both sdpMid and sdpMLineIndex.
+
+  assert_throws(new TypeError(), () => {
+    new RTCPeerConnectionIceEvent("type", {
+      candidate: new RTCIceCandidate({ candidate, sdpMid, ufrag })
+    });
+  });
+
+  assert_throws(new TypeError(), () => {
+    new RTCPeerConnectionIceEvent("type", {
+      candidate: new RTCIceCandidate({ candidate, sdpMLineIndex, ufrag })
+    });
+  });
+
+}, "RTCIceCandidate must include values for both sdpMid and sdpMLineIndex");
+*/
+
+test(() => {
+  const event = new RTCPeerConnectionIceEvent("type", {
+    candidate: null,
+    bubbles: true,
+    cancelable: true,
+  });
+
+  assert_true(event.bubbles);
+  assert_true(event.cancelable);
+}, "RTCPeerConnectionIceEvent bubbles and cancelable");
 </script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/workers/constructors/SharedWorker/same-origin.html b/third_party/WebKit/LayoutTests/external/wpt/workers/constructors/SharedWorker/same-origin.html
index 87a3cc89..78d53164 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/workers/constructors/SharedWorker/same-origin.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/workers/constructors/SharedWorker/same-origin.html
@@ -9,7 +9,7 @@
 <div id="log"></div>
 <script>
 // Needed to prevent a race condition if a worker throws an exception that may or may
-// not propagate to the window before the tests finish
+// not propogate to the window before the tests finish
 setup({allow_uncaught_exception: true});
 
 function testSharedWorkerHelper(t, script) {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/workers/constructors/Worker/same-origin.html b/third_party/WebKit/LayoutTests/external/wpt/workers/constructors/Worker/same-origin.html
index 7fbf6d34..bbc4382 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/workers/constructors/Worker/same-origin.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/workers/constructors/Worker/same-origin.html
@@ -7,7 +7,7 @@
 <div id="log"></div>
 <script>
 // Needed to prevent a race condition if a worker throws an exception that may or may
-// not propagate to the window before the tests finish
+// not propogate to the window before the tests finish
 setup({allow_uncaught_exception: true});
 
 function testSharedWorkerHelper(t, script) {
diff --git a/third_party/WebKit/LayoutTests/fast/dom/custom/svg-use-shadow-tree-expected.txt b/third_party/WebKit/LayoutTests/fast/dom/custom/svg-use-shadow-tree-expected.txt
index ea0d3ce..54e17406 100644
--- a/third_party/WebKit/LayoutTests/fast/dom/custom/svg-use-shadow-tree-expected.txt
+++ b/third_party/WebKit/LayoutTests/fast/dom/custom/svg-use-shadow-tree-expected.txt
@@ -1,7 +1,7 @@
-CONSOLE ERROR: line 2535: Uncaught Error: assert_false: Should not call createdCallback in UA ShadowRoot. expected false got true
-CONSOLE ERROR: line 2535: Uncaught Error: assert_false: Should not call attachedCallback in UA ShadowRoot. expected false got true
-CONSOLE ERROR: line 2535: Uncaught Error: assert_false: Should not call createdCallback in UA ShadowRoot. expected false got true
-CONSOLE ERROR: line 2535: Uncaught Error: assert_false: Should not call attachedCallback in UA ShadowRoot. expected false got true
+CONSOLE ERROR: line 2539: Uncaught Error: assert_false: Should not call createdCallback in UA ShadowRoot. expected false got true
+CONSOLE ERROR: line 2539: Uncaught Error: assert_false: Should not call attachedCallback in UA ShadowRoot. expected false got true
+CONSOLE ERROR: line 2539: Uncaught Error: assert_false: Should not call createdCallback in UA ShadowRoot. expected false got true
+CONSOLE ERROR: line 2539: Uncaught Error: assert_false: Should not call attachedCallback in UA ShadowRoot. expected false got true
 This is a testharness.js-based test.
 Harness Error. harness_status.status = 1 , harness_status.message = Uncaught Error: assert_false: Should not call attachedCallback in UA ShadowRoot. expected false got true
 PASS SVG <use> shadow trees should not be exposed through custom elements. 
diff --git a/third_party/WebKit/LayoutTests/fast/harness/results.html b/third_party/WebKit/LayoutTests/fast/harness/results.html
index 35af7a1..5a669d84 100644
--- a/third_party/WebKit/LayoutTests/fast/harness/results.html
+++ b/third_party/WebKit/LayoutTests/fast/harness/results.html
@@ -1362,6 +1362,7 @@
         '<a href="javascript:void()" onclick="expandAllExpectations()">expand all</a> ' +
         '<a href="javascript:void()" onclick="collapseAllExpectations()">collapse all</a> ' +
         '<label><input id="toggle-images" type=checkbox checked onchange="handleToggleImagesChange()">Toggle images</label>' +
+        ' <a href="test-expectations.html">Expectations</a>' +
         '<div id=container>Show: '+
         '<label><input id="show-expected-failures" type=checkbox onchange="handleUnexpectedResultsChange()">expected failures</label>' +
         '<label><input id="show-flaky-failures" type=checkbox onchange="handleFlakyFailuresChange()">flaky failures</label>' +
diff --git a/third_party/WebKit/LayoutTests/fast/harness/test-expectations.html b/third_party/WebKit/LayoutTests/fast/harness/test-expectations.html
new file mode 100644
index 0000000..c840649
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/harness/test-expectations.html
@@ -0,0 +1,666 @@
+<!doctype html>
+<meta charset="UTF-8">
+<title>Layout Tests</title>
+<style>
+body {
+  font-family: monospace;
+}
+button {
+  margin-top: 4px;
+}
+#help {
+  box-sizing: border-box;
+  position: fixed;
+  width: 96vw;
+  height: 96vh;
+  top: 2vh;
+  left: 2vw;
+  border: 5px solid black;
+  background-color: white;
+  padding: 16px;
+  box-shadow: 0 0 20px;
+}
+.hidden {
+  display: none;
+}
+.warn {
+  color: red;
+}
+.fix-width {
+  display: inline-block;
+  width: 9em;
+  text-align: right;
+}
+.h-expect {
+  margin-left: 1.25em;
+}
+.expect {
+  line-height: 180%;
+  cursor: zoom-in;
+}
+.expect:hover {
+  /*background-color: #F4F4F4;*/
+}
+.expect:focus > .details {
+  visibility: visible;
+}
+.details {
+  box-sizing: border-box;
+  visibility: hidden;
+  display: inline-block;
+  position: relative;
+  top: 0.2em;
+  width: 1em;
+  height: 1em;
+  border-top: 0.5em solid transparent;
+  border-bottom: 0.5em solid transparent;
+  border-right: none;
+  border-left: 0.5em solid gray;
+  margin-right: .25em;
+  cursor: pointer;
+}
+.details.open {
+  visibility: visible !important;
+  top: 0.5em;
+  border-left: 0.5em solid transparent;
+  border-right: 0.5em solid transparent;
+  border-top: 0.5em solid gray;
+  border-bottom: none;
+}
+
+.result-frame {
+  border: 1px solid gray;
+  border-top: 1px solid transparent;
+  margin-left: 2.25em;
+  margin-right: 2.25em;
+  margin-top: 4px;
+  margin-bottom: 16px;
+}
+.result-menu {
+  list-style-type: none;
+  margin: 0;
+  padding: 0;
+}
+.result-menu li {
+  display: inline-block;
+  min-width: 100px;
+  font-size: larger;
+  border: 1px dotted gray;
+  border-bottom: 1px solid transparent;
+  margin-right: 8px;
+}
+.result-output iframe {
+  width: 100%;
+  height: 50vh;
+  max-height: 800px;
+  border: 0px solid gray;
+  resize: both;
+  overflow: auto;
+}
+</style>
+<body>
+<h3>layout test results viewer</h3>
+<div style="position:absolute; top:8px;right:0;font-size:smaller">go back to <a href="results.html">legacy results.html</a></div>
+<div id="help" class="hidden">
+  <button onclick="toggleVisibility('help')">Close</button>
+  <pre>
+This page lets you query and display test results.
+
+<span style="font-size:larger">### Results</span>
+
+The results are shown in either plain text, or <a href="https://chromium.googlesource.com/chromium/src/+/master/docs/testing/layout_test_expectations.md">TestExpectations</a> format.
+
+TestExpectations result lines usually looks like this:
+
+<a href="#">crbug.com/bug</a> layout/test/path/<a href="#">test.html</a> [ Status ]
+
+The interesting part here is [ Status ]. Inside TestExpectations file, [ Status ]
+can have multiple values, representing all expected results. For example:
+
+[ Failure Slow Timeout Crash Pass ]
+
+Result lines include existing expected values, and make a guess about what the new
+test expectation line should look like by merging together expected and actual
+results. The actual result will be shown in bold. For example:
+
+TestResult(PASS) + TestExpectation(Failure) => [ Pass ]
+TestResult(CRASH) + TextExpectation(Failure) => [ Failure <b>Crash</b> ]
+
+If you are doing a lot of TestExpectation edits, the hope is that this will make
+your job as easy as copy and paste.
+
+<span style="font-size:larger">### Keyboard navigation</span>
+
+<b>Tab</b> to select tests.
+<b>Enter</b> to see test details. This will automatically close other details.
+
+Modifiers:
+
+<b>Shift</b> hold shift key to keep other details open.
+<b>Alt|Option</b> hold alt key to open details on all results (limit: 100)
+
+If you are unhappy with results, please file a bug, or fix it <a href="https://cs.chromium.org/chromium/src/third_party/WebKit/LayoutTests/fast/harness/test-expectations.html">here</a>.
+  </pre>
+</div>
+<pre id="summary">
+Test run summary <a id="help_button" href="javascript:toggleVisibility('help')">?</a>:
+Passed     : <span id="summary_passed"></span>
+Regressions: <span id="summary_regressions"></span>
+Total      : <span id="summary_total"></span>
+</pre>
+<div>Display filtered results by picking a filter:</div>
+<div id="filters">
+  <div>
+    <span class="fix-width">Unexpected:</span>
+    <button id="button_unexpected_fail" onclick="javascript:generateReport('Unexpected failures', Filters.unexpectedFailure)">
+      Unexpected Failure
+      <span id="count_unexpected_fail"></span>
+    </button>
+    <button onclick="javascript:generateReport('Unexpected passes', Filters.unexpectedPass)">
+      Unexpected Pass
+      <span id="count_unexpected_pass"></span>
+    </button>
+  </div>
+  <div>
+    <span class="fix-width">All failures:</span>
+    <button onclick="javascript:generateReport('Crash', Filters.actual('CRASH'))">
+      Crash
+      <span id="count_CRASH"></span>
+    </button>
+    <button onclick="javascript:generateReport('Timeout', Filters.actual('TIMEOUT'))">
+      Timeout
+      <span id="count_TIMEOUT"></span>
+    </button>
+    <button onclick="javascript:generateReport('Text failure', Filters.actual('TEXT'))">
+      Text failure
+      <span id="count_TEXT"></span>
+    </button>
+    <button onclick="javascript:generateReport('Image failure', Filters.actual('IMAGE'))">
+      Image failure
+      <span id="count_IMAGE"></span>
+    </button>
+    <button onclick="javascript:generateReport('Image+text failure', Filters.actual('IMAGE+TEXT'))">
+      Image+text failure
+      <span id="count_IMAGE_TEXT"></span>
+    </button>
+  </div>
+  <div>
+    <span class="fix-width">Misc:</span>
+
+    <button onclick="javascript:generateReport('Skipped', Filters.actual('SKIP'))">
+      Skipped
+      <span id="count_SKIP"></span>
+    </button>
+    <button onclick="javascript:generateReport('Pass', Filters.actual('PASS'))">
+      Pass
+      <span id="count_PASS"></span>
+    </button>
+    <button onclick="javascript:generateReport('Wontfix -- defined in NeverFixTests', Filters.wontfix)">
+      WontFix
+      <span id="count_WONTFIX"></span>
+    </button>
+    <button onclick="javascript:generateReport('Missing', Filters.actual('MISSING'))">
+      Missing
+      <span id="count_MISSING"></span>
+    </button>
+    <button onclick="javascript:generateReport('TestExpectations', Filters.notpass)">
+      TestExpectations
+      <span id="count_testexpectations"></span>
+    </button>
+  </div>
+</div>
+
+<div id="report_header" style="margin-top:8px">
+  Tests shown: <span id="report_title" style="font-weight:bold"></span>
+  in format:
+    <select id="report_format" onchange="generateReport()">
+      <option value="plain" selected>Plain text</option>
+      <option value="expectation">TestExpectations</option>
+    </select>
+</div>
+<div id="report" style="margin-top:8px">
+</div>
+
+<template id="genericResult">
+<div class="result-frame">
+  <ul class="result-menu">
+  </ul>
+  <div class="result-output">
+  </div>
+</div>
+</template>
+<script>
+
+var fullResults = {};
+
+function printSummary(r) {
+  document.querySelector('#summary_total').innerText = r.num_passes + r.num_regressions;
+  document.querySelector('#summary_passed').innerText = r.num_passes;
+  document.querySelector('#summary_regressions').innerText = r.num_regressions;
+  let failures = r["num_failures_by_type"];
+  var totalFailures = 0;
+  for (p in failures) {
+    if (failures[p]) {
+      try {
+        document.querySelector("#count_" + p.replace("+", "_")).innerText = failures[p];
+      } catch(e) {
+        console.error("Missing failure type", p);
+      }
+    }
+  }
+
+  var t = new Traversal(fullResults.tests);
+  t.traverse(Filters.unexpectedPass);
+  document.querySelector("#count_unexpected_pass").innerText = t.filteredCount;
+  t.reset().traverse(Filters.unexpectedFailure);
+  document.querySelector("#count_unexpected_fail").innerText = t.filteredCount;
+  t.reset().traverse(Filters.notpass);
+  document.querySelector("#count_testexpectations").innerText = t.filteredCount;
+  // Hide filters with zero count
+  for (let el of Array.from(
+        document.querySelector("#filters").querySelectorAll("*"))) {
+    if (el.id && el.id.startsWith("count")
+      && el.innerText == ""
+      && el.parentNode.nodeName == "BUTTON") {
+        el.parentNode.remove();
+    }
+  }
+}
+
+let Traversal = function(testRoot) {
+  this.root = testRoot;
+  this.reset();
+}
+
+Traversal.prototype = {
+  traverse: function(filter, action) {
+    action = action || function() {};
+    this._helper(this.root, "", filter, action);
+  },
+  reset: function() {
+    this.testCount = 0;
+    this.filteredCount = 0;
+    this.lastDir = "";
+    this.html = [];
+    return this;
+  },
+
+  _helper: function(node, path, filter, action) {
+    if ("actual" in node) {
+      this.testCount++;
+      if (filter(node, path)) {
+        this.filteredCount++;
+        action(node, path, this);
+      }
+    }
+    else {
+      // TODO(atotic) we need name + type sort, tests go before dirs.
+      var keys = Object.keys(node).sort();
+      for (p of keys)
+        this._helper(node[p], path + "/" + p, filter, action);
+    }
+  }
+}
+
+let PathParser = function(path) {
+  this.path = path;
+  [href, dir, file] = path.match("/(.*)/(.*)");
+  this.dir = dir;
+  this.file = file;
+  [tmp, this.basename, this.extension] = file.match(/(.*)\.(\w+)/);
+  this.testHref = "../../../third_party/WebKit/LayoutTests" + href.replace(/\/virtual\/[^\/]*/, "");
+}
+
+PathParser.prototype = {
+  resultLink: function(resultName) {
+    return this.dir + "/" + this.basename + resultName;
+  }
+}
+
+let Printers = {
+
+  getDefaultPrinter: () => {
+    switch(document.querySelector("#report_format").value) {
+      case "expectation":
+        return Printers.printExpectation;
+      case "plain":
+        return Printers.printPlainTest;
+      default:
+        console.error("Unknown printer type");
+    }
+  },
+
+  printPlainTest: (test, path, traversal) => {
+    let pathParser = new PathParser(path);
+    html = " " + pathParser.dir + "/"
+      + "<a target='test' tabindex='-1' href='" + pathParser.testHref + "'>"
+      + pathParser.file + "</a>";
+    html = "<div class='expect' tabindex='0' data-id='"+ test.expect_id +"'><div class='details'></div>" + html + "</div>";
+    traversal.html.push(html);
+  },
+
+  printExpectation: (test, path, traversal) => {
+    // TestExpectations file format is documented at:
+    // https://chromium.googlesource.com/chromium/src/+/master/docs/testing/layout_test_expectations.md
+
+    let pathParser = new PathParser(path);
+    // Print directory header if this test's directory is different from the last.
+    if (pathParser.dir != traversal.lastDir) {
+      traversal.html.push("<br>");
+      traversal.html.push("<div class='h-expect'>### " + pathParser.dir + "</div>");
+      traversal.lastDir = pathParser.dir;
+    }
+
+    let status = "";
+    let actual = test.actual;
+    let expected = test.expected;
+    if (actual == "TEXT" || actual == "IMAGE" || actual == "IMAGE+TEXT")
+      actual = "FAIL";
+    if (actual == "SKIP" && expected.indexOf("WONTFIX") != -1)
+      actual = "WONTFIX";
+    if (expected.indexOf(actual) == -1)
+      expected = actual;
+    if (expected.indexOf(" ") != -1) {  // make actual result bold
+      var regex = new RegExp("(" + actual + ")")
+      var substitute = "<b>$&</b>";
+      expected = expected.replace(regex, substitute);
+    }
+    expected = expected
+      .replace("CRASH", "Crash")
+      .replace("MISSING", "Skip")
+      .replace("PASS", "Pass")
+      .replace("FAIL", "Failure")
+      .replace("SKIP", "Skip")
+      .replace("TIMEOUT", "Timeout")
+      .replace("SLOW", "Slow")
+      .replace("WONTFIX", "Wontfix")
+      .replace("NEEDSMANUALREBASELINE", "NeedsManualRebaseline");
+    status = expected;
+    let bug = test.actual == "PASS" ? "" : "<span class='warn'>NEEDBUG</span>";
+    if ("bugs" in test && test.bugs.length > 0) {
+      bug = test.bugs.join(" ");
+      bug = "<a target='crbug' tabindex='-1' href='https://" + test.bugs[0] + "'>" + bug + "</a>";
+    }
+    html = "";
+    html += bug;
+    html += " " + pathParser.dir + "/"
+      + "<a target='test' tabindex='-1' href='" + pathParser.testHref + "'>"
+      + pathParser.file + "</a>";
+    html += " [ " + status + " ]";
+    html = "<div class='expect' tabindex='0' data-id='"+ test.expect_id +"'><div class='details'></div>" + html + "</div>";
+    traversal.html.push(html);
+  }
+}
+
+var lastReport;
+function generateReport(name, filter, report) {
+  report = report || Printers.getDefaultPrinter();
+  filter = filter || lastReport.filter;
+  name = name || lastReport.name;
+  lastReport = { name: name, filter: filter };
+  document.querySelector("#report").innerHTML = "";
+  document.querySelector("#report_title").innerHTML = name;
+
+  setTimeout( _ => {
+    var t = new Traversal(fullResults.tests);
+    t.traverse(filter, report);
+    document.querySelector("#report").innerHTML = t.html.join("\n");
+  }, 0);
+}
+
+function containsPass(str) {
+  return ["PASS", "SLOW"].some( v => str.indexOf(v) != -1);
+}
+
+function containsNoPass(str) {
+  return ["NEEDSMANUALREBASELINE", "WONTFIX", "FAIL", "SKIP", "CRASH"].some(
+    v => str.indexOf(v) != -1);
+}
+
+let Filters = {
+  unexpectedPass: test => {
+    return !containsPass(test.expected) && containsPass(test.actual);
+  },
+  unexpectedFailure: test => {
+    if (containsPass(test.actual))
+      return false;
+    if (test.expected.match(/NEEDSMANUALREBASELINE|WONTFIX/))
+      return false;
+    switch (test.actual) {
+      case "SKIP":
+      case "CRASH":
+      case "TIMEOUT":
+        if (test.expected.indexOf(test.actual) != -1)
+          return false;
+        break;
+      case "TEXT":
+      case "IMAGE":
+      case "IMAGE+TEXT":
+        if (containsNoPass(test.expected))
+          return false;
+        break;
+      case "MISSING":
+        return false;
+      default:
+        console.error("Unexpected test result", test.actual);
+      }
+    return true;
+  },
+  notpass: test => test.actual != "PASS",
+  actual: tag => {
+    return function(test) {
+      return test.actual == tag;
+    }
+  },
+  wontfix: test => test.expected == 'WONTFIX',
+  all: _ => true
+}
+
+function getResultLinks(test) {
+  let links = [];
+  let pathParser = new PathParser(test.expect_path);
+  links.push({text: test.actual});
+  switch(test.actual) {
+    case "PASS":
+    case "SLOW":
+      if (!test.has_stderr)
+        links.push({text: "Test passed without errors"});
+      break;
+    case "SKIP":
+      links.push({text: "Test did not run."});
+      break;
+    case "CRASH":
+      links.push({text: "crash log", link: pathParser.resultLink("-crash-log.txt")});
+      break;
+    case "TIMEOUT":
+      links.push({text: "Test timed out. "
+        + ("time" in test ? `(${test.time}s)` : "")});
+      break;
+    case "TEXT":
+      links.push({text: "actual text", link: pathParser.resultLink('-actual.txt')});
+      if (!test.is_testharness_test) {
+        links.push({text: "expected text", link: pathParser.resultLink('-expected.txt')});
+        links.push({text: "diff", link: pathParser.resultLink('-diff.txt')});
+      }
+      break;
+    case "IMAGE":
+      links.push({text: "actual image", link: pathParser.resultLink("-actual.png")});
+      links.push({text: "expected image ", link: pathParser.resultLink("-expected.png")});
+      links.push({text: "diff", link: pathParser.resultLink("-diff.png")});
+      break;
+    case "IMAGE+TEXT":
+      links.push({text: "actual image", link: pathParser.resultLink("-actual.png")});
+      links.push({text: "expected image ", link: pathParser.resultLink("-expected.png")});
+      links.push({text: "diff", link: pathParser.resultLink("-diff.png")});
+      links.push({text: "actual text", link: pathParser.resultLink('-actual.txt')});
+      if (!test.is_testharness_test) {
+        links.push({text: "expected text", link: pathParser.resultLink('-expected.txt')});
+        links.push({text: "diff", link: pathParser.resultLink('-diff.txt')});
+      }
+      break;
+    case "MISSING":
+      links.push({text: "Test is missing."});
+      break;
+    default:
+      console.error("unexpected actual", test.actual);
+  }
+  if (test.has_stderr) {
+    links.push({text: "stderr", link: pathParser.resultLink("-stderr.txt")});
+  }
+  return links;
+}
+
+function getResultsDiv(test) {
+  let clone = document.importNode(
+    document.querySelector("#genericResult").content, true);
+  let div = clone.children[0];
+  // Initialize the results
+  let menu = div.querySelector(".result-menu");
+  menu.innerHTML = "";
+  for (link of getResultLinks(test)) {
+    let li = document.createElement("li");
+    if (link.link) {
+      let anchor = document.createElement("a");
+      anchor.setAttribute("onclick", "return loadResult(this)");
+      anchor.setAttribute("href", link.link || "");
+      anchor.setAttribute("onfocus", "return loadResult(this)");
+      anchor.innerText = link.text;
+      li.appendChild(anchor);
+    }
+    else {
+      li.innerText = link.text;
+    }
+    menu.appendChild(li);
+  }
+  return div;
+}
+
+function closest(el, className) {
+  while (el) {
+    if (el.classList.contains(className))
+      return el;
+    else
+      el = el.parentNode;
+  }
+}
+
+function loadResult(anchor) {
+  if (!anchor.getAttribute("href"))
+    return false;
+  let frame = closest(anchor, "result-frame");
+  let output = frame.querySelector(".result-output");
+  let iframe = output.querySelector("iframe");
+  if (!iframe) {
+    iframe = document.createElement("iframe");
+    output.appendChild(iframe);
+  }
+  iframe.src = anchor.href;
+  iframe.setAttribute("tabindex", -1);
+  return false;
+}
+
+function showResults(expectation, doNotScroll) {
+  let details = expectation.querySelector(".details");
+  if (details.classList.contains("open"))
+    return;
+  details.classList.add("open");
+  let testId = parseInt(expectation.getAttribute("data-id"));
+  let test;
+  (new Traversal(fullResults.tests)).traverse(function(thisTest) {
+    if (thisTest.expect_id == testId)
+      test = thisTest;
+      return false;
+  });
+  if (!test)
+    console.error("could not find test by id");
+  let results = getResultsDiv(test);
+  results.classList.add("results");
+  expectation.parentNode.insertBefore(results, expectation.nextSibling);
+  let firstLink = results.querySelector(".result-menu a");
+  if (firstLink) {
+    firstLink.click();
+  }
+  if (doNotScroll) {
+    return;
+  }
+  // Scroll into view
+  let bottomDelta = results.offsetTop + results.offsetHeight - document.documentElement.clientHeight - window.scrollY + 48;
+  if (bottomDelta > 0)
+    window.scrollBy(0, bottomDelta);
+  let topDelta = results.offsetTop - document.documentElement.clientHeight - window.scrollY - 24;
+  if (topDelta > 0)
+    window.scrollBy(0, topDelta);
+}
+
+function hideResults(expectation) {
+  let details = expectation.querySelector(".details");
+  if (!details.classList.contains("open"))
+    return;
+  expectation.querySelector(".details").classList.remove("open");
+  expectation.nextSibling.remove();
+}
+
+function toggleResults(expectation, event) {
+  let applyToAll = event && event.altKey;
+  let closeOthers = !applyToAll && event && !event.shiftKey;
+  let details = expectation.querySelector(".details");
+  let isOpen = details.classList.contains("open");
+  if (applyToAll) {
+    let allExpectations = Array.from(document.querySelectorAll(".expect"));
+    if (allExpectations.length > 100) {
+      console.error("Too many details to be shown at once");
+    } else {
+      for (e of allExpectations)
+        if (e != expectation)
+          isOpen ? hideResults(e) : showResults(e, true);
+    }
+  }
+  if (closeOthers) {
+    for (el of Array.from(document.querySelectorAll(".details.open")))
+      hideResults(el.parentNode);
+  }
+  if (isOpen) {
+    hideResults(expectation);
+  }
+  else {
+    showResults(expectation);
+  }
+}
+
+function toggleVisibility(id) {
+  document.querySelector("#" + id).classList.toggle("hidden");
+}
+
+function initPage() {
+  let t = new Traversal(fullResults.tests);
+  // Add ids and paths to all the tests
+  let nextId = 1;
+  t.traverse(
+    test => true,
+    (test, path) => {
+      test.expect_id = nextId++;
+      test.expect_path = path;
+    }
+  );
+  printSummary(fullResults);
+  document.addEventListener("click", function(ev) {
+    if (ev.target.classList.contains("expect")) {
+      toggleResults(ev.target, ev);
+      ev.preventDefault();
+      ev.stopPropagation();
+    }
+  });
+  document.addEventListener('keydown', ev => {
+    if (ev.key == "Enter" && ev.target.classList.contains("expect"))
+      toggleResults(ev.target, ev);
+  });
+  // Show unexpected failures on startup.
+  document.querySelector("#button_unexpected_fail").click();
+}
+
+function ADD_FULL_RESULTS(results) {
+  fullResults = results;
+  initPage();
+}
+</script>
+<script src="full_results_jsonp.js"></script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector/resources/storage-view-reports-quota-expected.txt b/third_party/WebKit/LayoutTests/http/tests/inspector/resources/storage-view-reports-quota-expected.txt
deleted file mode 100644
index a025b6fe..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/inspector/resources/storage-view-reports-quota-expected.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-CONSOLE MESSAGE: line 112: InspectorTest.IndexedDB_callback1
-CONSOLE MESSAGE: line 112: InspectorTest.IndexedDB_callback2
-CONSOLE MESSAGE: line 112: InspectorTest.IndexedDB_callback3
-Tests quota reporting.
-
-Tree element found: true
-Clear storage view is visible: true
-0 B storage quota used out of -
-
-Running: Now with data
-9.5 MB storage quota used out of -
-
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector/resources/storage-view-reports-quota.html b/third_party/WebKit/LayoutTests/http/tests/inspector/resources/storage-view-reports-quota.html
deleted file mode 100644
index adcacf5..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/inspector/resources/storage-view-reports-quota.html
+++ /dev/null
@@ -1,67 +0,0 @@
-<html>
-<head>
-<script src="../inspector-test.js"></script>
-<script src="../resources-test.js"></script>
-<script src="../console-test.js"></script>
-<script src="../indexeddb/indexeddb-test.js"></script>
-<script>
-async function test() {
-  var updateListener = null;
-
-  async function writeArray() {
-    var array = [];
-    for (var i = 0; i < 5000000; i++)
-      array.push(i % 10);
-    var mainFrameId = InspectorTest.resourceTreeModel.mainFrame.id;
-    await new Promise(resolve => InspectorTest.createDatabase(mainFrameId, 'Database1', resolve));
-    await new Promise(resolve => InspectorTest.createObjectStore(mainFrameId, 'Database1', 'Store1', 'id', true, resolve));
-    await new Promise(resolve => InspectorTest.addIDBValue(mainFrameId, 'Database1', 'Store1', {key: 1, value: array}, '', resolve));
-  }
-
-  async function dumpWhenMatches(view, predicate) {
-    await new Promise(resolve => {
-      function sniffer(data) {
-        if (data && (!predicate || predicate(data)))
-          resolve();
-        else
-          InspectorTest.addSniffer(clearStorageView, '_updateQuotaDisplay', sniffer);
-      }
-      sniffer(null);
-    });
-    // Quota will vary between setups, rather strip it altogether
-    var clean = view._quotaRow.innerHTML.replace(/\&nbsp;/g, ' ');
-    var quotaStripped = clean.replace(/(.*) \d+ .?B([^\d]*)/, '$1 -$2');
-    InspectorTest.addResult(quotaStripped);
-  }
-  try {
-
-  UI.viewManager.showView('resources');
-
-  var parent = UI.panels.resources._sidebar._applicationTreeElement;
-  var clearStorageElement = parent.children().find(child => child.title === 'Clear storage');
-
-  InspectorTest.addResult('Tree element found: ' + !!clearStorageElement);
-  clearStorageElement.select();
-
-  var clearStorageView = UI.panels.resources.visibleView;
-  InspectorTest.addResult("Clear storage view is visible: " + (clearStorageView instanceof Resources.ClearStorageView));
-
-  await dumpWhenMatches(clearStorageView);
-
-  InspectorTest.markStep('Now with data');
-
-  await writeArray();
-  await dumpWhenMatches(clearStorageView, data => data.usage > 5000000);
-
-} catch (e) {
-  InspectorTest.addResult(e);
-}
-
-  InspectorTest.completeTest();
-}
-</script>
-</head>
-<body onload="runTest()">
-  <p>Tests quota reporting.</p>
-</body>
-</html>
diff --git a/third_party/WebKit/LayoutTests/http/tests/webaudio/autoplay-crossorigin-expected.txt b/third_party/WebKit/LayoutTests/http/tests/webaudio/autoplay-crossorigin-expected.txt
index d6b55d0..e1a131d 100644
--- a/third_party/WebKit/LayoutTests/http/tests/webaudio/autoplay-crossorigin-expected.txt
+++ b/third_party/WebKit/LayoutTests/http/tests/webaudio/autoplay-crossorigin-expected.txt
@@ -1,7 +1,7 @@
 CONSOLE WARNING: line 13: An AudioContext in a cross origin iframe must be created or resumed from a user gesture to enable audio output.
 CONSOLE WARNING: line 23: An AudioContext in a cross origin iframe must be created or resumed from a user gesture to enable audio output.
 CONSOLE WARNING: line 36: An AudioContext in a cross origin iframe must be created or resumed from a user gesture to enable audio output.
-CONSOLE ERROR: line 2535: Uncaught Error: assert_equals: stateAfterClick expected "running" but got "suspended"
+CONSOLE ERROR: line 2539: Uncaught Error: assert_equals: stateAfterClick expected "running" but got "suspended"
 This is a testharness.js-based test.
 Harness Error. harness_status.status = 1 , harness_status.message = Uncaught Error: assert_equals: stateAfterClick expected "running" but got "suspended"
 PASS Verify that autoplaying Web Audio from a cross origin iframe is blocked by mediaPlaybackRequiresUserGesture 
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/color-profile-background-image-space-expected.png b/third_party/WebKit/LayoutTests/images/color-profile-background-image-space-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/color-profile-background-image-space-expected.png
rename to third_party/WebKit/LayoutTests/images/color-profile-background-image-space-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/images/color-profile-svg-expected.png b/third_party/WebKit/LayoutTests/images/color-profile-svg-expected.png
similarity index 100%
rename from third_party/WebKit/LayoutTests/platform/win/images/color-profile-svg-expected.png
rename to third_party/WebKit/LayoutTests/images/color-profile-svg-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/mojo/bind-interface.html b/third_party/WebKit/LayoutTests/mojo/bind-interface.html
new file mode 100644
index 0000000..eaa08c3
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/mojo/bind-interface.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
+<script src="file:///gen/content/test/data/mojo_layouttest_test.mojom.js"></script>
+<script>
+
+promise_test(() => {
+  let helper = new content.mojom.MojoLayoutTestHelperPtr;
+  Mojo.bindInterface(content.mojom.MojoLayoutTestHelper.name,
+                     mojo.makeRequest(helper).handle);
+
+  const kTestMessage = "hello world.";
+  const kExpectedReply = ".dlrow olleh";
+  return helper.reverse(kTestMessage).then(reply => {
+    assert_equals(reply.reversed, kExpectedReply);
+  });
+}, "can bind interfaces");
+
+promise_test(() => {
+  let helper = new content.mojom.MojoLayoutTestHelperPtr;
+  Mojo.bindInterface("totally not a valid interface name",
+                     mojo.makeRequest(helper).handle);
+  return helper.reverse("doesn't matter").then(
+      reply => assert_unreached("request should not succeed"),
+      e => {});
+}, "bindInterface failure closes the request pipe");
+
+promise_test(() => {
+  const kTestReply = "hehe got ya";
+
+  // An impl of the test interface which replies to reverse() with a fixed
+  // message rather than the normally expected value.
+  class TestHelperImpl {
+    constructor() {
+      this.binding_ =
+          new mojo.Binding(content.mojom.MojoLayoutTestHelper, this);
+    }
+    bindRequest(request) { this.binding_.bind(request); }
+    reverse(message) {
+      return Promise.resolve({ reversed: kTestReply });
+    }
+  }
+
+  let helperImpl = new TestHelperImpl;
+  let interceptor =
+      new MojoInterfaceInterceptor(content.mojom.MojoLayoutTestHelper.name);
+  interceptor.oninterfacerequest = e => {
+    helperImpl.bindRequest(e.handle);
+  };
+  interceptor.start();
+
+  let helper = new content.mojom.MojoLayoutTestHelperPtr;
+  Mojo.bindInterface(content.mojom.MojoLayoutTestHelper.name,
+                     mojo.makeRequest(helper).handle);
+  interceptor.stop();
+
+  return helper.reverse("doesn't matter").then(reply => {
+    assert_equals(reply.reversed, kTestReply);
+  });
+}, "can intercept calls to bindInterface");
+
+promise_test(() => {
+  const kTestInterfaceName = "foo::mojom::Ba1r";
+  let a = new MojoInterfaceInterceptor(kTestInterfaceName);
+  let b = new MojoInterfaceInterceptor(kTestInterfaceName);
+  a.oninterfacerequest = () => {};
+  b.oninterfacerequest = () => {};
+  a.start();
+  try {
+    b.start();
+  } catch (e) {
+    return Promise.resolve();
+  } finally {
+    a.stop();
+  }
+  return Promise.reject();
+}, "interface interceptors are mutually exclusive");
+
+test(() => {
+  const kTestInterfaceName = "foo::mojom::Bar";
+  let interceptedHandle = null;
+
+  let interceptor = new MojoInterfaceInterceptor(kTestInterfaceName);
+  interceptor.oninterfacerequest = e => { interceptedHandle = e.handle; };
+  interceptor.start();
+
+  let {handle0, handle1} = Mojo.createMessagePipe();
+  Mojo.bindInterface(kTestInterfaceName, handle0);
+  interceptor.stop();
+
+  assert_true(interceptedHandle instanceof MojoHandle);
+  interceptedHandle.close();
+  interceptedHandle = null;
+
+  Mojo.bindInterface(kTestInterfaceName, handle1);
+  assert_equals(interceptedHandle, null);
+  handle1.close();
+
+  interceptor = new MojoInterfaceInterceptor(kTestInterfaceName);
+  interceptor.oninterfacerequest = e => {};
+  interceptor.start();
+  interceptor.stop();
+}, "interceptors cancel properly");
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
index da8ce41..45c260bd 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/gpu-rasterization/images/color-profile-group-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/gpu-rasterization/images/color-profile-group-expected.png
index ffd531b..8bcbcc1 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/gpu-rasterization/images/color-profile-group-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/gpu-rasterization/images/color-profile-group-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/gpu-rasterization/images/color-profile-layer-filter-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/gpu-rasterization/images/color-profile-layer-filter-expected.png
index 6d681b7..52b1f1e 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/gpu-rasterization/images/color-profile-layer-filter-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/gpu-rasterization/images/color-profile-layer-filter-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/gpu-rasterization/images/color-profile-svg-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/gpu-rasterization/images/color-profile-svg-expected.png
index 0e3dbec..13a65a2 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/gpu-rasterization/images/color-profile-svg-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/gpu-rasterization/images/color-profile-svg-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/gpu-rasterization/images/color-profile-svg-fill-text-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/gpu-rasterization/images/color-profile-svg-fill-text-expected.png
index 3d66609a..5672dcf 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/virtual/gpu-rasterization/images/color-profile-svg-fill-text-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/virtual/gpu-rasterization/images/color-profile-svg-fill-text-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/images/color-profile-svg-fill-text-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/images/color-profile-svg-fill-text-expected.png
index 4b5a3f7..eb2d2f3 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/images/color-profile-svg-fill-text-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/images/color-profile-svg-fill-text-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/virtual/gpu-rasterization/images/color-profile-group-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/virtual/gpu-rasterization/images/color-profile-group-expected.png
new file mode 100644
index 0000000..9a40d8e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/virtual/gpu-rasterization/images/color-profile-group-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/virtual/gpu-rasterization/images/color-profile-svg-fill-text-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/virtual/gpu-rasterization/images/color-profile-svg-fill-text-expected.png
index d6bd48a..31e275b17 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/virtual/gpu-rasterization/images/color-profile-svg-fill-text-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/virtual/gpu-rasterization/images/color-profile-svg-fill-text-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.11/virtual/gpu-rasterization/images/color-profile-clip-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.11/virtual/gpu-rasterization/images/color-profile-clip-expected.png
deleted file mode 100644
index ec168d6..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.11/virtual/gpu-rasterization/images/color-profile-clip-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.11/virtual/gpu-rasterization/images/color-profile-group-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.11/virtual/gpu-rasterization/images/color-profile-group-expected.png
new file mode 100644
index 0000000..7787526
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.11/virtual/gpu-rasterization/images/color-profile-group-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/external/wpt/payment-request/payment-request-show-method.https-expected.txt b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/external/wpt/payment-request/payment-request-show-method.https-expected.txt
new file mode 100644
index 0000000..e97e7f6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/external/wpt/payment-request/payment-request-show-method.https-expected.txt
@@ -0,0 +1,6 @@
+This is a testharness.js-based test.
+Harness Error. harness_status.status = 1 , harness_status.message = Request failed
+FAIL Throws if the promise [[state]] is not "created" promise_test: Unhandled rejection with value: object "UnknownError: Request failed"
+FAIL If the user agent's "payment request is showing" boolean is true, then return a promise rejected with an "AbortError" DOMException. assert_throws: function "function () { throw e }" threw object "UnknownError: Request failed" that is not a DOMException AbortError: property "code" is equal to 0, expected 20
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/images/color-profile-svg-fill-text-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/images/color-profile-svg-fill-text-expected.png
index f990132b..8b3c803 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/images/color-profile-svg-fill-text-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/images/color-profile-svg-fill-text-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/virtual/gpu-rasterization/images/color-profile-svg-fill-text-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/virtual/gpu-rasterization/images/color-profile-svg-fill-text-expected.png
index 5828672..5d18dabf 100644
--- a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/virtual/gpu-rasterization/images/color-profile-svg-fill-text-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/virtual/gpu-rasterization/images/color-profile-svg-fill-text-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-clip-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-clip-expected.png
deleted file mode 100644
index ec168d6..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-clip-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-group-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-group-expected.png
new file mode 100644
index 0000000..7787526
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-group-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-layer-filter-expected.png b/third_party/WebKit/LayoutTests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-layer-filter-expected.png
new file mode 100644
index 0000000..3732169
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/mac-retina/virtual/gpu-rasterization/images/color-profile-layer-filter-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-background-image-cross-fade-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-background-image-cross-fade-expected.png
index 099eeb2..62c0608 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-background-image-cross-fade-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-background-image-cross-fade-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-background-image-space-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-background-image-space-expected.png
deleted file mode 100644
index 1648363..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-background-image-space-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-group-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-group-expected.png
index 1dd52642..1f81b383 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-group-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-group-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-layer-filter-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-layer-filter-expected.png
index 2c585435..e575d50 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-layer-filter-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-layer-filter-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-svg-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-svg-expected.png
deleted file mode 100644
index eb0eda9..0000000
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-svg-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-svg-fill-text-expected.png b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-svg-fill-text-expected.png
index 09914eb..90d5b87b 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-svg-fill-text-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/images/color-profile-svg-fill-text-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png b/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
index b4456700..45c260bd 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-clip-expected.png b/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-clip-expected.png
index b0b6200..ec168d6 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-clip-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-clip-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-group-expected.png b/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-group-expected.png
index 7787526..9a40d8e 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-group-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-group-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-layer-filter-expected.png b/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-layer-filter-expected.png
index 3732169..52b1f1e 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-layer-filter-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-layer-filter-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-svg-expected.png b/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-svg-expected.png
index ce0ba8e..13a65a2 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-svg-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-svg-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-svg-fill-text-expected.png b/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-svg-fill-text-expected.png
index 5bc8e88..c4d1829 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-svg-fill-text-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/virtual/gpu-rasterization/images/color-profile-svg-fill-text-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png b/third_party/WebKit/LayoutTests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
index f09159e..e7469c4 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/gpu-rasterization/images/color-profile-background-image-space-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/gpu-rasterization/images/color-profile-layer-filter-expected.png b/third_party/WebKit/LayoutTests/platform/win/virtual/gpu-rasterization/images/color-profile-layer-filter-expected.png
index 4a13efc..3bb23a9 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/gpu-rasterization/images/color-profile-layer-filter-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/gpu-rasterization/images/color-profile-layer-filter-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/gpu-rasterization/images/color-profile-svg-expected.png b/third_party/WebKit/LayoutTests/platform/win/virtual/gpu-rasterization/images/color-profile-svg-expected.png
index 1287cea..7afe6939 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/gpu-rasterization/images/color-profile-svg-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/gpu-rasterization/images/color-profile-svg-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/gpu-rasterization/images/color-profile-svg-fill-text-expected.png b/third_party/WebKit/LayoutTests/platform/win/virtual/gpu-rasterization/images/color-profile-svg-fill-text-expected.png
index 12bb3a6..4b3375a9 100644
--- a/third_party/WebKit/LayoutTests/platform/win/virtual/gpu-rasterization/images/color-profile-svg-fill-text-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/virtual/gpu-rasterization/images/color-profile-svg-fill-text-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win7/virtual/gpu-rasterization/images/color-profile-group-expected.png b/third_party/WebKit/LayoutTests/platform/win7/virtual/gpu-rasterization/images/color-profile-group-expected.png
new file mode 100644
index 0000000..dc277d02
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/platform/win7/virtual/gpu-rasterization/images/color-profile-group-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/resources/bluetooth/bluetooth-helpers.js b/third_party/WebKit/LayoutTests/resources/bluetooth/bluetooth-helpers.js
index 862950d..aae8a9b 100644
--- a/third_party/WebKit/LayoutTests/resources/bluetooth/bluetooth-helpers.js
+++ b/third_party/WebKit/LayoutTests/resources/bluetooth/bluetooth-helpers.js
@@ -1,5 +1,11 @@
 'use strict';
 
+// HCI Error Codes. Used for simulateGATT[Dis]ConnectionResponse.
+// For a complete list of possible error codes see
+// BT 4.2 Vol 2 Part D 1.3 List Of Error Codes.
+const HCI_SUCCESS = 0x0000;
+const HCI_CONNECTION_TIMEOUT = 0x0008;
+
 // Bluetooth UUID constants:
 // Services:
 var blocklist_test_service_uuid = "611c954a-263b-4f4a-aab6-01ddb953f985";
@@ -416,6 +422,8 @@
   }];
 }
 
+// Simulates a pre-connected device with |address|, |name| and
+// |knownServiceUUIDs|.
 function setUpPreconnectedDevice({
   address = '00:00:00:00:00:00', name = 'LE Device', knownServiceUUIDs = []}) {
   return navigator.bluetooth.test.simulateCentral({state: 'powered-on'})
@@ -426,6 +434,8 @@
     }));
 }
 
+// Returns an array containing two FakePeripherals corresponding
+// to the simulated devices.
 function setUpHealthThermometerAndHeartRateDevices() {
   return navigator.bluetooth.test.simulateCentral({state: 'powered-on'})
    .then(fake_central => Promise.all([
@@ -440,3 +450,35 @@
        knownServiceUUIDs: ['generic_access', 'heart_rate'],
      })]));
 }
+
+// Returns a BluetoothDevice discovered using |options| and its
+// corresponding FakePeripheral.
+// The simulated device is called 'Health Thermometer' it has two known service
+// UUIDs: 'generic_access' and 'health_thermometer'. The device has been
+// connected to and its services have been discovered.
+// TODO(crbug.com/719816): Add services, characteristics and descriptors,
+// and discover all the attributes.
+function getHealthThermometerDevice(options) {
+  return getDiscoveredHealthThermometerDevice(options)
+    .then(([device, fake_peripheral]) => {
+      return fake_peripheral.setNextGATTConnectionResponse({code: HCI_SUCCESS})
+        .then(() => device.gatt.connect())
+        .then(gatt => [gatt.device, fake_peripheral]);
+    });
+}
+
+// Similar to getHealthThermometerDevice() except the device
+// is not connected and thus its services have not been
+// discovered.
+function getDiscoveredHealthThermometerDevice(
+  options = {filters: [{services: ['health_thermometer']}]}) {
+  return setUpPreconnectedDevice({
+    address: '09:09:09:09:09:09',
+    name: 'Health Thermometer',
+    knownServiceUUIDs: ['generic_access', 'health_thermometer'],
+  })
+  .then(fake_peripheral => {
+    return requestDeviceWithKeyDown(options)
+      .then(device => [device, fake_peripheral]);
+  });
+}
diff --git a/third_party/WebKit/LayoutTests/resources/bluetooth/web-bluetooth-test.js b/third_party/WebKit/LayoutTests/resources/bluetooth/web-bluetooth-test.js
index 450c27d..1f5e29de 100644
--- a/third_party/WebKit/LayoutTests/resources/bluetooth/web-bluetooth-test.js
+++ b/third_party/WebKit/LayoutTests/resources/bluetooth/web-bluetooth-test.js
@@ -139,7 +139,7 @@
 
       let peripheral = this.peripherals_.get(address);
       if (peripheral === undefined) {
-        peripheral = new FakePeripheral(address, this);
+        peripheral = new FakePeripheral(address, this.fake_central_ptr_);
         this.peripherals_.set(address, peripheral);
       }
 
@@ -148,9 +148,22 @@
   }
 
   class FakePeripheral {
-    constructor(address, fake_central) {
+    constructor(address, fake_central_ptr) {
       this.address = address;
-      this.fake_central_ = fake_central;
+      this.fake_central_ptr_ = fake_central_ptr;
+    }
+
+    // Sets the next GATT Connection request response to |code|. |code| could be
+    // an HCI Error Code from BT 4.2 Vol 2 Part D 1.3 List Of Error Codes or a
+    // number outside that range returned by specific platforms e.g. Android
+    // returns 0x101 to signal a GATT failure
+    // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#GATT_FAILURE
+    async setNextGATTConnectionResponse({code}) {
+      let {success} =
+        await this.fake_central_ptr_.setNextGATTConnectionResponse(
+          this.address, code);
+
+      if (success !== true) throw 'Cannot set next response.';
     }
   }
 
diff --git a/third_party/WebKit/LayoutTests/resources/testharness.js b/third_party/WebKit/LayoutTests/resources/testharness.js
index a0ec94d..bb63e02c 100644
--- a/third_party/WebKit/LayoutTests/resources/testharness.js
+++ b/third_party/WebKit/LayoutTests/resources/testharness.js
@@ -591,7 +591,7 @@
 
         /**
          * Returns a Promise that will resolve after the specified event or
-         * series of events has occurred.
+         * series of events has occured.
          */
         this.wait_for = function(types) {
             if (waitingFor) {
@@ -971,7 +971,7 @@
     function assert_approx_equals(actual, expected, epsilon, description)
     {
         /*
-         * Test if two primitive numbers are equal within +/- epsilon
+         * Test if two primitive numbers are equal withing +/- epsilon
          */
         assert(typeof actual === "number",
                "assert_approx_equals", description,
@@ -1371,12 +1371,14 @@
             this._structured_clone = merge({
                 name:String(this.name),
                 properties:merge({}, this.properties),
+                phases:merge({}, this.phases)
             }, Test.statuses);
         }
         this._structured_clone.status = this.status;
         this._structured_clone.message = this.message;
         this._structured_clone.stack = this.stack;
         this._structured_clone.index = this.index;
+        this._structured_clone.phase = this.phase;
         return this._structured_clone;
     };
 
@@ -1547,10 +1549,12 @@
         var clone = {};
         Object.keys(this).forEach(
                 (function(key) {
-                    if (typeof(this[key]) === "object") {
-                        clone[key] = merge({}, this[key]);
+                    var value = this[key];
+
+                    if (typeof value === "object" && value !== null) {
+                        clone[key] = merge({}, value);
                     } else {
-                        clone[key] = this[key];
+                        clone[key] = value;
                     }
                 }).bind(this));
         clone.phases = merge({}, this.phases);
diff --git a/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-expected.txt b/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-expected.txt
index aa614a4..6cd5de5 100644
--- a/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/webexposed/global-interface-listing-expected.txt
@@ -4217,6 +4217,7 @@
     method item
     method namedItem
 interface Mojo
+    static method bindInterface
     static method createDataPipe
     static method createMessagePipe
     static method createSharedBuffer
@@ -4253,6 +4254,17 @@
     method watch
     method writeData
     method writeMessage
+interface MojoInterfaceInterceptor : EventTarget
+    attribute @@toStringTag
+    getter oninterfacerequest
+    method constructor
+    method start
+    method stop
+    setter oninterfacerequest
+interface MojoInterfaceRequestEvent : Event
+    attribute @@toStringTag
+    getter handle
+    method constructor
 interface MojoWatcher
     attribute @@toStringTag
     method cancel
diff --git a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
index bd725eb..c782d58 100644
--- a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
@@ -4217,6 +4217,7 @@
     method item
     method namedItem
 interface Mojo
+    static method bindInterface
     static method createDataPipe
     static method createMessagePipe
     static method createSharedBuffer
@@ -4253,6 +4254,17 @@
     method watch
     method writeData
     method writeMessage
+interface MojoInterfaceInterceptor : EventTarget
+    attribute @@toStringTag
+    getter oninterfacerequest
+    method constructor
+    method start
+    method stop
+    setter oninterfacerequest
+interface MojoInterfaceRequestEvent : Event
+    attribute @@toStringTag
+    getter handle
+    method constructor
 interface MojoWatcher
     attribute @@toStringTag
     method cancel
diff --git a/third_party/WebKit/PerformanceTests/README.md b/third_party/WebKit/PerformanceTests/README.md
index d4b2cd1..87b3316 100644
--- a/third_party/WebKit/PerformanceTests/README.md
+++ b/third_party/WebKit/PerformanceTests/README.md
@@ -101,7 +101,7 @@
         PerfTestRunner.measureValueAsync(PerfTestRunner.now() - startTime);
         PerfTestRunner.addRunTestEndMarker(); // For tracing metrics
     }
-    if (!done) {
+    if (!isDone) {
         PerfTestRunner.addRunTestStartMarker();
         startTime = PerfTestRunner.now();  // For tracing metrics
         // runTest will be invoked after the async operation finish
diff --git a/third_party/WebKit/Source/bindings/core/v8/BUILD.gn b/third_party/WebKit/Source/bindings/core/v8/BUILD.gn
index 6708cbef..8fb8e61 100644
--- a/third_party/WebKit/Source/bindings/core/v8/BUILD.gn
+++ b/third_party/WebKit/Source/bindings/core/v8/BUILD.gn
@@ -159,6 +159,8 @@
   "$blink_core_output_dir/mojo/MojoWriteDataOptions.h",
   "$blink_core_output_dir/mojo/MojoWriteDataResult.cpp",
   "$blink_core_output_dir/mojo/MojoWriteDataResult.h",
+  "$blink_core_output_dir/mojo/testing/MojoInterfaceRequestEventInit.cpp",
+  "$blink_core_output_dir/mojo/testing/MojoInterfaceRequestEventInit.h",
   "$blink_core_output_dir/offscreencanvas/ImageEncodeOptions.cpp",
   "$blink_core_output_dir/offscreencanvas/ImageEncodeOptions.h",
   "$blink_core_output_dir/page/scrolling/ScrollStateInit.cpp",
diff --git a/third_party/WebKit/Source/bindings/modules/v8/generated.gni b/third_party/WebKit/Source/bindings/modules/v8/generated.gni
index 5db7f5f..85b447b 100644
--- a/third_party/WebKit/Source/bindings/modules/v8/generated.gni
+++ b/third_party/WebKit/Source/bindings/modules/v8/generated.gni
@@ -32,6 +32,10 @@
   "$bindings_modules_v8_output_dir/CanvasImageSource.h",
   "$bindings_modules_v8_output_dir/ClientOrServiceWorkerOrMessagePort.cpp",
   "$bindings_modules_v8_output_dir/ClientOrServiceWorkerOrMessagePort.h",
+  "$bindings_modules_v8_output_dir/DecodeErrorCallback.cpp",
+  "$bindings_modules_v8_output_dir/DecodeErrorCallback.h",
+  "$bindings_modules_v8_output_dir/DecodeSuccessCallback.cpp",
+  "$bindings_modules_v8_output_dir/DecodeSuccessCallback.h",
   "$bindings_modules_v8_output_dir/DictionaryOrString.cpp",
   "$bindings_modules_v8_output_dir/DictionaryOrString.h",
   "$bindings_modules_v8_output_dir/DoubleOrConstrainDoubleRange.cpp",
diff --git a/third_party/WebKit/Source/core/BUILD.gn b/third_party/WebKit/Source/core/BUILD.gn
index c40adf5..9e24917 100644
--- a/third_party/WebKit/Source/core/BUILD.gn
+++ b/third_party/WebKit/Source/core/BUILD.gn
@@ -103,6 +103,7 @@
 source_set("prerequisites") {
   public_deps = [
     "//gpu/command_buffer/client:gles2_c_lib",
+    "//services/service_manager/public/cpp",
     "//skia",
     "//third_party/WebKit/Source/core/inspector:generated",
     "//third_party/WebKit/Source/core/probe:generated",
@@ -331,6 +332,7 @@
     "events/UIEvent.idl",
     "events/WheelEvent.idl",
     "html/track/TrackEvent.idl",
+    "mojo/testing/MojoInterfaceRequestEvent.idl",
   ]
   output_file = "core/EventInterfaces.json5"
 }
diff --git a/third_party/WebKit/Source/core/DEPS b/third_party/WebKit/Source/core/DEPS
index b807314..bb64ef360 100644
--- a/third_party/WebKit/Source/core/DEPS
+++ b/third_party/WebKit/Source/core/DEPS
@@ -11,6 +11,7 @@
     "+platform",
     "+public/platform",
     "+public/web",
+    "+services/service_manager/public/cpp",
     "+third_party/skia/include",
     "-web",
 ]
diff --git a/third_party/WebKit/Source/core/core_idl_files.gni b/third_party/WebKit/Source/core/core_idl_files.gni
index 728f46ec..26c9fb8f6 100644
--- a/third_party/WebKit/Source/core/core_idl_files.gni
+++ b/third_party/WebKit/Source/core/core_idl_files.gni
@@ -286,6 +286,8 @@
                                  "mojo/Mojo.idl",
                                  "mojo/MojoHandle.idl",
                                  "mojo/MojoWatcher.idl",
+                                 "mojo/testing/MojoInterfaceInterceptor.idl",
+                                 "mojo/testing/MojoInterfaceRequestEvent.idl",
                                  "page/PagePopupController.idl",
                                  "page/scrolling/ScrollState.idl",
                                  "page/scrolling/ScrollStateCallback.idl",
@@ -578,6 +580,7 @@
                     "mojo/MojoReadMessageResult.idl",
                     "mojo/MojoWriteDataOptions.idl",
                     "mojo/MojoWriteDataResult.idl",
+                    "mojo/testing/MojoInterfaceRequestEventInit.idl",
                     "offscreencanvas/ImageEncodeOptions.idl",
                     "page/scrolling/ScrollStateInit.idl",
                     "timing/PerformanceObserverInit.idl",
diff --git a/third_party/WebKit/Source/core/editing/BUILD.gn b/third_party/WebKit/Source/core/editing/BUILD.gn
index 77592b6..951463db 100644
--- a/third_party/WebKit/Source/core/editing/BUILD.gn
+++ b/third_party/WebKit/Source/core/editing/BUILD.gn
@@ -83,6 +83,7 @@
     "VisibleUnits.cpp",
     "VisibleUnits.h",
     "VisibleUnitsLine.cpp",
+    "VisibleUnitsParagraph.cpp",
     "WritingDirection.h",
     "commands/AppendNodeCommand.cpp",
     "commands/AppendNodeCommand.h",
diff --git a/third_party/WebKit/Source/core/editing/VisibleUnits.cpp b/third_party/WebKit/Source/core/editing/VisibleUnits.cpp
index d4e28c9..9c1e4f9 100644
--- a/third_party/WebKit/Source/core/editing/VisibleUnits.cpp
+++ b/third_party/WebKit/Source/core/editing/VisibleUnits.cpp
@@ -816,363 +816,6 @@
       range.EndPosition()));
 }
 
-static bool NodeIsUserSelectAll(const Node* node) {
-  return node && node->GetLayoutObject() &&
-         node->GetLayoutObject()->Style()->UserSelect() == EUserSelect::kAll;
-}
-
-template <typename Strategy>
-PositionTemplate<Strategy> StartOfParagraphAlgorithm(
-    const PositionTemplate<Strategy>& position,
-    EditingBoundaryCrossingRule boundary_crossing_rule) {
-  Node* const start_node = position.AnchorNode();
-
-  if (!start_node)
-    return PositionTemplate<Strategy>();
-
-  if (IsRenderedAsNonInlineTableImageOrHR(start_node))
-    return PositionTemplate<Strategy>::BeforeNode(start_node);
-
-  Element* const start_block = EnclosingBlock(
-      PositionTemplate<Strategy>::FirstPositionInOrBeforeNode(start_node),
-      kCannotCrossEditingBoundary);
-  ContainerNode* const highest_root = HighestEditableRoot(position);
-  const bool start_node_is_editable = HasEditableStyle(*start_node);
-
-  Node* candidate_node = start_node;
-  PositionAnchorType candidate_type = position.AnchorType();
-  int candidate_offset = position.ComputeEditingOffset();
-
-  Node* previous_node_iterator = start_node;
-  while (previous_node_iterator) {
-    if (boundary_crossing_rule == kCannotCrossEditingBoundary &&
-        !NodeIsUserSelectAll(previous_node_iterator) &&
-        HasEditableStyle(*previous_node_iterator) != start_node_is_editable)
-      break;
-    if (boundary_crossing_rule == kCanSkipOverEditingBoundary) {
-      while (previous_node_iterator &&
-             HasEditableStyle(*previous_node_iterator) !=
-                 start_node_is_editable) {
-        previous_node_iterator =
-            Strategy::PreviousPostOrder(*previous_node_iterator, start_block);
-      }
-      if (!previous_node_iterator ||
-          !previous_node_iterator->IsDescendantOf(highest_root))
-        break;
-    }
-
-    const LayoutItem layout_item =
-        LayoutItem(previous_node_iterator->GetLayoutObject());
-    if (layout_item.IsNull()) {
-      previous_node_iterator =
-          Strategy::PreviousPostOrder(*previous_node_iterator, start_block);
-      continue;
-    }
-    const ComputedStyle& style = layout_item.StyleRef();
-    if (style.Visibility() != EVisibility::kVisible) {
-      previous_node_iterator =
-          Strategy::PreviousPostOrder(*previous_node_iterator, start_block);
-      continue;
-    }
-
-    if (layout_item.IsBR() || IsEnclosingBlock(previous_node_iterator))
-      break;
-
-    if (layout_item.IsText() &&
-        ToLayoutText(previous_node_iterator->GetLayoutObject())
-            ->ResolvedTextLength()) {
-      SECURITY_DCHECK(previous_node_iterator->IsTextNode());
-      if (style.PreserveNewline()) {
-        LayoutText* text =
-            ToLayoutText(previous_node_iterator->GetLayoutObject());
-        int index = text->TextLength();
-        if (previous_node_iterator == start_node && candidate_offset < index)
-          index = max(0, candidate_offset);
-        while (--index >= 0) {
-          if ((*text)[index] == '\n')
-            return PositionTemplate<Strategy>(ToText(previous_node_iterator),
-                                              index + 1);
-        }
-      }
-      candidate_node = previous_node_iterator;
-      candidate_type = PositionAnchorType::kOffsetInAnchor;
-      candidate_offset = 0;
-      previous_node_iterator =
-          Strategy::PreviousPostOrder(*previous_node_iterator, start_block);
-    } else if (EditingIgnoresContent(*previous_node_iterator) ||
-               IsDisplayInsideTable(previous_node_iterator)) {
-      candidate_node = previous_node_iterator;
-      candidate_type = PositionAnchorType::kBeforeAnchor;
-      previous_node_iterator = previous_node_iterator->previousSibling()
-                                   ? previous_node_iterator->previousSibling()
-                                   : Strategy::PreviousPostOrder(
-                                         *previous_node_iterator, start_block);
-    } else {
-      previous_node_iterator =
-          Strategy::PreviousPostOrder(*previous_node_iterator, start_block);
-    }
-  }
-
-  if (candidate_type == PositionAnchorType::kOffsetInAnchor)
-    return PositionTemplate<Strategy>(candidate_node, candidate_offset);
-
-  return PositionTemplate<Strategy>(candidate_node, candidate_type);
-}
-
-template <typename Strategy>
-VisiblePositionTemplate<Strategy> StartOfParagraphAlgorithm(
-    const VisiblePositionTemplate<Strategy>& visible_position,
-    EditingBoundaryCrossingRule boundary_crossing_rule) {
-  DCHECK(visible_position.IsValid()) << visible_position;
-  return CreateVisiblePosition(StartOfParagraphAlgorithm(
-      visible_position.DeepEquivalent(), boundary_crossing_rule));
-}
-
-VisiblePosition StartOfParagraph(
-    const VisiblePosition& c,
-    EditingBoundaryCrossingRule boundary_crossing_rule) {
-  return StartOfParagraphAlgorithm<EditingStrategy>(c, boundary_crossing_rule);
-}
-
-VisiblePositionInFlatTree StartOfParagraph(
-    const VisiblePositionInFlatTree& c,
-    EditingBoundaryCrossingRule boundary_crossing_rule) {
-  return StartOfParagraphAlgorithm<EditingInFlatTreeStrategy>(
-      c, boundary_crossing_rule);
-}
-
-template <typename Strategy>
-static PositionTemplate<Strategy> EndOfParagraphAlgorithm(
-    const PositionTemplate<Strategy>& position,
-    EditingBoundaryCrossingRule boundary_crossing_rule) {
-  Node* const start_node = position.AnchorNode();
-
-  if (!start_node)
-    return PositionTemplate<Strategy>();
-
-  if (IsRenderedAsNonInlineTableImageOrHR(start_node))
-    return PositionTemplate<Strategy>::AfterNode(start_node);
-
-  Element* const start_block = EnclosingBlock(
-      PositionTemplate<Strategy>::FirstPositionInOrBeforeNode(start_node),
-      kCannotCrossEditingBoundary);
-  ContainerNode* const highest_root = HighestEditableRoot(position);
-  const bool start_node_is_editable = HasEditableStyle(*start_node);
-
-  Node* candidate_node = start_node;
-  PositionAnchorType candidate_type = position.AnchorType();
-  int candidate_offset = position.ComputeEditingOffset();
-
-  Node* next_node_iterator = start_node;
-  while (next_node_iterator) {
-    if (boundary_crossing_rule == kCannotCrossEditingBoundary &&
-        !NodeIsUserSelectAll(next_node_iterator) &&
-        HasEditableStyle(*next_node_iterator) != start_node_is_editable)
-      break;
-    if (boundary_crossing_rule == kCanSkipOverEditingBoundary) {
-      while (next_node_iterator &&
-             HasEditableStyle(*next_node_iterator) != start_node_is_editable)
-        next_node_iterator = Strategy::Next(*next_node_iterator, start_block);
-      if (!next_node_iterator ||
-          !next_node_iterator->IsDescendantOf(highest_root))
-        break;
-    }
-
-    LayoutObject* const layout_object = next_node_iterator->GetLayoutObject();
-    if (!layout_object) {
-      next_node_iterator = Strategy::Next(*next_node_iterator, start_block);
-      continue;
-    }
-    const ComputedStyle& style = layout_object->StyleRef();
-    if (style.Visibility() != EVisibility::kVisible) {
-      next_node_iterator = Strategy::Next(*next_node_iterator, start_block);
-      continue;
-    }
-
-    if (layout_object->IsBR() || IsEnclosingBlock(next_node_iterator))
-      break;
-
-    // FIXME: We avoid returning a position where the layoutObject can't accept
-    // the caret.
-    if (layout_object->IsText() &&
-        ToLayoutText(layout_object)->ResolvedTextLength()) {
-      SECURITY_DCHECK(next_node_iterator->IsTextNode());
-      LayoutText* const text = ToLayoutText(layout_object);
-      if (style.PreserveNewline()) {
-        const int length = ToLayoutText(layout_object)->TextLength();
-        for (int i = (next_node_iterator == start_node ? candidate_offset : 0);
-             i < length; ++i) {
-          if ((*text)[i] == '\n')
-            return PositionTemplate<Strategy>(ToText(next_node_iterator),
-                                              i + text->TextStartOffset());
-        }
-      }
-
-      candidate_node = next_node_iterator;
-      candidate_type = PositionAnchorType::kOffsetInAnchor;
-      candidate_offset =
-          layout_object->CaretMaxOffset() + text->TextStartOffset();
-      next_node_iterator = Strategy::Next(*next_node_iterator, start_block);
-    } else if (EditingIgnoresContent(*next_node_iterator) ||
-               IsDisplayInsideTable(next_node_iterator)) {
-      candidate_node = next_node_iterator;
-      candidate_type = PositionAnchorType::kAfterAnchor;
-      next_node_iterator =
-          Strategy::NextSkippingChildren(*next_node_iterator, start_block);
-    } else {
-      next_node_iterator = Strategy::Next(*next_node_iterator, start_block);
-    }
-  }
-
-  if (candidate_type == PositionAnchorType::kOffsetInAnchor)
-    return PositionTemplate<Strategy>(candidate_node, candidate_offset);
-
-  return PositionTemplate<Strategy>(candidate_node, candidate_type);
-}
-
-template <typename Strategy>
-static VisiblePositionTemplate<Strategy> EndOfParagraphAlgorithm(
-    const VisiblePositionTemplate<Strategy>& visible_position,
-    EditingBoundaryCrossingRule boundary_crossing_rule) {
-  DCHECK(visible_position.IsValid()) << visible_position;
-  return CreateVisiblePosition(EndOfParagraphAlgorithm(
-      visible_position.DeepEquivalent(), boundary_crossing_rule));
-}
-
-VisiblePosition EndOfParagraph(
-    const VisiblePosition& c,
-    EditingBoundaryCrossingRule boundary_crossing_rule) {
-  return EndOfParagraphAlgorithm<EditingStrategy>(c, boundary_crossing_rule);
-}
-
-VisiblePositionInFlatTree EndOfParagraph(
-    const VisiblePositionInFlatTree& c,
-    EditingBoundaryCrossingRule boundary_crossing_rule) {
-  return EndOfParagraphAlgorithm<EditingInFlatTreeStrategy>(
-      c, boundary_crossing_rule);
-}
-
-// FIXME: isStartOfParagraph(startOfNextParagraph(pos)) is not always true
-VisiblePosition StartOfNextParagraph(const VisiblePosition& visible_position) {
-  DCHECK(visible_position.IsValid()) << visible_position;
-  VisiblePosition paragraph_end(
-      EndOfParagraph(visible_position, kCanSkipOverEditingBoundary));
-  VisiblePosition after_paragraph_end(
-      NextPositionOf(paragraph_end, kCannotCrossEditingBoundary));
-  // The position after the last position in the last cell of a table
-  // is not the start of the next paragraph.
-  if (TableElementJustBefore(after_paragraph_end))
-    return NextPositionOf(after_paragraph_end, kCannotCrossEditingBoundary);
-  return after_paragraph_end;
-}
-
-// FIXME: isStartOfParagraph(startOfNextParagraph(pos)) is not always true
-bool InSameParagraph(const VisiblePosition& a,
-                     const VisiblePosition& b,
-                     EditingBoundaryCrossingRule boundary_crossing_rule) {
-  DCHECK(a.IsValid()) << a;
-  DCHECK(b.IsValid()) << b;
-  return a.IsNotNull() &&
-         StartOfParagraph(a, boundary_crossing_rule).DeepEquivalent() ==
-             StartOfParagraph(b, boundary_crossing_rule).DeepEquivalent();
-}
-
-template <typename Strategy>
-static bool IsStartOfParagraphAlgorithm(
-    const VisiblePositionTemplate<Strategy>& pos,
-    EditingBoundaryCrossingRule boundary_crossing_rule) {
-  DCHECK(pos.IsValid()) << pos;
-  return pos.IsNotNull() &&
-         pos.DeepEquivalent() ==
-             StartOfParagraph(pos, boundary_crossing_rule).DeepEquivalent();
-}
-
-bool IsStartOfParagraph(const VisiblePosition& pos,
-                        EditingBoundaryCrossingRule boundary_crossing_rule) {
-  return IsStartOfParagraphAlgorithm<EditingStrategy>(pos,
-                                                      boundary_crossing_rule);
-}
-
-bool IsStartOfParagraph(const VisiblePositionInFlatTree& pos,
-                        EditingBoundaryCrossingRule boundary_crossing_rule) {
-  return IsStartOfParagraphAlgorithm<EditingInFlatTreeStrategy>(
-      pos, boundary_crossing_rule);
-}
-
-template <typename Strategy>
-static bool IsEndOfParagraphAlgorithm(
-    const VisiblePositionTemplate<Strategy>& pos,
-    EditingBoundaryCrossingRule boundary_crossing_rule) {
-  DCHECK(pos.IsValid()) << pos;
-  return pos.IsNotNull() &&
-         pos.DeepEquivalent() ==
-             EndOfParagraph(pos, boundary_crossing_rule).DeepEquivalent();
-}
-
-bool IsEndOfParagraph(const VisiblePosition& pos,
-                      EditingBoundaryCrossingRule boundary_crossing_rule) {
-  return IsEndOfParagraphAlgorithm<EditingStrategy>(pos,
-                                                    boundary_crossing_rule);
-}
-
-bool IsEndOfParagraph(const VisiblePositionInFlatTree& pos,
-                      EditingBoundaryCrossingRule boundary_crossing_rule) {
-  return IsEndOfParagraphAlgorithm<EditingInFlatTreeStrategy>(
-      pos, boundary_crossing_rule);
-}
-
-VisiblePosition PreviousParagraphPosition(const VisiblePosition& p,
-                                          LayoutUnit x) {
-  DCHECK(p.IsValid()) << p;
-  VisiblePosition pos = p;
-  do {
-    VisiblePosition n = PreviousLinePosition(pos, x);
-    if (n.IsNull() || n.DeepEquivalent() == pos.DeepEquivalent())
-      break;
-    pos = n;
-  } while (InSameParagraph(p, pos));
-  return pos;
-}
-
-VisiblePosition NextParagraphPosition(const VisiblePosition& p, LayoutUnit x) {
-  DCHECK(p.IsValid()) << p;
-  VisiblePosition pos = p;
-  do {
-    VisiblePosition n = NextLinePosition(pos, x);
-    if (n.IsNull() || n.DeepEquivalent() == pos.DeepEquivalent())
-      break;
-    pos = n;
-  } while (InSameParagraph(p, pos));
-  return pos;
-}
-
-EphemeralRange ExpandToParagraphBoundary(const EphemeralRange& range) {
-  const VisiblePosition& start = CreateVisiblePosition(range.StartPosition());
-  DCHECK(start.IsNotNull()) << range.StartPosition();
-  const Position& paragraph_start = StartOfParagraph(start).DeepEquivalent();
-  DCHECK(paragraph_start.IsNotNull()) << range.StartPosition();
-
-  const VisiblePosition& end = CreateVisiblePosition(range.EndPosition());
-  DCHECK(end.IsNotNull()) << range.EndPosition();
-  const Position& paragraph_end = EndOfParagraph(end).DeepEquivalent();
-  DCHECK(paragraph_end.IsNotNull()) << range.EndPosition();
-
-  // TODO(xiaochengh): There are some cases (crbug.com/640112) where we get
-  // |paragraphStart > paragraphEnd|, which is the reason we cannot directly
-  // return |EphemeralRange(paragraphStart, paragraphEnd)|. This is not
-  // desired, though. We should do more investigation to ensure that why
-  // |paragraphStart <= paragraphEnd| is violated.
-  const Position& result_start =
-      paragraph_start.IsNotNull() && paragraph_start <= range.StartPosition()
-          ? paragraph_start
-          : range.StartPosition();
-  const Position& result_end =
-      paragraph_end.IsNotNull() && paragraph_end >= range.EndPosition()
-          ? paragraph_end
-          : range.EndPosition();
-  return EphemeralRange(result_start, result_end);
-}
-
 // ---------
 
 VisiblePosition StartOfBlock(const VisiblePosition& visible_position,
@@ -2036,6 +1679,36 @@
   return last_visible.DeprecatedComputePosition();
 }
 
+// The text continues on the next line only if the last text box is not on this
+// line and none of the boxes on this line have a larger start offset.
+static bool DoesContinueOnNextLine(const LayoutText& text_layout_object,
+                                   InlineBox* box,
+                                   unsigned text_offset) {
+  InlineTextBox* const last_text_box = text_layout_object.LastTextBox();
+  DCHECK_NE(box, last_text_box);
+  for (InlineBox* runner = box->NextLeafChild(); runner;
+       runner = runner->NextLeafChild()) {
+    if (runner == last_text_box)
+      return false;
+    if (LineLayoutAPIShim::LayoutObjectFrom(runner->GetLineLayoutItem()) ==
+            text_layout_object &&
+        ToInlineTextBox(runner)->Start() >= text_offset)
+      return false;
+  }
+
+  for (InlineBox* runner = box->PrevLeafChild(); runner;
+       runner = runner->PrevLeafChild()) {
+    if (runner == last_text_box)
+      return false;
+    if (LineLayoutAPIShim::LayoutObjectFrom(runner->GetLineLayoutItem()) ==
+            text_layout_object &&
+        ToInlineTextBox(runner)->Start() >= text_offset)
+      return false;
+  }
+
+  return true;
+}
+
 // TODO(editing-dev): This function is just moved out from
 // |MostBackwardCaretPosition()|. We should study this function more and
 // name it appropriately. See https://trac.webkit.org/changeset/32438/
@@ -2075,40 +1748,7 @@
     if (box == last_text_box || text_offset != box->Start() + box->Len() + 1)
       continue;
 
-    // TODO(yosin): We should move below code fragment into
-    // |DoesContinueOnNextLine()|. Note: |MostForwardCaretPosition()| has
-    // same code fragment except for comparison on |text_offset|.
-    // Backward: other_box->Start() >  text_offset
-    // Forward:  other_box->Start() >= text_offset
-    // The text continues on the next line only if the last text box is not
-    // on this line and none of the boxes on this line have a larger start
-    // offset.
-    bool continues_on_next_line = true;
-    InlineBox* other_box = box;
-    while (continues_on_next_line) {
-      other_box = other_box->NextLeafChild();
-      if (!other_box)
-        break;
-      if (other_box == last_text_box ||
-          (LineLayoutAPIShim::LayoutObjectFrom(
-               other_box->GetLineLayoutItem()) == text_layout_object &&
-           ToInlineTextBox(other_box)->Start() > text_offset))
-        continues_on_next_line = false;
-    }
-
-    other_box = box;
-    while (continues_on_next_line) {
-      other_box = other_box->PrevLeafChild();
-      if (!other_box)
-        break;
-      if (other_box == last_text_box ||
-          (LineLayoutAPIShim::LayoutObjectFrom(
-               other_box->GetLineLayoutItem()) == text_layout_object &&
-           ToInlineTextBox(other_box)->Start() > text_offset))
-        continues_on_next_line = false;
-    }
-
-    if (continues_on_next_line)
+    if (DoesContinueOnNextLine(*text_layout_object, box, text_offset + 1))
       return true;
   }
   return false;
@@ -2247,40 +1887,7 @@
     if (box == last_text_box || text_offset != box->Start() + box->Len())
       continue;
 
-    // TODO(yosin): We should move below code fragment into
-    // |DoesContinueOnNextLine()|. Note: |MostBackwardCaretPosition()| has
-    // same code fragment except for comparison on |text_offset|.
-    // Backward: other_box->Start() >  text_offset
-    // Forward:  other_box->Start() >= text_offset
-    // The text continues on the next line only if the last text box is not
-    // on this line and none of the boxes on this line have a larger start
-    // offset.
-    bool continues_on_next_line = true;
-    InlineBox* other_box = box;
-    while (continues_on_next_line) {
-      other_box = other_box->NextLeafChild();
-      if (!other_box)
-        break;
-      if (other_box == last_text_box ||
-          (LineLayoutAPIShim::LayoutObjectFrom(
-               other_box->GetLineLayoutItem()) == text_layout_object &&
-           ToInlineTextBox(other_box)->Start() >= text_offset))
-        continues_on_next_line = false;
-    }
-
-    other_box = box;
-    while (continues_on_next_line) {
-      other_box = other_box->PrevLeafChild();
-      if (!other_box)
-        break;
-      if (other_box == last_text_box ||
-          (LineLayoutAPIShim::LayoutObjectFrom(
-               other_box->GetLineLayoutItem()) == text_layout_object &&
-           ToInlineTextBox(other_box)->Start() >= text_offset))
-        continues_on_next_line = false;
-    }
-
-    if (continues_on_next_line)
+    if (DoesContinueOnNextLine(*text_layout_object, box, text_offset))
       return true;
   }
   return false;
diff --git a/third_party/WebKit/Source/core/editing/VisibleUnitsParagraph.cpp b/third_party/WebKit/Source/core/editing/VisibleUnitsParagraph.cpp
new file mode 100644
index 0000000..e4eb2451
--- /dev/null
+++ b/third_party/WebKit/Source/core/editing/VisibleUnitsParagraph.cpp
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "core/editing/VisibleUnits.h"
+
+#include "core/editing/EditingUtilities.h"
+#include "core/layout/LayoutText.h"
+#include "core/layout/api/LayoutItem.h"
+
+namespace blink {
+
+namespace {
+
+bool NodeIsUserSelectAll(const Node* node) {
+  return node && node->GetLayoutObject() &&
+         node->GetLayoutObject()->Style()->UserSelect() == EUserSelect::kAll;
+}
+
+template <typename Strategy>
+PositionTemplate<Strategy> StartOfParagraphAlgorithm(
+    const PositionTemplate<Strategy>& position,
+    EditingBoundaryCrossingRule boundary_crossing_rule) {
+  Node* const start_node = position.AnchorNode();
+
+  if (!start_node)
+    return PositionTemplate<Strategy>();
+
+  if (IsRenderedAsNonInlineTableImageOrHR(start_node))
+    return PositionTemplate<Strategy>::BeforeNode(start_node);
+
+  Element* const start_block = EnclosingBlock(
+      PositionTemplate<Strategy>::FirstPositionInOrBeforeNode(start_node),
+      kCannotCrossEditingBoundary);
+  ContainerNode* const highest_root = HighestEditableRoot(position);
+  const bool start_node_is_editable = HasEditableStyle(*start_node);
+
+  Node* candidate_node = start_node;
+  PositionAnchorType candidate_type = position.AnchorType();
+  int candidate_offset = position.ComputeEditingOffset();
+
+  Node* previous_node_iterator = start_node;
+  while (previous_node_iterator) {
+    if (boundary_crossing_rule == kCannotCrossEditingBoundary &&
+        !NodeIsUserSelectAll(previous_node_iterator) &&
+        HasEditableStyle(*previous_node_iterator) != start_node_is_editable)
+      break;
+    if (boundary_crossing_rule == kCanSkipOverEditingBoundary) {
+      while (previous_node_iterator &&
+             HasEditableStyle(*previous_node_iterator) !=
+                 start_node_is_editable) {
+        previous_node_iterator =
+            Strategy::PreviousPostOrder(*previous_node_iterator, start_block);
+      }
+      if (!previous_node_iterator ||
+          !previous_node_iterator->IsDescendantOf(highest_root))
+        break;
+    }
+
+    const LayoutItem layout_item =
+        LayoutItem(previous_node_iterator->GetLayoutObject());
+    if (layout_item.IsNull()) {
+      previous_node_iterator =
+          Strategy::PreviousPostOrder(*previous_node_iterator, start_block);
+      continue;
+    }
+    const ComputedStyle& style = layout_item.StyleRef();
+    if (style.Visibility() != EVisibility::kVisible) {
+      previous_node_iterator =
+          Strategy::PreviousPostOrder(*previous_node_iterator, start_block);
+      continue;
+    }
+
+    if (layout_item.IsBR() || IsEnclosingBlock(previous_node_iterator))
+      break;
+
+    if (layout_item.IsText() &&
+        ToLayoutText(previous_node_iterator->GetLayoutObject())
+            ->ResolvedTextLength()) {
+      SECURITY_DCHECK(previous_node_iterator->IsTextNode());
+      if (style.PreserveNewline()) {
+        LayoutText* text =
+            ToLayoutText(previous_node_iterator->GetLayoutObject());
+        int index = text->TextLength();
+        if (previous_node_iterator == start_node && candidate_offset < index)
+          index = max(0, candidate_offset);
+        while (--index >= 0) {
+          if ((*text)[index] == '\n') {
+            return PositionTemplate<Strategy>(ToText(previous_node_iterator),
+                                              index + 1);
+          }
+        }
+      }
+      candidate_node = previous_node_iterator;
+      candidate_type = PositionAnchorType::kOffsetInAnchor;
+      candidate_offset = 0;
+      previous_node_iterator =
+          Strategy::PreviousPostOrder(*previous_node_iterator, start_block);
+    } else if (EditingIgnoresContent(*previous_node_iterator) ||
+               IsDisplayInsideTable(previous_node_iterator)) {
+      candidate_node = previous_node_iterator;
+      candidate_type = PositionAnchorType::kBeforeAnchor;
+      previous_node_iterator = previous_node_iterator->previousSibling()
+                                   ? previous_node_iterator->previousSibling()
+                                   : Strategy::PreviousPostOrder(
+                                         *previous_node_iterator, start_block);
+    } else {
+      previous_node_iterator =
+          Strategy::PreviousPostOrder(*previous_node_iterator, start_block);
+    }
+  }
+
+  if (candidate_type == PositionAnchorType::kOffsetInAnchor)
+    return PositionTemplate<Strategy>(candidate_node, candidate_offset);
+
+  return PositionTemplate<Strategy>(candidate_node, candidate_type);
+}
+
+template <typename Strategy>
+VisiblePositionTemplate<Strategy> StartOfParagraphAlgorithm(
+    const VisiblePositionTemplate<Strategy>& visible_position,
+    EditingBoundaryCrossingRule boundary_crossing_rule) {
+  DCHECK(visible_position.IsValid()) << visible_position;
+  return CreateVisiblePosition(StartOfParagraphAlgorithm(
+      visible_position.DeepEquivalent(), boundary_crossing_rule));
+}
+
+template <typename Strategy>
+PositionTemplate<Strategy> EndOfParagraphAlgorithm(
+    const PositionTemplate<Strategy>& position,
+    EditingBoundaryCrossingRule boundary_crossing_rule) {
+  Node* const start_node = position.AnchorNode();
+
+  if (!start_node)
+    return PositionTemplate<Strategy>();
+
+  if (IsRenderedAsNonInlineTableImageOrHR(start_node))
+    return PositionTemplate<Strategy>::AfterNode(start_node);
+
+  Element* const start_block = EnclosingBlock(
+      PositionTemplate<Strategy>::FirstPositionInOrBeforeNode(start_node),
+      kCannotCrossEditingBoundary);
+  ContainerNode* const highest_root = HighestEditableRoot(position);
+  const bool start_node_is_editable = HasEditableStyle(*start_node);
+
+  Node* candidate_node = start_node;
+  PositionAnchorType candidate_type = position.AnchorType();
+  int candidate_offset = position.ComputeEditingOffset();
+
+  Node* next_node_iterator = start_node;
+  while (next_node_iterator) {
+    if (boundary_crossing_rule == kCannotCrossEditingBoundary &&
+        !NodeIsUserSelectAll(next_node_iterator) &&
+        HasEditableStyle(*next_node_iterator) != start_node_is_editable)
+      break;
+    if (boundary_crossing_rule == kCanSkipOverEditingBoundary) {
+      while (next_node_iterator &&
+             HasEditableStyle(*next_node_iterator) != start_node_is_editable)
+        next_node_iterator = Strategy::Next(*next_node_iterator, start_block);
+      if (!next_node_iterator ||
+          !next_node_iterator->IsDescendantOf(highest_root))
+        break;
+    }
+
+    LayoutObject* const layout_object = next_node_iterator->GetLayoutObject();
+    if (!layout_object) {
+      next_node_iterator = Strategy::Next(*next_node_iterator, start_block);
+      continue;
+    }
+    const ComputedStyle& style = layout_object->StyleRef();
+    if (style.Visibility() != EVisibility::kVisible) {
+      next_node_iterator = Strategy::Next(*next_node_iterator, start_block);
+      continue;
+    }
+
+    if (layout_object->IsBR() || IsEnclosingBlock(next_node_iterator))
+      break;
+
+    // TODO(editing-dev): We avoid returning a position where the layoutObject
+    // can't accept the caret.
+    if (layout_object->IsText() &&
+        ToLayoutText(layout_object)->ResolvedTextLength()) {
+      SECURITY_DCHECK(next_node_iterator->IsTextNode());
+      LayoutText* const text = ToLayoutText(layout_object);
+      if (style.PreserveNewline()) {
+        const int length = ToLayoutText(layout_object)->TextLength();
+        for (int i = (next_node_iterator == start_node ? candidate_offset : 0);
+             i < length; ++i) {
+          if ((*text)[i] == '\n') {
+            return PositionTemplate<Strategy>(ToText(next_node_iterator),
+                                              i + text->TextStartOffset());
+          }
+        }
+      }
+
+      candidate_node = next_node_iterator;
+      candidate_type = PositionAnchorType::kOffsetInAnchor;
+      candidate_offset =
+          layout_object->CaretMaxOffset() + text->TextStartOffset();
+      next_node_iterator = Strategy::Next(*next_node_iterator, start_block);
+    } else if (EditingIgnoresContent(*next_node_iterator) ||
+               IsDisplayInsideTable(next_node_iterator)) {
+      candidate_node = next_node_iterator;
+      candidate_type = PositionAnchorType::kAfterAnchor;
+      next_node_iterator =
+          Strategy::NextSkippingChildren(*next_node_iterator, start_block);
+    } else {
+      next_node_iterator = Strategy::Next(*next_node_iterator, start_block);
+    }
+  }
+
+  if (candidate_type == PositionAnchorType::kOffsetInAnchor)
+    return PositionTemplate<Strategy>(candidate_node, candidate_offset);
+
+  return PositionTemplate<Strategy>(candidate_node, candidate_type);
+}
+
+template <typename Strategy>
+VisiblePositionTemplate<Strategy> EndOfParagraphAlgorithm(
+    const VisiblePositionTemplate<Strategy>& visible_position,
+    EditingBoundaryCrossingRule boundary_crossing_rule) {
+  DCHECK(visible_position.IsValid()) << visible_position;
+  return CreateVisiblePosition(EndOfParagraphAlgorithm(
+      visible_position.DeepEquivalent(), boundary_crossing_rule));
+}
+
+template <typename Strategy>
+bool IsStartOfParagraphAlgorithm(
+    const VisiblePositionTemplate<Strategy>& pos,
+    EditingBoundaryCrossingRule boundary_crossing_rule) {
+  DCHECK(pos.IsValid()) << pos;
+  return pos.IsNotNull() &&
+         pos.DeepEquivalent() ==
+             StartOfParagraph(pos, boundary_crossing_rule).DeepEquivalent();
+}
+
+template <typename Strategy>
+bool IsEndOfParagraphAlgorithm(
+    const VisiblePositionTemplate<Strategy>& pos,
+    EditingBoundaryCrossingRule boundary_crossing_rule) {
+  DCHECK(pos.IsValid()) << pos;
+  return pos.IsNotNull() &&
+         pos.DeepEquivalent() ==
+             EndOfParagraph(pos, boundary_crossing_rule).DeepEquivalent();
+}
+
+}  // namespace
+
+VisiblePosition StartOfParagraph(
+    const VisiblePosition& c,
+    EditingBoundaryCrossingRule boundary_crossing_rule) {
+  return StartOfParagraphAlgorithm<EditingStrategy>(c, boundary_crossing_rule);
+}
+
+VisiblePositionInFlatTree StartOfParagraph(
+    const VisiblePositionInFlatTree& c,
+    EditingBoundaryCrossingRule boundary_crossing_rule) {
+  return StartOfParagraphAlgorithm<EditingInFlatTreeStrategy>(
+      c, boundary_crossing_rule);
+}
+
+VisiblePosition EndOfParagraph(
+    const VisiblePosition& c,
+    EditingBoundaryCrossingRule boundary_crossing_rule) {
+  return EndOfParagraphAlgorithm<EditingStrategy>(c, boundary_crossing_rule);
+}
+
+VisiblePositionInFlatTree EndOfParagraph(
+    const VisiblePositionInFlatTree& c,
+    EditingBoundaryCrossingRule boundary_crossing_rule) {
+  return EndOfParagraphAlgorithm<EditingInFlatTreeStrategy>(
+      c, boundary_crossing_rule);
+}
+
+// TODO(editing-dev): isStartOfParagraph(startOfNextParagraph(pos)) is not
+// always true
+VisiblePosition StartOfNextParagraph(const VisiblePosition& visible_position) {
+  DCHECK(visible_position.IsValid()) << visible_position;
+  VisiblePosition paragraph_end(
+      EndOfParagraph(visible_position, kCanSkipOverEditingBoundary));
+  VisiblePosition after_paragraph_end(
+      NextPositionOf(paragraph_end, kCannotCrossEditingBoundary));
+  // The position after the last position in the last cell of a table
+  // is not the start of the next paragraph.
+  if (TableElementJustBefore(after_paragraph_end))
+    return NextPositionOf(after_paragraph_end, kCannotCrossEditingBoundary);
+  return after_paragraph_end;
+}
+
+// TODO(editing-dev): isStartOfParagraph(startOfNextParagraph(pos)) is not
+// always true
+bool InSameParagraph(const VisiblePosition& a,
+                     const VisiblePosition& b,
+                     EditingBoundaryCrossingRule boundary_crossing_rule) {
+  DCHECK(a.IsValid()) << a;
+  DCHECK(b.IsValid()) << b;
+  return a.IsNotNull() &&
+         StartOfParagraph(a, boundary_crossing_rule).DeepEquivalent() ==
+             StartOfParagraph(b, boundary_crossing_rule).DeepEquivalent();
+}
+
+bool IsStartOfParagraph(const VisiblePosition& pos,
+                        EditingBoundaryCrossingRule boundary_crossing_rule) {
+  return IsStartOfParagraphAlgorithm<EditingStrategy>(pos,
+                                                      boundary_crossing_rule);
+}
+
+bool IsStartOfParagraph(const VisiblePositionInFlatTree& pos,
+                        EditingBoundaryCrossingRule boundary_crossing_rule) {
+  return IsStartOfParagraphAlgorithm<EditingInFlatTreeStrategy>(
+      pos, boundary_crossing_rule);
+}
+
+bool IsEndOfParagraph(const VisiblePosition& pos,
+                      EditingBoundaryCrossingRule boundary_crossing_rule) {
+  return IsEndOfParagraphAlgorithm<EditingStrategy>(pos,
+                                                    boundary_crossing_rule);
+}
+
+bool IsEndOfParagraph(const VisiblePositionInFlatTree& pos,
+                      EditingBoundaryCrossingRule boundary_crossing_rule) {
+  return IsEndOfParagraphAlgorithm<EditingInFlatTreeStrategy>(
+      pos, boundary_crossing_rule);
+}
+
+// TODO(editing-dev): We should move |PreviousParagraphPosition()| to
+// "SelectionModifier.cpp"
+VisiblePosition PreviousParagraphPosition(const VisiblePosition& p,
+                                          LayoutUnit x) {
+  DCHECK(p.IsValid()) << p;
+  VisiblePosition pos = p;
+  do {
+    VisiblePosition n = PreviousLinePosition(pos, x);
+    if (n.IsNull() || n.DeepEquivalent() == pos.DeepEquivalent())
+      break;
+    pos = n;
+  } while (InSameParagraph(p, pos));
+  return pos;
+}
+
+// TODO(editing-dev): We should move |NextParagraphPosition()| to
+// "SelectionModifier.cpp"
+VisiblePosition NextParagraphPosition(const VisiblePosition& p, LayoutUnit x) {
+  DCHECK(p.IsValid()) << p;
+  VisiblePosition pos = p;
+  do {
+    VisiblePosition n = NextLinePosition(pos, x);
+    if (n.IsNull() || n.DeepEquivalent() == pos.DeepEquivalent())
+      break;
+    pos = n;
+  } while (InSameParagraph(p, pos));
+  return pos;
+}
+
+EphemeralRange ExpandToParagraphBoundary(const EphemeralRange& range) {
+  const VisiblePosition& start = CreateVisiblePosition(range.StartPosition());
+  DCHECK(start.IsNotNull()) << range.StartPosition();
+  const Position& paragraph_start = StartOfParagraph(start).DeepEquivalent();
+  DCHECK(paragraph_start.IsNotNull()) << range.StartPosition();
+
+  const VisiblePosition& end = CreateVisiblePosition(range.EndPosition());
+  DCHECK(end.IsNotNull()) << range.EndPosition();
+  const Position& paragraph_end = EndOfParagraph(end).DeepEquivalent();
+  DCHECK(paragraph_end.IsNotNull()) << range.EndPosition();
+
+  // TODO(xiaochengh): There are some cases (crbug.com/640112) where we get
+  // |paragraphStart > paragraphEnd|, which is the reason we cannot directly
+  // return |EphemeralRange(paragraphStart, paragraphEnd)|. This is not
+  // desired, though. We should do more investigation to ensure that why
+  // |paragraphStart <= paragraphEnd| is violated.
+  const Position& result_start =
+      paragraph_start.IsNotNull() && paragraph_start <= range.StartPosition()
+          ? paragraph_start
+          : range.StartPosition();
+  const Position& result_end =
+      paragraph_end.IsNotNull() && paragraph_end >= range.EndPosition()
+          ? paragraph_end
+          : range.EndPosition();
+  return EphemeralRange(result_start, result_end);
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/core/events/EventTargetFactory.json5 b/third_party/WebKit/Source/core/events/EventTargetFactory.json5
index 05eca22..875c970 100644
--- a/third_party/WebKit/Source/core/events/EventTargetFactory.json5
+++ b/third_party/WebKit/Source/core/events/EventTargetFactory.json5
@@ -19,6 +19,7 @@
     "core/html/track/TextTrackList",
     "core/html/track/VideoTrackList",
     "core/loader/appcache/ApplicationCache",
+    "core/mojo/testing/MojoInterfaceInterceptor",
     "core/offscreencanvas/OffscreenCanvas",
     "core/page/EventSource",
     "core/timing/Performance",
diff --git a/third_party/WebKit/Source/core/events/EventTypeNames.json5 b/third_party/WebKit/Source/core/events/EventTypeNames.json5
index f9a2d98c..fefd670 100644
--- a/third_party/WebKit/Source/core/events/EventTypeNames.json5
+++ b/third_party/WebKit/Source/core/events/EventTypeNames.json5
@@ -130,6 +130,7 @@
     "inactive",
     "input",
     "install",
+    "interfacerequest",
     "invalid",
     "keydown",
     "keypress",
diff --git a/third_party/WebKit/Source/core/frame/LocalFrame.h b/third_party/WebKit/Source/core/frame/LocalFrame.h
index c354f93d..a0435f2 100644
--- a/third_party/WebKit/Source/core/frame/LocalFrame.h
+++ b/third_party/WebKit/Source/core/frame/LocalFrame.h
@@ -226,7 +226,12 @@
 
   bool CanNavigate(const Frame&);
 
+  // This method is deprecated. Please use
+  // LocalFrameClient::GetInterfaceProvider() instead.
+  //
+  // TODO(crbug.com/726943): Remove this method.
   InterfaceProvider* GetInterfaceProvider() { return interface_provider_; }
+
   InterfaceRegistry* GetInterfaceRegistry() { return interface_registry_; }
 
   LocalFrameClient* Client() const;
diff --git a/third_party/WebKit/Source/core/frame/LocalFrameClient.h b/third_party/WebKit/Source/core/frame/LocalFrameClient.h
index 76b023f..12070f1 100644
--- a/third_party/WebKit/Source/core/frame/LocalFrameClient.h
+++ b/third_party/WebKit/Source/core/frame/LocalFrameClient.h
@@ -57,6 +57,10 @@
 #include "public/platform/WebURLRequest.h"
 #include "v8/include/v8.h"
 
+namespace service_manager {
+class InterfaceProvider;
+}
+
 namespace blink {
 
 class Document;
@@ -330,6 +334,10 @@
 
   virtual BlameContext* GetFrameBlameContext() { return nullptr; }
 
+  virtual service_manager::InterfaceProvider* GetInterfaceProvider() {
+    return nullptr;
+  }
+
   virtual void SetHasReceivedUserGesture(bool received_previously) {}
 
   virtual void SetDevToolsFrameId(const String& devtools_frame_id) {}
diff --git a/third_party/WebKit/Source/core/inspector/browser_protocol.json b/third_party/WebKit/Source/core/inspector/browser_protocol.json
index 676f45d1..1fdc86b 100644
--- a/third_party/WebKit/Source/core/inspector/browser_protocol.json
+++ b/third_party/WebKit/Source/core/inspector/browser_protocol.json
@@ -1708,7 +1708,7 @@
                     { "name": "redirectStatusCode", "type": "integer", "optional": true, "description": "HTTP response code, only sent if a redirect was intercepted." },
                     { "name": "redirectUrl", "optional": true, "type": "string", "description": "Redirect location, only sent if a redirect was intercepted."}
                 ],
-                "experimental": true
+                "experimental": true 
             }
         ]
     },
@@ -4528,15 +4528,6 @@
                     "all"
                 ],
                 "description": "Enum of possible storage types."
-            },
-            {
-                "id": "QuotaAndUsage",
-                "type": "object",
-                "properties": [
-                    { "name": "quota", "type": "number", "description": "Storage quota (bytes)." },
-                    { "name": "usage", "type": "number", "description": "Storage usage (bytes)." }
-                ],
-                "description": "Quota and usage information."
             }
         ],
         "commands": [
@@ -4547,16 +4538,6 @@
                     { "name": "storageTypes", "type": "string", "description": "Comma separated origin names." }
                 ],
                 "description": "Clears storage for origin."
-            },
-            {
-                "name": "getUsageAndQuota",
-                "parameters": [
-                    { "name": "origin", "type": "string", "description": "Security origin." }
-                ],
-                "returns": [
-                    { "name": "quotaAndUsage", "$ref": "QuotaAndUsage", "description": "Quota and usage information."}
-                ],
-                "description": "Returns usage and quota in bytes."
             }
         ]
     },
diff --git a/third_party/WebKit/Source/core/mojo/BUILD.gn b/third_party/WebKit/Source/core/mojo/BUILD.gn
index d28d6fe..202d78e 100644
--- a/third_party/WebKit/Source/core/mojo/BUILD.gn
+++ b/third_party/WebKit/Source/core/mojo/BUILD.gn
@@ -12,9 +12,14 @@
     "MojoHandle.h",
     "MojoWatcher.cpp",
     "MojoWatcher.h",
+    "testing/MojoInterfaceInterceptor.cpp",
+    "testing/MojoInterfaceInterceptor.h",
+    "testing/MojoInterfaceRequestEvent.cpp",
+    "testing/MojoInterfaceRequestEvent.h",
   ]
 
   deps = [
     "//mojo/public/cpp/system",
+    "//services/service_manager/public/cpp",
   ]
 }
diff --git a/third_party/WebKit/Source/core/mojo/DEPS b/third_party/WebKit/Source/core/mojo/DEPS
index a3ad469..e6eb1f8 100644
--- a/third_party/WebKit/Source/core/mojo/DEPS
+++ b/third_party/WebKit/Source/core/mojo/DEPS
@@ -1,3 +1,4 @@
 include_rules = [
   "+mojo/public/cpp/system",
+  "+services/service_manager/public/cpp",
 ]
diff --git a/third_party/WebKit/Source/core/mojo/Mojo.cpp b/third_party/WebKit/Source/core/mojo/Mojo.cpp
index bf56e19..47f53e41 100644
--- a/third_party/WebKit/Source/core/mojo/Mojo.cpp
+++ b/third_party/WebKit/Source/core/mojo/Mojo.cpp
@@ -4,11 +4,19 @@
 
 #include "core/mojo/Mojo.h"
 
+#include <string>
+
+#include "core/frame/LocalDOMWindow.h"
+#include "core/frame/LocalFrame.h"
+#include "core/frame/LocalFrameClient.h"
 #include "core/mojo/MojoCreateDataPipeOptions.h"
 #include "core/mojo/MojoCreateDataPipeResult.h"
 #include "core/mojo/MojoCreateMessagePipeResult.h"
 #include "core/mojo/MojoCreateSharedBufferResult.h"
 #include "core/mojo/MojoHandle.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "platform/bindings/ScriptState.h"
+#include "services/service_manager/public/cpp/interface_provider.h"
 
 namespace blink {
 
@@ -65,4 +73,17 @@
   }
 }
 
+// static
+void Mojo::bindInterface(ScriptState* script_state,
+                         const String& interface_name,
+                         MojoHandle* request_handle) {
+  LocalDOMWindow::From(script_state)
+      ->GetFrame()
+      ->Client()
+      ->GetInterfaceProvider()
+      ->GetInterface(
+          std::string(interface_name.Utf8().data()),
+          mojo::ScopedMessagePipeHandle::From(request_handle->TakeHandle()));
+}
+
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/mojo/Mojo.h b/third_party/WebKit/Source/core/mojo/Mojo.h
index c3123bb..e33c0d2 100644
--- a/third_party/WebKit/Source/core/mojo/Mojo.h
+++ b/third_party/WebKit/Source/core/mojo/Mojo.h
@@ -7,6 +7,7 @@
 
 #include "mojo/public/cpp/system/core.h"
 #include "platform/bindings/ScriptWrappable.h"
+#include "platform/wtf/text/WTFString.h"
 
 namespace blink {
 
@@ -14,6 +15,8 @@
 class MojoCreateDataPipeResult;
 class MojoCreateMessagePipeResult;
 class MojoCreateSharedBufferResult;
+class MojoHandle;
+class ScriptState;
 
 class Mojo final : public GarbageCollected<Mojo>, public ScriptWrappable {
   DEFINE_WRAPPERTYPEINFO();
@@ -49,6 +52,10 @@
   static void createSharedBuffer(unsigned num_bytes,
                                  MojoCreateSharedBufferResult&);
 
+  static void bindInterface(ScriptState*,
+                            const String& interface_name,
+                            MojoHandle*);
+
   DEFINE_INLINE_TRACE() {}
 };
 
diff --git a/third_party/WebKit/Source/core/mojo/Mojo.idl b/third_party/WebKit/Source/core/mojo/Mojo.idl
index 1cc8d3b..d90a1df 100644
--- a/third_party/WebKit/Source/core/mojo/Mojo.idl
+++ b/third_party/WebKit/Source/core/mojo/Mojo.idl
@@ -32,4 +32,6 @@
     static MojoCreateMessagePipeResult createMessagePipe();
     static MojoCreateDataPipeResult createDataPipe(MojoCreateDataPipeOptions options);
     static MojoCreateSharedBufferResult createSharedBuffer(unsigned long numBytes);
+
+    [CallWith=ScriptState] static void bindInterface(DOMString interfaceName, MojoHandle request_handle);
 };
diff --git a/third_party/WebKit/Source/core/mojo/MojoHandle.cpp b/third_party/WebKit/Source/core/mojo/MojoHandle.cpp
index b28d4ee7..36aa5e6 100644
--- a/third_party/WebKit/Source/core/mojo/MojoHandle.cpp
+++ b/third_party/WebKit/Source/core/mojo/MojoHandle.cpp
@@ -32,6 +32,10 @@
   return new MojoHandle(std::move(handle));
 }
 
+mojo::ScopedHandle MojoHandle::TakeHandle() {
+  return std::move(handle_);
+}
+
 MojoHandle::MojoHandle(mojo::ScopedHandle handle)
     : handle_(std::move(handle)) {}
 
diff --git a/third_party/WebKit/Source/core/mojo/MojoHandle.h b/third_party/WebKit/Source/core/mojo/MojoHandle.h
index 5dd19bb..81b8c7c 100644
--- a/third_party/WebKit/Source/core/mojo/MojoHandle.h
+++ b/third_party/WebKit/Source/core/mojo/MojoHandle.h
@@ -34,6 +34,8 @@
  public:
   CORE_EXPORT static MojoHandle* Create(mojo::ScopedHandle);
 
+  mojo::ScopedHandle TakeHandle();
+
   void close();
   MojoWatcher* watch(ScriptState*,
                      const MojoHandleSignals&,
diff --git a/third_party/WebKit/Source/core/mojo/testing/MojoInterfaceInterceptor.cpp b/third_party/WebKit/Source/core/mojo/testing/MojoInterfaceInterceptor.cpp
new file mode 100644
index 0000000..d46c2a88
--- /dev/null
+++ b/third_party/WebKit/Source/core/mojo/testing/MojoInterfaceInterceptor.cpp
@@ -0,0 +1,97 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "core/mojo/testing/MojoInterfaceInterceptor.h"
+
+#include "bindings/core/v8/ExceptionState.h"
+#include "core/dom/ExecutionContext.h"
+#include "core/frame/LocalDOMWindow.h"
+#include "core/frame/LocalFrame.h"
+#include "core/frame/LocalFrameClient.h"
+#include "core/mojo/MojoHandle.h"
+#include "core/mojo/testing/MojoInterfaceRequestEvent.h"
+#include "platform/bindings/ScriptState.h"
+#include "platform/wtf/text/StringUTF8Adaptor.h"
+#include "services/service_manager/public/cpp/interface_provider.h"
+
+namespace blink {
+
+// static
+MojoInterfaceInterceptor* MojoInterfaceInterceptor::Create(
+    ScriptState* script_state,
+    const String& interface_name) {
+  return new MojoInterfaceInterceptor(script_state, interface_name);
+}
+
+MojoInterfaceInterceptor::~MojoInterfaceInterceptor() {}
+
+void MojoInterfaceInterceptor::start(ExceptionState& exception_state) {
+  if (started_)
+    return;
+
+  std::string interface_name =
+      StringUTF8Adaptor(interface_name_).AsStringPiece().as_string();
+  service_manager::InterfaceProvider::TestApi test_api(
+      GetFrame()->Client()->GetInterfaceProvider());
+  if (test_api.HasBinderForName(interface_name)) {
+    exception_state.ThrowDOMException(
+        kInvalidModificationError,
+        "Interface " + interface_name_ +
+            " is already intercepted by another MojoInterfaceInterceptor.");
+    return;
+  }
+
+  started_ = true;
+  test_api.SetBinderForName(interface_name,
+                            ConvertToBaseCallback(WTF::Bind(
+                                &MojoInterfaceInterceptor::OnInterfaceRequest,
+                                WrapWeakPersistent(this))));
+}
+
+void MojoInterfaceInterceptor::stop() {
+  if (!started_)
+    return;
+
+  started_ = false;
+  service_manager::InterfaceProvider::TestApi test_api(
+      GetFrame()->Client()->GetInterfaceProvider());
+  std::string interface_name =
+      StringUTF8Adaptor(interface_name_).AsStringPiece().as_string();
+  DCHECK(test_api.HasBinderForName(interface_name));
+  test_api.ClearBinderForName(interface_name);
+}
+
+DEFINE_TRACE(MojoInterfaceInterceptor) {
+  EventTargetWithInlineData::Trace(visitor);
+  ContextLifecycleObserver::Trace(visitor);
+}
+
+const AtomicString& MojoInterfaceInterceptor::InterfaceName() const {
+  return EventTargetNames::MojoInterfaceInterceptor;
+}
+
+ExecutionContext* MojoInterfaceInterceptor::GetExecutionContext() const {
+  return ContextLifecycleObserver::GetExecutionContext();
+}
+
+bool MojoInterfaceInterceptor::HasPendingActivity() const {
+  return started_;
+}
+
+void MojoInterfaceInterceptor::ContextDestroyed(ExecutionContext*) {
+  stop();
+}
+
+MojoInterfaceInterceptor::MojoInterfaceInterceptor(ScriptState* script_state,
+                                                   const String& interface_name)
+    : ContextLifecycleObserver(ExecutionContext::From(script_state)),
+      interface_name_(interface_name) {}
+
+void MojoInterfaceInterceptor::OnInterfaceRequest(
+    mojo::ScopedMessagePipeHandle handle) {
+  DispatchEvent(MojoInterfaceRequestEvent::Create(
+      MojoHandle::Create(mojo::ScopedHandle::From(std::move(handle)))));
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/core/mojo/testing/MojoInterfaceInterceptor.h b/third_party/WebKit/Source/core/mojo/testing/MojoInterfaceInterceptor.h
new file mode 100644
index 0000000..4c7ce0d
--- /dev/null
+++ b/third_party/WebKit/Source/core/mojo/testing/MojoInterfaceInterceptor.h
@@ -0,0 +1,70 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MojoInterfaceInterceptor_h
+#define MojoInterfaceInterceptor_h
+
+#include "core/dom/ContextLifecycleObserver.h"
+#include "core/events/EventListener.h"
+#include "core/events/EventTarget.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "platform/bindings/ActiveScriptWrappable.h"
+#include "platform/bindings/ScriptWrappable.h"
+#include "platform/wtf/text/WTFString.h"
+
+namespace blink {
+
+class ExceptionState;
+class ExecutionContext;
+class ScriptState;
+
+// A MojoInterfaceInterceptor can be constructed by test scripts in order to
+// intercept all outgoing requests for a specific named interface from the
+// owning document, whether the requests come from other script or from native
+// code (e.g. native API implementation code.) In production, such reqiests are
+// normally routed to the browser to be bound to real interface implementations,
+// but in test environments it's often useful to mock them out locally.
+class MojoInterfaceInterceptor final
+    : public EventTargetWithInlineData,
+      public ActiveScriptWrappable<MojoInterfaceInterceptor>,
+      public ContextLifecycleObserver {
+  DEFINE_WRAPPERTYPEINFO();
+  USING_GARBAGE_COLLECTED_MIXIN(MojoInterfaceInterceptor);
+
+ public:
+  static MojoInterfaceInterceptor* Create(ScriptState*,
+                                          const String& interface_name);
+  ~MojoInterfaceInterceptor();
+
+  void start(ExceptionState&);
+  void stop();
+
+  DEFINE_ATTRIBUTE_EVENT_LISTENER(interfacerequest);
+
+  DECLARE_VIRTUAL_TRACE();
+
+  // EventTargetWithInlineData
+  const AtomicString& InterfaceName() const override;
+  ExecutionContext* GetExecutionContext() const override;
+
+  // ActiveScriptWrappable
+  bool HasPendingActivity() const final;
+
+  // ContextLifecycleObserver
+  void ContextDestroyed(ExecutionContext*) final;
+
+ private:
+  friend class V8MojoInterfaceInterceptor;
+
+  MojoInterfaceInterceptor(ScriptState*, const String& interface_name);
+
+  void OnInterfaceRequest(mojo::ScopedMessagePipeHandle);
+
+  const String interface_name_;
+  bool started_ = false;
+};
+
+}  // namespace blink
+
+#endif  // MojoInterfaceInterceptor_h
diff --git a/third_party/WebKit/Source/core/mojo/testing/MojoInterfaceInterceptor.idl b/third_party/WebKit/Source/core/mojo/testing/MojoInterfaceInterceptor.idl
new file mode 100644
index 0000000..ce432ea3
--- /dev/null
+++ b/third_party/WebKit/Source/core/mojo/testing/MojoInterfaceInterceptor.idl
@@ -0,0 +1,16 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+    ActiveScriptWrappable,
+    Constructor(DOMString interfaceName),
+    ConstructorCallWith=ScriptState,
+    DependentLifetime,
+    RuntimeEnabled=MojoJSTest,
+] interface MojoInterfaceInterceptor : EventTarget {
+    [RaisesException] void start();
+    void stop();
+
+    attribute EventHandler oninterfacerequest;
+};
diff --git a/third_party/WebKit/Source/core/mojo/testing/MojoInterfaceRequestEvent.cpp b/third_party/WebKit/Source/core/mojo/testing/MojoInterfaceRequestEvent.cpp
new file mode 100644
index 0000000..a25cd08
--- /dev/null
+++ b/third_party/WebKit/Source/core/mojo/testing/MojoInterfaceRequestEvent.cpp
@@ -0,0 +1,26 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "core/mojo/testing/MojoInterfaceRequestEvent.h"
+
+#include "core/mojo/MojoHandle.h"
+
+namespace blink {
+
+MojoInterfaceRequestEvent::~MojoInterfaceRequestEvent() {}
+
+DEFINE_TRACE(MojoInterfaceRequestEvent) {
+  Event::Trace(visitor);
+  visitor->Trace(handle_);
+}
+
+MojoInterfaceRequestEvent::MojoInterfaceRequestEvent(MojoHandle* handle)
+    : Event(EventTypeNames::interfacerequest, false, false), handle_(handle) {}
+
+MojoInterfaceRequestEvent::MojoInterfaceRequestEvent(
+    const AtomicString& type,
+    const MojoInterfaceRequestEventInit& initializer)
+    : Event(type, false, false), handle_(initializer.handle()) {}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/core/mojo/testing/MojoInterfaceRequestEvent.h b/third_party/WebKit/Source/core/mojo/testing/MojoInterfaceRequestEvent.h
new file mode 100644
index 0000000..db63af9
--- /dev/null
+++ b/third_party/WebKit/Source/core/mojo/testing/MojoInterfaceRequestEvent.h
@@ -0,0 +1,53 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MojoInterfaceRequestEvent_h
+#define MojoInterfaceRequestEvent_h
+
+#include "core/events/Event.h"
+#include "core/mojo/testing/MojoInterfaceRequestEventInit.h"
+
+namespace blink {
+
+class MojoHandle;
+
+// An event dispatched to a MojoInterfaceInterceptor when its frame sends an
+// outgoing request for the interface it was configured to intercept. The event
+// includes the intercepted MojoHandle of the request pipe, which a listener may
+// use to bind the request to e.g. a mock interface implementation.
+class MojoInterfaceRequestEvent final : public Event {
+  DEFINE_WRAPPERTYPEINFO();
+
+ public:
+  ~MojoInterfaceRequestEvent() override;
+
+  static MojoInterfaceRequestEvent* Create(MojoHandle* handle) {
+    return new MojoInterfaceRequestEvent(handle);
+  }
+
+  static MojoInterfaceRequestEvent* Create(
+      const AtomicString& type,
+      const MojoInterfaceRequestEventInit& initializer) {
+    return new MojoInterfaceRequestEvent(type, initializer);
+  }
+
+  MojoHandle* handle() const { return handle_; }
+
+  const AtomicString& InterfaceName() const override {
+    return EventNames::MojoInterfaceRequestEvent;
+  }
+
+  DECLARE_VIRTUAL_TRACE();
+
+ private:
+  explicit MojoInterfaceRequestEvent(MojoHandle*);
+  MojoInterfaceRequestEvent(const AtomicString& type,
+                            const MojoInterfaceRequestEventInit&);
+
+  Member<MojoHandle> handle_;
+};
+
+}  // namespace blink
+
+#endif  // MojoInterfaceRequestEvent_h
diff --git a/third_party/WebKit/Source/core/mojo/testing/MojoInterfaceRequestEvent.idl b/third_party/WebKit/Source/core/mojo/testing/MojoInterfaceRequestEvent.idl
new file mode 100644
index 0000000..59bedea
--- /dev/null
+++ b/third_party/WebKit/Source/core/mojo/testing/MojoInterfaceRequestEvent.idl
@@ -0,0 +1,10 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+    Constructor(DOMString type, optional MojoInterfaceRequestEventInit eventInitDict),
+    RuntimeEnabled=MojoJSTest,
+] interface MojoInterfaceRequestEvent : Event {
+    readonly attribute MojoHandle handle;
+};
diff --git a/third_party/WebKit/Source/core/mojo/testing/MojoInterfaceRequestEventInit.idl b/third_party/WebKit/Source/core/mojo/testing/MojoInterfaceRequestEventInit.idl
new file mode 100644
index 0000000..3eaf1da
--- /dev/null
+++ b/third_party/WebKit/Source/core/mojo/testing/MojoInterfaceRequestEventInit.idl
@@ -0,0 +1,7 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+dictionary MojoInterfaceRequestEventInit : EventInit {
+    MojoHandle handle;
+};
diff --git a/third_party/WebKit/Source/devtools/front_end/resources/ApplicationPanelSidebar.js b/third_party/WebKit/Source/devtools/front_end/resources/ApplicationPanelSidebar.js
index 1c0ee67..6f7fdec 100644
--- a/third_party/WebKit/Source/devtools/front_end/resources/ApplicationPanelSidebar.js
+++ b/third_party/WebKit/Source/devtools/front_end/resources/ApplicationPanelSidebar.js
@@ -49,8 +49,8 @@
 
     this.contentElement.appendChild(this._sidebarTree.element);
     this._applicationTreeElement = this._addSidebarSection(Common.UIString('Application'));
-    var manifestTreeElement = new Resources.AppManifestTreeElement(panel);
-    this._applicationTreeElement.appendChild(manifestTreeElement);
+    this._manifestTreeElement = new Resources.AppManifestTreeElement(panel);
+    this._applicationTreeElement.appendChild(this._manifestTreeElement);
     this.serviceWorkersTreeElement = new Resources.ServiceWorkersTreeElement(panel);
     this._applicationTreeElement.appendChild(this.serviceWorkersTreeElement);
     var clearStorageTreeElement = new Resources.ClearStorageTreeElement(panel);
@@ -114,7 +114,7 @@
 
     var selection = this._panel.lastSelectedItemPath();
     if (!selection.length)
-      manifestTreeElement.select();
+      this._manifestTreeElement.select();
   }
 
   /**
diff --git a/third_party/WebKit/Source/devtools/front_end/resources/ClearStorageView.js b/third_party/WebKit/Source/devtools/front_end/resources/ClearStorageView.js
index 1911554..32cc5ff5 100644
--- a/third_party/WebKit/Source/devtools/front_end/resources/ClearStorageView.js
+++ b/third_party/WebKit/Source/devtools/front_end/resources/ClearStorageView.js
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 /**
  * @implements {SDK.TargetManager.Observer}
+ * @unrestricted
  */
 Resources.ClearStorageView = class extends UI.VBox {
   constructor() {
@@ -12,11 +13,6 @@
     this._reportView.registerRequiredCSS('resources/clearStorageView.css');
     this._reportView.element.classList.add('clear-storage-header');
     this._reportView.show(this.contentElement);
-    /** @type {?SDK.Target} */
-    this._target = null;
-    /** @type {?string} */
-    this._securityOrigin = null;
-    this._throttler = new Common.Throttler(1000);
 
     this._settings = new Map();
     for (var type
@@ -26,8 +22,6 @@
                  Protocol.Storage.StorageType.Websql])
       this._settings.set(type, Common.settings.createSetting('clear-storage-' + type, true));
 
-    var quota = this._reportView.appendSection(Common.UIString('Usage'));
-    this._quotaRow = quota.appendRow();
 
     var application = this._reportView.appendSection(Common.UIString('Application'));
     this._appendItem(application, Common.UIString('Unregister service workers'), 'service_workers');
@@ -47,9 +41,6 @@
     this._clearButton = UI.createTextButton(
         Common.UIString('Clear site data'), this._clear.bind(this), Common.UIString('Clear site data'));
     footer.appendChild(this._clearButton);
-
-
-    this._refreshQuota();
   }
 
   /**
@@ -105,8 +96,6 @@
   }
 
   _clear() {
-    if (!this._securityOrigin)
-      return;
     var storageTypes = [];
     for (var type of this._settings.keys()) {
       if (this._settings.get(type).get())
@@ -166,38 +155,4 @@
       this._clearButton.textContent = label;
     }, 500);
   }
-
-  /**
-   * @param {?Protocol.Storage.QuotaAndUsage} quotaAndUsage
-   */
-  _updateQuotaDisplay(quotaAndUsage) {
-    this._quotaRow.textContent = '';
-
-    if (!quotaAndUsage)
-      return;
-
-    this._quotaRow.textContent = Common.UIString(
-        '%s storage quota used out of %s', Number.bytesToString(quotaAndUsage.usage),
-        Number.bytesToString(quotaAndUsage.quota));
-  }
-
-  /**
-   * @return {!Promise<?>}
-   */
-  _refreshQuota() {
-    if (this.isShowing())
-      this._throttler.schedule(this._refreshQuota.bind(this));
-
-    if (!this._securityOrigin)
-      return Promise.resolve(true);
-
-    return this._target.storageAgent().getUsageAndQuota(this._securityOrigin).then(this._updateQuotaDisplay.bind(this));
-  }
-
-  /**
-   * @override
-   */
-  wasShown() {
-    this._refreshQuota();
-  }
 };
diff --git a/third_party/WebKit/Source/modules/modules_idl_files.gni b/third_party/WebKit/Source/modules/modules_idl_files.gni
index f6a9a02..2ebc247 100644
--- a/third_party/WebKit/Source/modules/modules_idl_files.gni
+++ b/third_party/WebKit/Source/modules/modules_idl_files.gni
@@ -282,7 +282,6 @@
                     "vr/VRStageParameters.idl",
                     "webaudio/AnalyserNode.idl",
                     "webaudio/AudioBuffer.idl",
-                    "webaudio/AudioBufferCallback.idl",
                     "webaudio/AudioBufferSourceNode.idl",
                     "webaudio/AudioContext.idl",
                     "webaudio/AudioDestinationNode.idl",
diff --git a/third_party/WebKit/Source/modules/webaudio/AsyncAudioDecoder.cpp b/third_party/WebKit/Source/modules/webaudio/AsyncAudioDecoder.cpp
index a6b08eba..18f830c 100644
--- a/third_party/WebKit/Source/modules/webaudio/AsyncAudioDecoder.cpp
+++ b/third_party/WebKit/Source/modules/webaudio/AsyncAudioDecoder.cpp
@@ -24,9 +24,11 @@
  */
 
 #include "modules/webaudio/AsyncAudioDecoder.h"
+
+#include "bindings/modules/v8/DecodeErrorCallback.h"
+#include "bindings/modules/v8/DecodeSuccessCallback.h"
 #include "core/dom/DOMArrayBuffer.h"
 #include "modules/webaudio/AudioBuffer.h"
-#include "modules/webaudio/AudioBufferCallback.h"
 #include "modules/webaudio/BaseAudioContext.h"
 #include "platform/CrossThreadFunctional.h"
 #include "platform/audio/AudioBus.h"
@@ -40,8 +42,8 @@
 
 void AsyncAudioDecoder::DecodeAsync(DOMArrayBuffer* audio_data,
                                     float sample_rate,
-                                    AudioBufferCallback* success_callback,
-                                    AudioBufferCallback* error_callback,
+                                    DecodeSuccessCallback* success_callback,
+                                    DecodeErrorCallback* error_callback,
                                     ScriptPromiseResolver* resolver,
                                     BaseAudioContext* context) {
   DCHECK(IsMainThread());
@@ -62,8 +64,8 @@
 void AsyncAudioDecoder::DecodeOnBackgroundThread(
     DOMArrayBuffer* audio_data,
     float sample_rate,
-    AudioBufferCallback* success_callback,
-    AudioBufferCallback* error_callback,
+    DecodeSuccessCallback* success_callback,
+    DecodeErrorCallback* error_callback,
     ScriptPromiseResolver* resolver,
     BaseAudioContext* context) {
   DCHECK(!IsMainThread());
@@ -89,8 +91,8 @@
 }
 
 void AsyncAudioDecoder::NotifyComplete(DOMArrayBuffer*,
-                                       AudioBufferCallback* success_callback,
-                                       AudioBufferCallback* error_callback,
+                                       DecodeSuccessCallback* success_callback,
+                                       DecodeErrorCallback* error_callback,
                                        AudioBus* audio_bus,
                                        ScriptPromiseResolver* resolver,
                                        BaseAudioContext* context) {
diff --git a/third_party/WebKit/Source/modules/webaudio/AsyncAudioDecoder.h b/third_party/WebKit/Source/modules/webaudio/AsyncAudioDecoder.h
index e5ba089..a5d739111 100644
--- a/third_party/WebKit/Source/modules/webaudio/AsyncAudioDecoder.h
+++ b/third_party/WebKit/Source/modules/webaudio/AsyncAudioDecoder.h
@@ -34,8 +34,9 @@
 
 class BaseAudioContext;
 class AudioBuffer;
-class AudioBufferCallback;
 class AudioBus;
+class DecodeErrorCallback;
+class DecodeSuccessCallback;
 class DOMArrayBuffer;
 class ScriptPromiseResolver;
 
@@ -58,8 +59,8 @@
   // appropriately when finished.
   void DecodeAsync(DOMArrayBuffer* audio_data,
                    float sample_rate,
-                   AudioBufferCallback* success_callback,
-                   AudioBufferCallback* error_callback,
+                   DecodeSuccessCallback*,
+                   DecodeErrorCallback*,
                    ScriptPromiseResolver*,
                    BaseAudioContext*);
 
@@ -67,13 +68,13 @@
   AudioBuffer* CreateAudioBufferFromAudioBus(AudioBus*);
   static void DecodeOnBackgroundThread(DOMArrayBuffer* audio_data,
                                        float sample_rate,
-                                       AudioBufferCallback* success_callback,
-                                       AudioBufferCallback* error_callback,
+                                       DecodeSuccessCallback*,
+                                       DecodeErrorCallback*,
                                        ScriptPromiseResolver*,
                                        BaseAudioContext*);
   static void NotifyComplete(DOMArrayBuffer* audio_data,
-                             AudioBufferCallback* success_callback,
-                             AudioBufferCallback* error_callback,
+                             DecodeSuccessCallback*,
+                             DecodeErrorCallback*,
                              AudioBus*,
                              ScriptPromiseResolver*,
                              BaseAudioContext*);
diff --git a/third_party/WebKit/Source/modules/webaudio/AudioBufferCallback.h b/third_party/WebKit/Source/modules/webaudio/AudioBufferCallback.h
deleted file mode 100644
index e8ccc98..0000000
--- a/third_party/WebKit/Source/modules/webaudio/AudioBufferCallback.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2011, Google Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1.  Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2.  Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
- * DAMAGE.
- */
-
-#ifndef AudioBufferCallback_h
-#define AudioBufferCallback_h
-
-#include "platform/heap/Handle.h"
-#include "platform/wtf/build_config.h"
-
-namespace blink {
-
-class AudioBuffer;
-class DOMException;
-
-class AudioBufferCallback
-    : public GarbageCollectedFinalized<AudioBufferCallback> {
- public:
-  virtual ~AudioBufferCallback() {}
-  DEFINE_INLINE_VIRTUAL_TRACE() {}
-  virtual void handleEvent(AudioBuffer*) = 0;
-  virtual void handleEvent(DOMException*) = 0;
-};
-
-}  // namespace blink
-
-#endif  // AudioBufferCallback_h
diff --git a/third_party/WebKit/Source/modules/webaudio/AudioBufferCallback.idl b/third_party/WebKit/Source/modules/webaudio/AudioBufferCallback.idl
deleted file mode 100644
index 83b627d..0000000
--- a/third_party/WebKit/Source/modules/webaudio/AudioBufferCallback.idl
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2011, Google Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1.  Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2.  Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
- * DAMAGE.
- */
-
-callback interface AudioBufferCallback {
-    void handleEvent(AudioBuffer audioBuffer);
-    void handleEvent(DOMException exception);
-};
diff --git a/third_party/WebKit/Source/modules/webaudio/AudioContext.cpp b/third_party/WebKit/Source/modules/webaudio/AudioContext.cpp
index 98dfc4dd..f0a5e2f0 100644
--- a/third_party/WebKit/Source/modules/webaudio/AudioContext.cpp
+++ b/third_party/WebKit/Source/modules/webaudio/AudioContext.cpp
@@ -13,7 +13,6 @@
 #include "core/frame/UseCounter.h"
 #include "core/timing/DOMWindowPerformance.h"
 #include "core/timing/Performance.h"
-#include "modules/webaudio/AudioBufferCallback.h"
 #include "modules/webaudio/AudioContextOptions.h"
 #include "modules/webaudio/AudioTimestamp.h"
 #include "modules/webaudio/DefaultAudioDestinationNode.h"
diff --git a/third_party/WebKit/Source/modules/webaudio/BUILD.gn b/third_party/WebKit/Source/modules/webaudio/BUILD.gn
index b10d919..8cfcd64 100644
--- a/third_party/WebKit/Source/modules/webaudio/BUILD.gn
+++ b/third_party/WebKit/Source/modules/webaudio/BUILD.gn
@@ -16,7 +16,6 @@
     "AudioBasicProcessorHandler.h",
     "AudioBuffer.cpp",
     "AudioBuffer.h",
-    "AudioBufferCallback.h",
     "AudioBufferSourceNode.cpp",
     "AudioBufferSourceNode.h",
     "AudioContext.cpp",
diff --git a/third_party/WebKit/Source/modules/webaudio/BaseAudioContext.cpp b/third_party/WebKit/Source/modules/webaudio/BaseAudioContext.cpp
index d8416e5..6bdb579e 100644
--- a/third_party/WebKit/Source/modules/webaudio/BaseAudioContext.cpp
+++ b/third_party/WebKit/Source/modules/webaudio/BaseAudioContext.cpp
@@ -29,6 +29,8 @@
 #include "bindings/core/v8/ExceptionMessages.h"
 #include "bindings/core/v8/ExceptionState.h"
 #include "bindings/core/v8/ScriptPromiseResolver.h"
+#include "bindings/modules/v8/DecodeErrorCallback.h"
+#include "bindings/modules/v8/DecodeSuccessCallback.h"
 #include "core/dom/DOMException.h"
 #include "core/dom/Document.h"
 #include "core/dom/ExceptionCode.h"
@@ -41,7 +43,6 @@
 #include "modules/mediastream/MediaStream.h"
 #include "modules/webaudio/AnalyserNode.h"
 #include "modules/webaudio/AudioBuffer.h"
-#include "modules/webaudio/AudioBufferCallback.h"
 #include "modules/webaudio/AudioBufferSourceNode.h"
 #include "modules/webaudio/AudioContext.h"
 #include "modules/webaudio/AudioListener.h"
@@ -280,8 +281,25 @@
 ScriptPromise BaseAudioContext::decodeAudioData(
     ScriptState* script_state,
     DOMArrayBuffer* audio_data,
-    AudioBufferCallback* success_callback,
-    AudioBufferCallback* error_callback,
+    ExceptionState& exception_state) {
+  return decodeAudioData(script_state, audio_data, nullptr, nullptr,
+                         exception_state);
+}
+
+ScriptPromise BaseAudioContext::decodeAudioData(
+    ScriptState* script_state,
+    DOMArrayBuffer* audio_data,
+    DecodeSuccessCallback* success_callback,
+    ExceptionState& exception_state) {
+  return decodeAudioData(script_state, audio_data, success_callback, nullptr,
+                         exception_state);
+}
+
+ScriptPromise BaseAudioContext::decodeAudioData(
+    ScriptState* script_state,
+    DOMArrayBuffer* audio_data,
+    DecodeSuccessCallback* success_callback,
+    DecodeErrorCallback* error_callback,
     ExceptionState& exception_state) {
   DCHECK(IsMainThread());
   DCHECK(audio_data);
@@ -302,6 +320,17 @@
     DOMArrayBuffer* audio = DOMArrayBuffer::Create(buffer_contents);
 
     decode_audio_resolvers_.insert(resolver);
+
+    // Add a reference to success_callback and error_callback so that
+    // they don't get collected prematurely before decodeAudioData
+    // calls them.
+    if (success_callback) {
+      success_callbacks_.emplace_back(this, success_callback);
+    }
+    if (error_callback) {
+      error_callbacks_.emplace_back(this, error_callback);
+    }
+
     audio_decoder_.DecodeAsync(audio, rate, success_callback, error_callback,
                                resolver, this);
   } else {
@@ -311,7 +340,7 @@
         kDataCloneError, "Cannot decode detached ArrayBuffer");
     resolver->Reject(error);
     if (error_callback) {
-      error_callback->handleEvent(error);
+      error_callback->call(this, error);
     }
   }
 
@@ -321,27 +350,45 @@
 void BaseAudioContext::HandleDecodeAudioData(
     AudioBuffer* audio_buffer,
     ScriptPromiseResolver* resolver,
-    AudioBufferCallback* success_callback,
-    AudioBufferCallback* error_callback) {
+    DecodeSuccessCallback* success_callback,
+    DecodeErrorCallback* error_callback) {
   DCHECK(IsMainThread());
 
   if (audio_buffer) {
     // Resolve promise successfully and run the success callback
     resolver->Resolve(audio_buffer);
     if (success_callback)
-      success_callback->handleEvent(audio_buffer);
+      success_callback->call(this, audio_buffer);
   } else {
     // Reject the promise and run the error callback
     DOMException* error =
         DOMException::Create(kEncodingError, "Unable to decode audio data");
     resolver->Reject(error);
     if (error_callback)
-      error_callback->handleEvent(error);
+      error_callback->call(this, error);
   }
 
   // We've resolved the promise.  Remove it now.
   DCHECK(decode_audio_resolvers_.Contains(resolver));
   decode_audio_resolvers_.erase(resolver);
+
+  // Find the success_callback and error_callback and remove it from
+  // our list so that it can be collected.  Remove any matching entry
+  // found (callback methods could be duplicated) which should be ok
+  // because we're only using this to hold a reference to the
+  // callback.
+
+  if (success_callback) {
+    size_t index = success_callbacks_.Find(success_callback);
+    DCHECK_NE(index, kNotFound);
+    success_callbacks_.erase(index);
+  }
+
+  if (error_callback) {
+    size_t index = error_callbacks_.Find(error_callback);
+    DCHECK_NE(index, kNotFound);
+    error_callbacks_.erase(index);
+  }
 }
 
 AudioBufferSourceNode* BaseAudioContext::createBufferSource(
@@ -951,6 +998,8 @@
   visitor->Trace(listener_);
   visitor->Trace(active_source_nodes_);
   visitor->Trace(resume_resolvers_);
+  visitor->Trace(success_callbacks_);
+  visitor->Trace(error_callbacks_);
   visitor->Trace(decode_audio_resolvers_);
 
   visitor->Trace(periodic_wave_sine_);
@@ -961,6 +1010,17 @@
   SuspendableObject::Trace(visitor);
 }
 
+DEFINE_TRACE_WRAPPERS(BaseAudioContext) {
+  // Inform V8's GC that we have references to these objects so they
+  // don't get collected until we're done with them.
+  for (auto callback : success_callbacks_) {
+    visitor->TraceWrappers(callback);
+  }
+  for (auto callback : error_callbacks_) {
+    visitor->TraceWrappers(callback);
+  }
+}
+
 SecurityOrigin* BaseAudioContext::GetSecurityOrigin() const {
   if (GetExecutionContext())
     return GetExecutionContext()->GetSecurityOrigin();
diff --git a/third_party/WebKit/Source/modules/webaudio/BaseAudioContext.h b/third_party/WebKit/Source/modules/webaudio/BaseAudioContext.h
index 7980fe1..13db3f7 100644
--- a/third_party/WebKit/Source/modules/webaudio/BaseAudioContext.h
+++ b/third_party/WebKit/Source/modules/webaudio/BaseAudioContext.h
@@ -41,6 +41,7 @@
 #include "modules/webaudio/IIRFilterNode.h"
 #include "platform/audio/AudioBus.h"
 #include "platform/bindings/ActiveScriptWrappable.h"
+#include "platform/bindings/TraceWrapperMember.h"
 #include "platform/heap/Handle.h"
 #include "platform/wtf/HashSet.h"
 #include "platform/wtf/RefPtr.h"
@@ -52,7 +53,6 @@
 
 class AnalyserNode;
 class AudioBuffer;
-class AudioBufferCallback;
 class AudioBufferSourceNode;
 class AudioContextOptions;
 class AudioListener;
@@ -62,6 +62,8 @@
 class ConstantSourceNode;
 class ConvolverNode;
 class DelayNode;
+class DecodeErrorCallback;
+class DecodeSuccessCallback;
 class Document;
 class DynamicsCompressorNode;
 class ExceptionState;
@@ -111,6 +113,8 @@
 
   DECLARE_VIRTUAL_TRACE();
 
+  DECLARE_VIRTUAL_TRACE_WRAPPERS();
+
   // Is the destination node initialized and ready to handle audio?
   bool IsDestinationInitialized() const {
     AudioDestinationNode* dest = destination();
@@ -169,16 +173,25 @@
   // Asynchronous audio file data decoding.
   ScriptPromise decodeAudioData(ScriptState*,
                                 DOMArrayBuffer* audio_data,
-                                AudioBufferCallback* success_callback,
-                                AudioBufferCallback* error_callback,
+                                DecodeSuccessCallback*,
+                                DecodeErrorCallback*,
+                                ExceptionState&);
+
+  ScriptPromise decodeAudioData(ScriptState*,
+                                DOMArrayBuffer* audio_data,
+                                ExceptionState&);
+
+  ScriptPromise decodeAudioData(ScriptState*,
+                                DOMArrayBuffer* audio_data,
+                                DecodeSuccessCallback*,
                                 ExceptionState&);
 
   // Handles the promise and callbacks when |decodeAudioData| is finished
   // decoding.
   void HandleDecodeAudioData(AudioBuffer*,
                              ScriptPromiseResolver*,
-                             AudioBufferCallback* success_callback,
-                             AudioBufferCallback* error_callback);
+                             DecodeSuccessCallback*,
+                             DecodeErrorCallback*);
 
   AudioListener* listener() { return listener_; }
 
@@ -467,6 +480,12 @@
 
   AsyncAudioDecoder audio_decoder_;
 
+  // Hold references to the |decodeAudioData| callbacks so that they
+  // don't get prematurely GCed by v8 before |decodeAudioData| returns
+  // and calls them.
+  HeapVector<TraceWrapperMember<DecodeSuccessCallback>> success_callbacks_;
+  HeapVector<TraceWrapperMember<DecodeErrorCallback>> error_callbacks_;
+
   // When a context is closed, the sample rate is cleared.  But decodeAudioData
   // can be called after the context has been closed and it needs the sample
   // rate.  When the context is closed, the sample rate is saved here.
diff --git a/third_party/WebKit/Source/modules/webaudio/BaseAudioContext.idl b/third_party/WebKit/Source/modules/webaudio/BaseAudioContext.idl
index d8d4d6d..f058169 100644
--- a/third_party/WebKit/Source/modules/webaudio/BaseAudioContext.idl
+++ b/third_party/WebKit/Source/modules/webaudio/BaseAudioContext.idl
@@ -3,12 +3,16 @@
 // found in the LICENSE file.
 
 // See https://webaudio.github.io/web-audio-api/#BaseAudioContext
+
 enum AudioContextState {
     "suspended",
     "running",
     "closed"
 };
 
+callback DecodeErrorCallback = void (DOMException error);
+callback DecodeSuccessCallback = void (AudioBuffer decodedData);
+
 [
     ActiveScriptWrappable,
     DependentLifetime,
@@ -31,7 +35,7 @@
     [RaisesException] AudioBuffer createBuffer(unsigned long numberOfChannels, unsigned long numberOfFrames, float sampleRate);
 
     // Asynchronous audio file data decoding.
-    [RaisesException, MeasureAs=AudioContextDecodeAudioData, CallWith=ScriptState] Promise<AudioBuffer> decodeAudioData(ArrayBuffer audioData, optional AudioBufferCallback successCallback, optional AudioBufferCallback errorCallback);
+    [RaisesException, MeasureAs=AudioContextDecodeAudioData, CallWith=ScriptState] Promise<AudioBuffer> decodeAudioData(ArrayBuffer audioData, optional DecodeSuccessCallback successCallback, optional DecodeErrorCallback  errorCallback);
 
     // Sources
     [RaisesException, MeasureAs=AudioContextCreateBufferSource] AudioBufferSourceNode createBufferSource();
diff --git a/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5 b/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5
index c85bf8f..29127f8 100644
--- a/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5
+++ b/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5
@@ -670,6 +670,12 @@
       name: "MojoJS",
       status: "test",
     },
+    // MojoJSTest is used exclusively in testing environments, whereas MojoJS
+    // may also be used elsewhere.
+    {
+      name: "MojoJSTest",
+      status: "test",
+    },
     {
       name: "MultipleColorStopPositions",
       status: "experimental",
diff --git a/third_party/WebKit/Source/web/LocalFrameClientImpl.cpp b/third_party/WebKit/Source/web/LocalFrameClientImpl.cpp
index a34476b..344ac29 100644
--- a/third_party/WebKit/Source/web/LocalFrameClientImpl.cpp
+++ b/third_party/WebKit/Source/web/LocalFrameClientImpl.cpp
@@ -1052,4 +1052,9 @@
   return web_frame_->CreateURLLoader();
 }
 
+service_manager::InterfaceProvider*
+LocalFrameClientImpl::GetInterfaceProvider() {
+  return web_frame_->Client()->GetInterfaceProvider();
+}
+
 }  // namespace blink
diff --git a/third_party/WebKit/Source/web/LocalFrameClientImpl.h b/third_party/WebKit/Source/web/LocalFrameClientImpl.h
index 5e1b0a19..6dff5c8 100644
--- a/third_party/WebKit/Source/web/LocalFrameClientImpl.h
+++ b/third_party/WebKit/Source/web/LocalFrameClientImpl.h
@@ -229,6 +229,8 @@
 
   std::unique_ptr<WebURLLoader> CreateURLLoader() override;
 
+  service_manager::InterfaceProvider* GetInterfaceProvider() override;
+
  private:
   explicit LocalFrameClientImpl(WebLocalFrameBase*);
 
diff --git a/third_party/WebKit/Source/web/tests/FrameTestHelpers.cpp b/third_party/WebKit/Source/web/tests/FrameTestHelpers.cpp
index 3d01f98..fff97d9d 100644
--- a/third_party/WebKit/Source/web/tests/FrameTestHelpers.cpp
+++ b/third_party/WebKit/Source/web/tests/FrameTestHelpers.cpp
@@ -160,8 +160,10 @@
 
   WebLocalFrameBase* frame = ToWebLocalFrameBase(parent->CreateLocalChild(
       WebTreeScopeType::kDocument, name, WebSandboxFlags::kNone, client,
-      static_cast<TestWebFrameClient*>(client)->GetInterfaceProvider(), nullptr,
-      previous_sibling, WebParsedFeaturePolicy(), properties, nullptr));
+      static_cast<TestWebFrameClient*>(client)
+          ->GetInterfaceProviderForTesting(),
+      nullptr, previous_sibling, WebParsedFeaturePolicy(), properties,
+      nullptr));
 
   if (!widget_client)
     widget_client = DefaultWebWidgetClient();
@@ -221,7 +223,7 @@
   web_view_->SetDefaultPageScaleLimits(1, 4);
   WebLocalFrame* frame = WebLocalFrameBase::Create(
       WebTreeScopeType::kDocument, web_frame_client,
-      web_frame_client->GetInterfaceProvider(), nullptr, opener);
+      web_frame_client->GetInterfaceProviderForTesting(), nullptr, opener);
   web_view_->SetMainFrame(frame);
   web_frame_client->SetFrame(frame);
   // TODO(dcheng): The main frame widget currently has a special case.
@@ -293,8 +295,8 @@
     WebSandboxFlags sandbox_flags,
     const WebParsedFeaturePolicy& container_policy,
     const WebFrameOwnerProperties& frame_owner_properties) {
-  WebLocalFrame* frame =
-      parent->CreateLocalChild(scope, this, GetInterfaceProvider(), nullptr);
+  WebLocalFrame* frame = parent->CreateLocalChild(
+      scope, this, GetInterfaceProviderForTesting(), nullptr);
   return frame;
 }
 
diff --git a/third_party/WebKit/Source/web/tests/FrameTestHelpers.h b/third_party/WebKit/Source/web/tests/FrameTestHelpers.h
index e5477af..1d5b0350 100644
--- a/third_party/WebKit/Source/web/tests/FrameTestHelpers.h
+++ b/third_party/WebKit/Source/web/tests/FrameTestHelpers.h
@@ -274,7 +274,9 @@
   bool IsLoading() { return loads_in_progress_ > 0; }
 
   // Tests can override the virtual method below to mock the interface provider.
-  virtual blink::InterfaceProvider* GetInterfaceProvider() { return nullptr; }
+  virtual blink::InterfaceProvider* GetInterfaceProviderForTesting() {
+    return nullptr;
+  }
 
   std::unique_ptr<blink::WebURLLoader> CreateURLLoader() override {
     // TODO(yhirano): Stop using Platform::CreateURLLoader() here.
diff --git a/third_party/WebKit/Source/web/tests/ScreenWakeLockTest.cpp b/third_party/WebKit/Source/web/tests/ScreenWakeLockTest.cpp
index 12b1a754af..b7a280f 100644
--- a/third_party/WebKit/Source/web/tests/ScreenWakeLockTest.cpp
+++ b/third_party/WebKit/Source/web/tests/ScreenWakeLockTest.cpp
@@ -71,7 +71,7 @@
 class TestWebFrameClient : public FrameTestHelpers::TestWebFrameClient {
  public:
   ~TestWebFrameClient() override = default;
-  InterfaceProvider* GetInterfaceProvider() override {
+  InterfaceProvider* GetInterfaceProviderForTesting() override {
     return &interface_provider_;
   }
 
@@ -121,7 +121,7 @@
 
   bool ClientKeepScreenAwake() {
     return static_cast<MockInterfaceProvider*>(
-               test_web_frame_client_.GetInterfaceProvider())
+               test_web_frame_client_.GetInterfaceProviderForTesting())
         ->WakeLockStatus();
   }
 
diff --git a/third_party/WebKit/Tools/Scripts/run-perf-tests b/third_party/WebKit/Tools/Scripts/run-perf-tests
index 667af3e..f85e496 100755
--- a/third_party/WebKit/Tools/Scripts/run-perf-tests
+++ b/third_party/WebKit/Tools/Scripts/run-perf-tests
@@ -37,4 +37,10 @@
 if '__main__' == __name__:
     logging.basicConfig(level=logging.INFO, format="%(message)s")
 
+    raw_input(
+        '\nWARNING: this script will be removed on 6/20/2017. '
+        'Please run "src/tools/perf/run_benchmark blink_perf" instead.\n'
+        'For more details, see the announcement in '
+        'https://groups.google.com/a/chromium.org/forum/#!topic/blink-dev/mpZmEfeQPps\n'
+        'Press enter to keep running this anyway.')
     sys.exit(PerfTestsRunner().run())
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/common/system/executive_mock.py b/third_party/WebKit/Tools/Scripts/webkitpy/common/system/executive_mock.py
index 262a4b1..b02a45f 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/common/system/executive_mock.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/common/system/executive_mock.py
@@ -38,12 +38,12 @@
 
 class MockProcess(object):
 
-    def __init__(self, stdout='MOCK STDOUT\n', stderr=''):
+    def __init__(self, stdout='MOCK STDOUT\n', stderr='', returncode=0):
         self.pid = 42
         self.stdout = StringIO.StringIO(stdout)
         self.stderr = StringIO.StringIO(stderr)
         self.stdin = StringIO.StringIO()
-        self.returncode = 0
+        self.returncode = returncode
 
     def wait(self):
         return
@@ -75,7 +75,8 @@
 
     def __init__(self, should_log=False, should_throw=False,
                  output='MOCK output of child process', stderr='',
-                 exit_code=0, exception=None, run_command_fn=None):
+                 exit_code=0, exception=None, run_command_fn=None,
+                 proc=None):
         self._should_log = should_log
         self._should_throw = should_throw
         # FIXME: Once executive wraps os.getpid() we can just use a static pid for "this" process.
@@ -85,7 +86,7 @@
         self._exit_code = exit_code
         self._exception = exception
         self._run_command_fn = run_command_fn
-        self._proc = None
+        self._proc = proc
         self.full_calls = []
 
     def _append_call(self, args, env=None):
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/common/system/executive_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/common/system/executive_unittest.py
index f457650..63de883 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/common/system/executive_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/common/system/executive_unittest.py
@@ -80,12 +80,15 @@
     def test_run_command_with_bad_command(self):
         def run_bad_command():
             Executive().run_command(['foo_bar_command_blah'], error_handler=Executive.ignore_error, return_exit_code=True)
-        self.assertRaises(OSError, run_bad_command)
+        with self.assertRaises(OSError):
+            run_bad_command()
 
     def test_run_command_args_type(self):
         executive = Executive()
-        self.assertRaises(AssertionError, executive.run_command, 'echo')
-        self.assertRaises(AssertionError, executive.run_command, u"echo")
+        with self.assertRaises(AssertionError):
+            executive.run_command('echo')
+        with self.assertRaises(AssertionError):
+            executive.run_command(u'echo')
         executive.run_command(command_line('echo', 'foo'))
         executive.run_command(tuple(command_line('echo', 'foo')))
 
@@ -147,7 +150,8 @@
 
         def timeout():
             executive.run_command(command_line('sleep', 'infinity'), timeout_seconds=0.01)
-        self.assertRaises(ScriptError, timeout)
+        with self.assertRaises(ScriptError):
+            timeout()
 
     def test_timeout_exceeded_exit_code(self):
         executive = Executive()
@@ -171,7 +175,8 @@
         self.assertIn(os.getpid(), pids)
 
     def test_run_in_parallel_assert_nonempty(self):
-        self.assertRaises(AssertionError, Executive().run_in_parallel, [])
+        with self.assertRaises(AssertionError):
+            Executive().run_in_parallel([])
 
 
 def main(platform, stdin, stdout, cmd, args):
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/common/system/filesystem_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/common/system/filesystem_unittest.py
index 858c47c8..140a97a 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/common/system/filesystem_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/common/system/filesystem_unittest.py
@@ -175,7 +175,8 @@
         newdir = '/dirdoesnotexist'
         if sys.platform == 'win32':
             newdir = 'c:\\dirdoesnotexist'
-        self.assertRaises(OSError, fs.chdir, newdir)
+        with self.assertRaises(OSError):
+            fs.chdir(newdir)
 
     def test_exists__true(self):
         fs = FileSystem()
@@ -256,7 +257,8 @@
 
             # Now try to create a sub directory - should fail.
             sub_dir = fs.join(d, 'subdir')
-            self.assertRaises(OSError, fs.maybe_make_directory, sub_dir)
+            with self.assertRaises(OSError):
+                fs.maybe_make_directory(sub_dir)
 
             # Clean up in case the test failed and we did create the
             # directory.
@@ -308,11 +310,13 @@
 
     def test_read_binary_file__missing(self):
         fs = FileSystem()
-        self.assertRaises(IOError, fs.read_binary_file, self._missing_file)
+        with self.assertRaises(IOError):
+            fs.read_binary_file(self._missing_file)
 
     def test_read_text_file__missing(self):
         fs = FileSystem()
-        self.assertRaises(IOError, fs.read_text_file, self._missing_file)
+        with self.assertRaises(IOError):
+            fs.read_text_file(self._missing_file)
 
     def test_remove_file_with_retry(self):
         RealFileSystemTest._remove_failures = 2
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/common/system/platform_info_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/common/system/platform_info_unittest.py
index a1e239a..095cea5 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/common/system/platform_info_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/common/system/platform_info_unittest.py
@@ -129,10 +129,12 @@
         self.assertFalse(info.is_win())
         self.assertTrue(info.is_freebsd())
 
-        self.assertRaises(AssertionError, self.make_info, fake_sys('vms'))
+        with self.assertRaises(AssertionError):
+            self.make_info(fake_sys('vms'))
 
     def test_os_version(self):
-        self.assertRaises(AssertionError, self.make_info, fake_sys('darwin'), fake_platform('10.6.3'))
+        with self.assertRaises(AssertionError):
+            self.make_info(fake_sys('darwin'), fake_platform('10.6.3'))
         self.assertEqual(self.make_info(fake_sys('darwin'), fake_platform('10.9.0')).os_version, 'mac10.9')
         self.assertEqual(self.make_info(fake_sys('darwin'), fake_platform('10.10.0')).os_version, 'mac10.10')
         self.assertEqual(self.make_info(fake_sys('darwin'), fake_platform('10.11.0')).os_version, 'mac10.11')
@@ -146,8 +148,10 @@
         self.assertEqual(self.make_info(fake_sys('freebsd8'), fake_platform('', '8.3-PRERELEASE')).os_version, '8.3-PRERELEASE')
         self.assertEqual(self.make_info(fake_sys('freebsd9'), fake_platform('', '9.0-RELEASE')).os_version, '9.0-RELEASE')
 
-        self.assertRaises(AssertionError, self.make_info, fake_sys('win32', tuple([5, 0, 1234])))
-        self.assertRaises(AssertionError, self.make_info, fake_sys('win32', tuple([6, 1, 1234])))
+        with self.assertRaises(AssertionError):
+            self.make_info(fake_sys('win32', tuple([5, 0, 1234])))
+        with self.assertRaises(AssertionError):
+            self.make_info(fake_sys('win32', tuple([6, 1, 1234])))
         self.assertEqual(self.make_info(fake_sys('win32', tuple([10, 1, 1234]))).os_version, 'future')
         self.assertEqual(self.make_info(fake_sys('win32', tuple([10, 0, 1234]))).os_version, '10')
         self.assertEqual(self.make_info(fake_sys('win32', tuple([6, 3, 1234]))).os_version, '8.1')
@@ -157,10 +161,10 @@
         self.assertEqual(self.make_info(fake_sys('win32', tuple([6, 0, 1234]))).os_version, 'vista')
         self.assertEqual(self.make_info(fake_sys('win32', tuple([5, 1, 1234]))).os_version, 'xp')
 
-        self.assertRaises(AssertionError, self.make_info, fake_sys('win32'),
-                          executive=fake_executive('5.0.1234'))
-        self.assertRaises(AssertionError, self.make_info, fake_sys('win32'),
-                          executive=fake_executive('6.1.1234'))
+        with self.assertRaises(AssertionError):
+            self.make_info(fake_sys('win32'), executive=fake_executive('5.0.1234'))
+        with self.assertRaises(AssertionError):
+            self.make_info(fake_sys('win32'), executive=fake_executive('6.1.1234'))
 
     def _assert_file_implies_linux_distribution(self, file_path, distribution):
         info = self.make_info(sys_module=fake_sys('linux2'), filesystem_module=MockFileSystem({file_path: ''}))
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/layout_test_runner_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/layout_test_runner_unittest.py
index 6eae779..d1128af 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/layout_test_runner_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/layout_test_runner_unittest.py
@@ -96,6 +96,8 @@
 
 class LayoutTestRunnerTests(unittest.TestCase):
 
+    # pylint: disable=protected-access
+
     def _runner(self, port=None):
         # FIXME: we shouldn't have to use run_webkit_tests.py to get the options we need.
         options = run_webkit_tests.parse_args(['--platform', 'test-mac-mac10.11'])[0]
@@ -115,7 +117,7 @@
         runner._options.exit_after_n_failures = None
         runner._options.exit_after_n_crashes_or_times = None
         test_names = ['passes/text.html', 'passes/image.html']
-        runner._test_inputs = [TestInput(test_name, timeout_ms=6000) for test_name in test_names]  # pylint: disable=protected-access
+        runner._test_inputs = [TestInput(test_name, timeout_ms=6000) for test_name in test_names]
 
         run_results = TestRunResults(TestExpectations(runner._port, test_names), len(test_names))
         run_results.unexpected_failures = 100
@@ -131,13 +133,15 @@
 
         # Interrupt if we've exceeded either limit:
         runner._options.exit_after_n_crashes_or_timeouts = 10
-        self.assertRaises(TestRunInterruptedException, runner._interrupt_if_at_failure_limits, run_results)
+        with self.assertRaises(TestRunInterruptedException):
+            runner._interrupt_if_at_failure_limits(run_results)
         self.assertEqual(run_results.results_by_name['passes/text.html'].type, test_expectations.SKIP)
         self.assertEqual(run_results.results_by_name['passes/image.html'].type, test_expectations.SKIP)
 
         runner._options.exit_after_n_crashes_or_timeouts = None
         runner._options.exit_after_n_failures = 10
-        self.assertRaises(TestRunInterruptedException, runner._interrupt_if_at_failure_limits, run_results)
+        with self.assertRaises(TestRunInterruptedException):
+            runner._interrupt_if_at_failure_limits(run_results)
 
     def test_update_summary_with_result(self):
         # Reftests expected to be image mismatch should be respected when pixel_tests=False.
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py
index 35ab4247..95c34cc 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py
@@ -221,6 +221,8 @@
 
             results_path = self._filesystem.join(self._results_directory, 'results.html')
             self._copy_results_html_file(results_path)
+            expectations_path = self._filesystem.join(self._results_directory, 'test-expectations.html')
+            self._copy_testexpectations_html_file(expectations_path)
             if initial_results.keyboard_interrupted:
                 exit_code = exit_codes.INTERRUPTED_EXIT_STATUS
             else:
@@ -491,6 +493,11 @@
         full_results_path = self._filesystem.join(self._results_directory, 'full_results.json')
         json_results_generator.write_json(self._filesystem, summarized_full_results, full_results_path)
 
+        full_results_jsonp_path = self._filesystem.join(self._results_directory, 'full_results_jsonp.js')
+        json_results_generator.write_json(self._filesystem,
+                                          summarized_full_results,
+                                          full_results_jsonp_path,
+                                          callback='ADD_FULL_RESULTS')
         full_results_path = self._filesystem.join(self._results_directory, 'failing_results.json')
         # We write failing_results.json out as jsonp because we need to load it
         # from a file url for results.html and Chromium doesn't allow that.
@@ -548,6 +555,12 @@
         if self._filesystem.exists(results_file):
             self._filesystem.copyfile(results_file, destination_path)
 
+    def _copy_testexpectations_html_file(self, destination_path):
+        base_dir = self._path_finder.path_from_layout_tests('fast', 'harness')
+        expectations_file = self._filesystem.join(base_dir, 'test-expectations.html')
+        if self._filesystem.exists(expectations_file):
+            self._filesystem.copyfile(expectations_file, destination_path)
+
     def _stats_trie(self, initial_results):
         def _worker_number(worker_name):
             return int(worker_name.split('/')[1]) if worker_name else -1
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/merge_results.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/merge_results.py
index b806719..9f1d5ee 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/merge_results.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/merge_results.py
@@ -621,3 +621,6 @@
         self.add_helper(
             FilenameRegexMatch(r'output\.json$'),
             results_json_file_merger)
+        self.add_helper(
+            FilenameRegexMatch(r'full_results_jsonp\.js$'),
+            results_json_file_merger)
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/merge_results_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/merge_results_unittest.py
index d6a0a03..9e95452 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/merge_results_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/merge_results_unittest.py
@@ -77,8 +77,8 @@
         for expected, (inputa, inputb) in tests:
             self.assertDictEqual(expected, m.merge_dictlike([inputa, inputb]))
 
-        self.assertRaises(
-            merge_results.MergeFailure, m.merge_dictlike, [{'a': 1}, {'a': 2}])
+        with self.assertRaises(merge_results.MergeFailure):
+            m.merge_dictlike([{'a': 1}, {'a': 2}])
 
     def test_merge_compound_dict(self):
         m = merge_results.JSONMerger()
@@ -145,8 +145,8 @@
         for expected, (inputa, inputb) in tests:
             self.assertEqual(expected, m.merge([inputa, inputb]))
 
-        self.assertRaises(
-            merge_results.MergeFailure, m.merge, [{'a': 1}, {'a': 2}])
+        with self.assertRaises(merge_results.MergeFailure):
+            m.merge([{'a': 1}, {'a': 2}])
 
         # Ordered values
         a = OrderedDict({'a': 1})
@@ -175,8 +175,8 @@
             lambda o, name=None: sum(o))
 
         self.assertDictEqual({'a': 3}, m.merge([{'a': 1}, {'a': 2}]))
-        self.assertRaises(
-            merge_results.MergeFailure, m.merge, [{'b': 1}, {'b': 2}])
+        with self.assertRaises(merge_results.MergeFailure):
+            m.merge([{'b': 1}, {'b': 2}])
 
         # Test that helpers that are added later have precedence.
         m.add_helper(
@@ -203,8 +203,8 @@
         self.assertDictEqual({'a': 6}, m.merge([{'a': 3}, {'a': 3}]))
         self.assertDictEqual({'a': 5}, m.merge([{'a': 2}, {'a': 3}]))
         self.assertDictEqual({'a': 7}, m.merge([{'a': 3}, {'a': 4}]))
-        self.assertRaises(
-            merge_results.MergeFailure, m.merge, {'a': 1}, {'a': 2})
+        with self.assertRaises(merge_results.MergeFailure):
+            m.merge([{'a': 1}, {'a': 2}])
 
 
 class MergeFilesOneTests(FileSystemTestCase):
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_configuration_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_configuration_unittest.py
index b1fabcd..b4b4db36 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_configuration_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_configuration_unittest.py
@@ -82,7 +82,8 @@
         def query_unknown_key():
             return config_dict[TestConfiguration('win7', 'x86', 'debug')]
 
-        self.assertRaises(KeyError, query_unknown_key)
+        with self.assertRaises(KeyError):
+            query_unknown_key()
         self.assertIn(TestConfiguration('win7', 'x86', 'release'), config_dict)
         self.assertNotIn(TestConfiguration('win7', 'x86', 'debug'), config_dict)
         configs_list = [TestConfiguration('win7', 'x86', 'release'), TestConfiguration(
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_expectations_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_expectations_unittest.py
index 9ffc1a2..8839328 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_expectations_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_expectations_unittest.py
@@ -91,7 +91,8 @@
         self.assert_exp_list(test, [result])
 
     def assert_bad_expectations(self, expectations, overrides=None):
-        self.assertRaises(ParseError, self.parse_exp, expectations, is_lint_mode=True, overrides=overrides)
+        with self.assertRaises(ParseError):
+            self.parse_exp(expectations, is_lint_mode=True, overrides=overrides)
 
 
 class BasicTests(Base):
@@ -170,8 +171,8 @@
         self.parse_exp(exp_str)
         test_name = 'failures/expected/unknown-test.html'
         unknown_test = test_name
-        self.assertRaises(KeyError, self._exp.get_expectations,
-                          unknown_test)
+        with self.assertRaises(KeyError):
+            self._exp.get_expectations(unknown_test)
         self.assert_exp_list('failures/expected/crash.html', [PASS])
 
     def test_get_expectations_string(self):
@@ -181,8 +182,8 @@
     def test_expectation_to_string(self):
         # Normal cases are handled by other tests.
         self.parse_exp(self.get_basic_expectations())
-        self.assertRaises(ValueError, self._exp.expectation_to_string,
-                          -1)
+        with self.assertRaises(ValueError):
+            self._exp.expectation_to_string(-1)
 
     def test_get_test_set(self):
         # Handle some corner cases for this routine not covered by other tests.
@@ -288,21 +289,19 @@
 
     def test_error_on_different_platform(self):
         # parse_exp uses a Windows port. Assert errors on Mac show up in lint mode.
-        self.assertRaises(
-            ParseError,
-            self.parse_exp,
-            ('Bug(test) [ Mac ] failures/expected/text.html [ Failure ]\n'
-             'Bug(test) [ Mac ] failures/expected/text.html [ Failure ]'),
-            is_lint_mode=True)
+        with self.assertRaises(ParseError):
+            self.parse_exp(
+                'Bug(test) [ Mac ] failures/expected/text.html [ Failure ]\n'
+                'Bug(test) [ Mac ] failures/expected/text.html [ Failure ]',
+                is_lint_mode=True)
 
     def test_error_on_different_build_type(self):
         # parse_exp uses a Release port. Assert errors on DEBUG show up in lint mode.
-        self.assertRaises(
-            ParseError,
-            self.parse_exp,
-            ('Bug(test) [ Debug ] failures/expected/text.html [ Failure ]\n'
-             'Bug(test) [ Debug ] failures/expected/text.html [ Failure ]'),
-            is_lint_mode=True)
+        with self.assertRaises(ParseError):
+            self.parse_exp(
+                'Bug(test) [ Debug ] failures/expected/text.html [ Failure ]\n'
+                'Bug(test) [ Debug ] failures/expected/text.html [ Failure ]',
+                is_lint_mode=True)
 
     def test_overrides(self):
         self.parse_exp('Bug(exp) failures/expected/text.html [ Failure ]',
@@ -417,8 +416,10 @@
         self.check(expectations='', overrides=None, skips=['failures/expected/text.html'], expected_results=[WONTFIX, SKIP])
 
     def test_duplicate_skipped_test_fails_lint(self):
-        self.assertRaises(ParseError, self.check, expectations='Bug(x) failures/expected/text.html [ Failure ]\n',
-                          overrides=None, skips=['failures/expected/text.html'], lint=True)
+        with self.assertRaises(ParseError):
+            self.check(
+                expectations='Bug(x) failures/expected/text.html [ Failure ]\n',
+                overrides=None, skips=['failures/expected/text.html'], lint=True)
 
     def test_skipped_file_overrides_expectations(self):
         self.check(expectations='Bug(x) failures/expected/text.html [ Failure ]\n', overrides=None,
@@ -538,7 +539,8 @@
 class SemanticTests(Base):
 
     def test_bug_format(self):
-        self.assertRaises(ParseError, self.parse_exp, 'BUG1234 failures/expected/text.html [ Failure ]', is_lint_mode=True)
+        with self.assertRaises(ParseError):
+            self.parse_exp('BUG1234 failures/expected/text.html [ Failure ]', is_lint_mode=True)
 
     def test_bad_bugid(self):
         try:
@@ -570,17 +572,18 @@
 
     def test_rebaseline(self):
         # Can't lint a file w/ 'REBASELINE' in it.
-        self.assertRaises(ParseError, self.parse_exp,
-                          'Bug(test) failures/expected/text.html [ Failure Rebaseline ]',
-                          is_lint_mode=True)
+        with self.assertRaises(ParseError):
+            self.parse_exp(
+                'Bug(test) failures/expected/text.html [ Failure Rebaseline ]',
+                is_lint_mode=True)
 
     def test_duplicates(self):
         self.assertRaises(ParseError, self.parse_exp, """
 Bug(exp) failures/expected/text.html [ Failure ]
 Bug(exp) failures/expected/text.html [ Timeout ]""", is_lint_mode=True)
-
-        self.assertRaises(ParseError, self.parse_exp,
-                          self.get_basic_expectations(), overrides="""
+        with self.assertRaises(ParseError):
+            self.parse_exp(
+                self.get_basic_expectations(), overrides="""
 Bug(override) failures/expected/text.html [ Failure ]
 Bug(override) failures/expected/text.html [ Timeout ]""", is_lint_mode=True)
 
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_failures_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_failures_unittest.py
index 9f160972..aea1fb2 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_failures_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_failures_unittest.py
@@ -54,11 +54,13 @@
                 return ''
 
         failure_obj = UnknownFailure()
-        self.assertRaises(ValueError, determine_result_type, [failure_obj])
+        with self.assertRaises(ValueError):
+            determine_result_type([failure_obj])
 
     def test_message_is_virtual(self):
         failure_obj = TestFailure()
-        self.assertRaises(NotImplementedError, failure_obj.message)
+        with self.assertRaises(NotImplementedError):
+            failure_obj.message()
 
     def test_loads(self):
         for c in ALL_FAILURE_CLASSES:
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/android_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/android_unittest.py
index 1fb15ff..076f591 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/android_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/android_unittest.py
@@ -104,7 +104,8 @@
 
     # Test that content_shell currently is the only supported driver.
     def test_non_content_shell_driver(self):
-        self.assertRaises(self.make_port, options=optparse.Values({'driver_name': 'foobar'}))
+        with self.assertRaises(Exception):
+            self.make_port(options=optparse.Values({'driver_name': 'foobar'}))
 
     # Test that the number of child processes to create depends on the devices.
     def test_default_child_processes(self):
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/factory_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/factory_unittest.py
index f1eb93e6..47fca50 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/factory_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/factory_unittest.py
@@ -69,10 +69,12 @@
                          cls=win.WinPort)
 
     def test_unknown_specified(self):
-        self.assertRaises(NotImplementedError, factory.PortFactory(MockHost()).get, port_name='unknown')
+        with self.assertRaises(NotImplementedError):
+            factory.PortFactory(MockHost()).get(port_name='unknown')
 
     def test_unknown_default(self):
-        self.assertRaises(NotImplementedError, factory.PortFactory(MockHost(os_name='vms')).get)
+        with self.assertRaises(NotImplementedError):
+            factory.PortFactory(MockHost(os_name='vms')).get()
 
     def test_get_from_builder_name(self):
         self.assertEqual(factory.PortFactory(MockHost()).get_from_builder_name('WebKit Mac10.11').name(),
@@ -147,8 +149,10 @@
         self.assertEqual(port._options.target, 'foo')
 
     def test_unknown_dir(self):
-        self.assertRaises(ValueError, self.get_port, target='unknown')
+        with self.assertRaises(ValueError):
+            self.get_port(target='unknown')
 
     def test_both_configuration_and_target_is_an_error(self):
-        self.assertRaises(ValueError, self.get_port, target='Debug', configuration='Release',
+        with self.assertRaises(ValueError):
+            self.get_port(target='Debug', configuration='Release',
                           files={'out/Debug/toolchain.ninja': ''})
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/linux.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/linux.py
index b61674b..b040c7a 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/linux.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/linux.py
@@ -184,7 +184,9 @@
         # While xvfb is running, the poll() method will return None;
         # https://docs.python.org/2/library/subprocess.html#subprocess.Popen.poll
         start_time = self.host.time()
-        while self.host.time() - start_time < self.XVFB_START_TIMEOUT or self._xvfb_process.poll() is not None:
+        while self.host.time() - start_time < self.XVFB_START_TIMEOUT:
+            if self._xvfb_process.poll() is not None:
+                break
             # We don't explicitly set the display, as we want to check the
             # environment value.
             exit_code = self.host.executive.run_command(
@@ -217,7 +219,7 @@
             self._xvfb_stdout.close()
         if self._xvfb_stderr:
             self._xvfb_stderr.close()
-        if self._xvfb_process:
+        if self._xvfb_process and self._xvfb_process.poll() is None:
             _log.debug('Killing Xvfb process pid %d.', self._xvfb_process.pid)
             self._xvfb_process.kill()
             self._xvfb_process.wait()
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/linux_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/linux_unittest.py
index 0855e08..d39df533 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/linux_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/linux_unittest.py
@@ -26,15 +26,17 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+import logging
 import optparse
 
-from webkitpy.common.system.executive_mock import MockExecutive
+from webkitpy.common.system.executive_mock import MockExecutive, MockProcess
 from webkitpy.common.system.system_host_mock import MockSystemHost
 from webkitpy.layout_tests.port import linux
 from webkitpy.layout_tests.port import port_testcase
+from webkitpy.common.system.log_testing import LoggingTestCase
 
 
-class LinuxPortTest(port_testcase.PortTestCase):
+class LinuxPortTest(port_testcase.PortTestCase, LoggingTestCase):
     os_name = 'linux'
     os_version = 'trusty'
     port_name = 'linux'
@@ -57,8 +59,8 @@
 
         self.assert_version_properties('linux', 'trusty', 'linux-trusty', 'trusty')
         self.assert_version_properties('linux-trusty', None, 'linux-trusty', 'trusty')
-        self.assertRaises(AssertionError, self.assert_version_properties,
-                          'linux-utopic', None, 'ignored', 'ignored', 'ignored')
+        with self.assertRaises(AssertionError):
+            self.assert_version_properties('linux-utopic', None, 'ignored', 'ignored', 'ignored')
 
     def assert_baseline_paths(self, port_name, os_version, *expected_paths):
         port = self.make_port(port_name=port_name, os_version=os_version)
@@ -76,7 +78,8 @@
     def test_check_illegal_port_names(self):
         # FIXME: Check that, for now, these are illegal port names.
         # Eventually we should be able to do the right thing here.
-        self.assertRaises(AssertionError, linux.LinuxPort, MockSystemHost(), port_name='linux-x86')
+        with self.assertRaises(AssertionError):
+            linux.LinuxPort(MockSystemHost(), port_name='linux-x86')
 
     def test_operating_system(self):
         self.assertEqual('linux', self.make_port().operating_system())
@@ -145,8 +148,7 @@
 
         port = self.make_port()
         port.host.environ['TMPDIR'] = '/foo/bar'
-        port.host.executive = MockExecutive(
-            run_command_fn=run_command_fake)
+        port.host.executive = MockExecutive(run_command_fn=run_command_fake)
         port.setup_test_run()
 
         self.assertEqual(
@@ -214,7 +216,7 @@
         env = port.setup_environ_for_server()
         self.assertEqual(env['DISPLAY'], ':99')
 
-    def test_setup_test_runs_eventually_failes_on_failure(self):
+    def test_setup_test_runs_eventually_times_out(self):
         def run_command_fake(args):
             if args[0] == 'xdpyinfo':
                 return 1
@@ -224,6 +226,8 @@
         port = self.make_port(host=host)
         port.host.executive = MockExecutive(
             run_command_fn=run_command_fake)
+        self.set_logging_level(logging.DEBUG)
+
         port.setup_test_run()
         self.assertEqual(
             port.host.executive.calls,
@@ -233,3 +237,38 @@
             ] + [['xdpyinfo']] * 51)
         env = port.setup_environ_for_server()
         self.assertEqual(env['DISPLAY'], ':99')
+        self.assertLog(
+            ['DEBUG: Starting Xvfb with display ":99".\n'] +
+            ['WARNING: xdpyinfo check failed with exit code 1 while starting Xvfb on ":99".\n'] * 51 +
+            [
+                'DEBUG: Killing Xvfb process pid 42.\n',
+                'CRITICAL: Failed to start Xvfb on display ":99" (xvfb retcode: None).\n',
+            ])
+
+    def test_setup_test_runs_terminates_if_xvfb_proc_fails(self):
+        def run_command_fake(args):
+            if args[0] == 'xdpyinfo':
+                return 1
+            return 0
+
+        host = MockSystemHost(os_name=self.os_name, os_version=self.os_version)
+        port = self.make_port(host=host)
+        # Xvfb is started via Executive.popen, which returns an object for the
+        # process. Here we set up a fake process object that acts as if it has
+        # exited with return code 1 immediately.
+        proc = MockProcess(stdout='', stderr='', returncode=3)
+        port.host.executive = MockExecutive(
+            run_command_fn=run_command_fake, proc=proc)
+        self.set_logging_level(logging.DEBUG)
+
+        port.setup_test_run()
+        self.assertEqual(
+            port.host.executive.calls,
+            [
+                ['xdpyinfo', '-display', ':99'],
+                ['Xvfb', ':99', '-screen', '0', '1280x800x24', '-ac', '-dpi', '96']
+            ])
+        self.assertLog([
+            'DEBUG: Starting Xvfb with display ":99".\n',
+            'CRITICAL: Failed to start Xvfb on display ":99" (xvfb retcode: 3).\n'
+        ])
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py
index 05a2461..55ac76cd 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py
@@ -267,7 +267,8 @@
         port = self.make_port()
 
         port.host.environ['WEBKIT_HTTP_SERVER_CONF_PATH'] = '/path/to/httpd.conf'
-        self.assertRaises(IOError, port.path_to_apache_config_file)
+        with self.assertRaises(IOError):
+            port.path_to_apache_config_file()
         port.host.filesystem.write_text_file('/existing/httpd.conf', 'Hello, world!')
         port.host.environ['WEBKIT_HTTP_SERVER_CONF_PATH'] = '/existing/httpd.conf'
         self.assertEqual(port.path_to_apache_config_file(), '/existing/httpd.conf')
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/win_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/win_unittest.py
index 1ee7183..bbfdc7f 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/win_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/win_unittest.py
@@ -84,7 +84,8 @@
         self.assert_name('win', 'future', 'win-win10')
         self.assert_name('win-win10', 'future', 'win-win10')
 
-        self.assertRaises(AssertionError, self.assert_name, None, 'w2k', 'win-win7')
+        with self.assertRaises(AssertionError):
+            self.assert_name(None, 'w2k', 'win-win7')
 
     def assert_baseline_paths(self, port_name, *expected_paths):
         port = self.make_port(port_name=port_name)
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py
index caf2f0c..f718cde 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py
@@ -253,15 +253,14 @@
         # Exceptions raised in a separate process are re-packaged into
         # WorkerExceptions (a subclass of BaseException), which have a string capture of the stack which can
         # be printed, but don't display properly in the unit test exception handlers.
-        self.assertRaises(BaseException, logging_run,
-                          ['failures/expected/exception.html', '--child-processes', '1'], tests_included=True)
+        with self.assertRaises(BaseException):
+            logging_run(['failures/expected/exception.html', '--child-processes', '1'], tests_included=True)
 
-        self.assertRaises(
-            BaseException,
-            logging_run,
-            ['--child-processes', '2', '--skipped=ignore', 'failures/expected/exception.html', 'passes/text.html'],
-            tests_included=True,
-            shared_port=False)
+        with self.assertRaises(BaseException):
+            logging_run(
+                ['--child-processes', '2', '--skipped=ignore', 'failures/expected/exception.html', 'passes/text.html'],
+                tests_included=True,
+                shared_port=False)
 
     def test_device_failure(self):
         # Test that we handle a device going offline during a test properly.
@@ -517,9 +516,12 @@
         self.assertEqual(tests_run, ['perf/foo/test.html'])
 
     def test_sharding_incorrect_arguments(self):
-        self.assertRaises(ValueError, get_tests_run, ['--shard-index', '3'])
-        self.assertRaises(ValueError, get_tests_run, ['--total-shards', '3'])
-        self.assertRaises(ValueError, get_tests_run, ['--shard-index', '3', '--total-shards', '3'])
+        with self.assertRaises(ValueError):
+            get_tests_run(['--shard-index', '3'])
+        with self.assertRaises(ValueError):
+            get_tests_run(['--total-shards', '3'])
+        with self.assertRaises(ValueError):
+            get_tests_run(['--shard-index', '3', '--total-shards', '3'])
 
     def test_sharding_environ(self):
         tests_to_run = ['passes/error.html', 'passes/image.html', 'passes/platform_image.html', 'passes/text.html']
@@ -1105,7 +1107,7 @@
             tests_included=True, host=host)
         file_list = host.filesystem.written_files.keys()
         self.assertEqual(details.exit_code, 0)
-        self.assertEqual(len(file_list), 8)
+        self.assertEqual(len(file_list), 9)
         self.assert_baselines(file_list, 'failures/unexpected/text-image-checksum', ['.txt', '.png'], err)
 
     def test_reset_missing_results(self):
@@ -1118,7 +1120,7 @@
                                       tests_included=True, host=host)
         file_list = host.filesystem.written_files.keys()
         self.assertEqual(details.exit_code, 0)
-        self.assertEqual(len(file_list), 9)
+        self.assertEqual(len(file_list), 10)
         self.assert_baselines(file_list, 'failures/unexpected/missing_text', ['.txt'], err)
         self.assert_baselines(file_list, 'failures/unexpected/missing_image', ['.png'], err)
         self.assert_baselines(file_list, 'failures/unexpected/missing_render_tree_dump', ['.txt'], err)
@@ -1139,7 +1141,7 @@
             tests_included=True, host=host)
         file_list = host.filesystem.written_files.keys()
         self.assertEqual(details.exit_code, 0)
-        self.assertEqual(len(file_list), 8)
+        self.assertEqual(len(file_list), 9)
         self.assert_baselines(file_list,
                               'platform/test-mac-mac10.10/failures/unexpected/text-image-checksum',
                               ['.png'], err)
@@ -1161,7 +1163,7 @@
             tests_included=True, host=host)
         file_list = host.filesystem.written_files.keys()
         self.assertEqual(details.exit_code, 0)
-        self.assertEqual(len(file_list), 8)
+        self.assertEqual(len(file_list), 9)
         # We should create new pixel baseline only.
         self.assertFalse(
             host.filesystem.exists(
@@ -1177,7 +1179,7 @@
         details, err, _ = logging_run(['--reset-results', 'passes/reftest.html'], tests_included=True, host=host)
         file_list = host.filesystem.written_files.keys()
         self.assertEqual(details.exit_code, 0)
-        self.assertEqual(len(file_list), 6)
+        self.assertEqual(len(file_list), 7)
         self.assert_baselines(file_list, '', [], err)
 
         host.filesystem.write_text_file(test.LAYOUT_TEST_DIR + '/passes/reftest-expected.txt', '')
@@ -1185,7 +1187,7 @@
         details, err, _ = logging_run(['--reset-results', 'passes/reftest.html'], tests_included=True, host=host)
         file_list = host.filesystem.written_files.keys()
         self.assertEqual(details.exit_code, 0)
-        self.assertEqual(len(file_list), 6)
+        self.assertEqual(len(file_list), 7)
         self.assert_baselines(file_list, 'passes/reftest', ['.txt'], err)
 
 
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/style/checker_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/style/checker_unittest.py
index af9b1a7..5bc96fb 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/style/checker_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/style/checker_unittest.py
@@ -793,9 +793,10 @@
     def test_process__no_checker_dispatched(self):
         """Test the process() method for a path with no dispatched checker."""
         path = os.path.join('foo', 'do_not_process.txt')
-        self.assertRaises(AssertionError, self._processor.process,
-                          lines=['line1', 'line2'], file_path=path,
-                          line_numbers=[100])
+        with self.assertRaises(AssertionError):
+            self._processor.process(
+                lines=['line1', 'line2'], file_path=path,
+                line_numbers=[100])
 
     def test_process__carriage_returns_not_stripped(self):
         """Test that carriage returns aren't stripped from files that are allowed to contain them."""
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/style/filter_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/style/filter_unittest.py
index a6a93bd..951b9ca6 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/style/filter_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/style/filter_unittest.py
@@ -73,8 +73,8 @@
         ]
 
         for rule in bad_rules:
-            self.assertRaises(ValueError, validate_filter_rules,
-                              [rule], all_categories)
+            with self.assertRaises(ValueError):
+                validate_filter_rules([rule], all_categories)
 
         for rule in good_rules:
             # This works: no error.
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/style/optparser_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/style/optparser_unittest.py
index 1a535a1..09776f40 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/style/optparser_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/style/optparser_unittest.py
@@ -102,36 +102,44 @@
         #        filter categories help.
 
         # Request the usage string.
-        self.assertRaises(SystemExit, parse, ['--help'])
+        with self.assertRaises(SystemExit):
+            parse(['--help'])
         # Request default filter rules and available style categories.
-        self.assertRaises(SystemExit, parse, ['--filter='])
+        with self.assertRaises(SystemExit):
+            parse(['--filter='])
 
     def test_parse_bad_values(self):
         parse = self._parse
 
         # Pass an unsupported argument.
-        self.assertRaises(SystemExit, parse, ['--bad'])
+        with self.assertRaises(SystemExit):
+            parse(['--bad'])
         self.assertLog(['ERROR: no such option: --bad\n'])
 
-        self.assertRaises(SystemExit, parse, ['--min-confidence=bad'])
+        with self.assertRaises(SystemExit):
+            parse(['--min-confidence=bad'])
         self.assertLog(['ERROR: option --min-confidence: '
                         "invalid integer value: 'bad'\n"])
-        self.assertRaises(SystemExit, parse, ['--min-confidence=0'])
+        with self.assertRaises(SystemExit):
+            parse(['--min-confidence=0'])
         self.assertLog(['ERROR: option --min-confidence: invalid integer: 0: '
                         'value must be between 1 and 5\n'])
-        self.assertRaises(SystemExit, parse, ['--min-confidence=6'])
+        with self.assertRaises(SystemExit):
+            parse(['--min-confidence=6'])
         self.assertLog(['ERROR: option --min-confidence: invalid integer: 6: '
                         'value must be between 1 and 5\n'])
         parse(['--min-confidence=1'])  # works
         parse(['--min-confidence=5'])  # works
 
-        self.assertRaises(SystemExit, parse, ['--output=bad'])
+        with self.assertRaises(SystemExit):
+            parse(['--output=bad'])
         self.assertLog(['ERROR: option --output-format: invalid choice: '
                         "'bad' (choose from 'emacs', 'vs7')\n"])
         parse(['--output=vs7'])  # works
 
         # Pass a filter rule not beginning with + or -.
-        self.assertRaises(SystemExit, parse, ['--filter=build'])
+        with self.assertRaises(SystemExit):
+            parse(['--filter=build'])
         self.assertLog(['ERROR: Invalid filter rule "build": '
                         'every rule must start with + or -.\n'])
         parse(['--filter=+build'])  # works
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/command_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/command_unittest.py
index 21d0731..f9cd52f 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/command_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/command_unittest.py
@@ -64,11 +64,13 @@
         self.assertEqual(command.name_with_arguments(), "trivial [options]")
 
     def test_parse_required_arguments(self):
+        # Unit test for protected method - pylint: disable=protected-access
         self.assertEqual(Command._parse_required_arguments("ARG1 ARG2"), ["ARG1", "ARG2"])
         self.assertEqual(Command._parse_required_arguments("[ARG1] [ARG2]"), [])
         self.assertEqual(Command._parse_required_arguments("[ARG1] ARG2"), ["ARG2"])
         # Note: We might make our arg parsing smarter in the future and allow this type of arguments string.
-        self.assertRaises(Exception, Command._parse_required_arguments, "[ARG1 ARG2]")
+        with self.assertRaises(Exception):
+            Command._parse_required_arguments("[ARG1 ARG2]")
 
     def test_required_arguments(self):
         class TrivialCommandWithRequiredAndOptionalArgs(TrivialCommand):
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer.py b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer.py
index 3ef1178..ce627c7 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer.py
@@ -408,11 +408,12 @@
             description += 'Build: %s\n\n' % build_link
 
         description += (
-            'Background: https://chromium.googlesource.com'
-            '/chromium/src/+/master/docs/testing/web_platform_tests.md\n\n'
-            'Note to sheriffs: If this CL causes a small number of new layout\n'
-            'test failures, it may be easier to add lines to TestExpectations\n'
-            'rather than reverting.\n')
+            'Note to sheriffs: This CL imports external tests and adds\n'
+            'expectations for those tests; if this CL is large and causes\n'
+            'a few new failures, please fix the failures by adding new\n'
+            'lines to TestExpectations rather than reverting. See:\n'
+            'https://chromium.googlesource.com'
+            '/chromium/src/+/master/docs/testing/web_platform_tests.md\n\n')
 
         if directory_owners:
             description += self._format_directory_owners(directory_owners) + '\n\n'
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer_unittest.py
index 67d8aae..15e1d61 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer_unittest.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/test_importer_unittest.py
@@ -80,11 +80,12 @@
         self.assertEqual(
             description,
             'Last commit message\n\n'
-            'Background: https://chromium.googlesource.com/'
-            'chromium/src/+/master/docs/testing/web_platform_tests.md\n\n'
-            'Note to sheriffs: If this CL causes a small number of new layout\n'
-            'test failures, it may be easier to add lines to TestExpectations\n'
-            'rather than reverting.\n'
+            'Note to sheriffs: This CL imports external tests and adds\n'
+            'expectations for those tests; if this CL is large and causes\n'
+            'a few new failures, please fix the failures by adding new\n'
+            'lines to TestExpectations rather than reverting. See:\n'
+            'https://chromium.googlesource.com'
+            '/chromium/src/+/master/docs/testing/web_platform_tests.md\n\n'
             'TBR=qyearsley@chromium.org\n'
             'NOEXPORT=true')
         self.assertEqual(host.executive.calls, [['git', 'log', '-1', '--format=%B']])
diff --git a/third_party/WebKit/public/web/WebFrameClient.h b/third_party/WebKit/public/web/WebFrameClient.h
index c2e13f4..8c10ed8 100644
--- a/third_party/WebKit/public/web/WebFrameClient.h
+++ b/third_party/WebKit/public/web/WebFrameClient.h
@@ -73,6 +73,10 @@
 #include "public/platform/modules/serviceworker/WebServiceWorkerProvider.h"
 #include "v8/include/v8.h"
 
+namespace service_manager {
+class InterfaceProvider;
+}
+
 namespace blink {
 
 enum class WebTreeScopeType;
@@ -177,6 +181,12 @@
   // Returns a blame context for attributing work belonging to this frame.
   virtual BlameContext* GetFrameBlameContext() { return nullptr; }
 
+  // Returns an InterfaceProvider the frame can use to request interfaces from
+  // the browser.
+  virtual service_manager::InterfaceProvider* GetInterfaceProvider() {
+    return nullptr;
+  }
+
   // General notifications -----------------------------------------------
 
   // Indicates if creating a plugin without an associated renderer is supported.
diff --git a/third_party/node/node_modules.py b/third_party/node/node_modules.py
index e9aa705..4235dc77e 100755
--- a/third_party/node/node_modules.py
+++ b/third_party/node/node_modules.py
@@ -25,8 +25,8 @@
   return _path_in_node_modules('polymer-css-build', 'bin', 'polymer-css-build')
 
 
-def PathToUglifyJs():
- return _path_in_node_modules('uglifyjs', 'bin', 'uglifyjs')
+def PathToUglify():
+ return _path_in_node_modules('uglify-es', 'bin', 'uglifyjs')
 
 
 def PathToEsLint():
diff --git a/third_party/node/node_modules.tar.gz.sha1 b/third_party/node/node_modules.tar.gz.sha1
index 85949287..e6bd3d9 100644
--- a/third_party/node/node_modules.tar.gz.sha1
+++ b/third_party/node/node_modules.tar.gz.sha1
@@ -1 +1 @@
-70eddb115269dddec7f911b3833fff3e37a6fbe8
+6dbd64a4fc98b0f3b72625c1585d95f9e9b6d71a
diff --git a/third_party/node/package.json b/third_party/node/package.json
index 3ed265a..0723d6fc 100644
--- a/third_party/node/package.json
+++ b/third_party/node/package.json
@@ -6,7 +6,7 @@
     "crisper": "2.0.2",
     "eslint": "3.19.0",
     "polymer-css-build": "0.1.2",
-    "uglifyjs": "2.4.10",
+    "uglify-es": "3.0.15",
     "vulcanize": "1.15.4"
   }
 }
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 5411a46..f6fba52 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -29255,6 +29255,13 @@
   </summary>
 </histogram>
 
+<histogram name="Media.RebuffersCount" units="rebuffers">
+  <owner>dalecurtis@chromium.org</owner>
+  <summary>
+    Indicates the number of rebuffers a given watch time session had.
+  </summary>
+</histogram>
+
 <histogram name="Media.Remoting.AllowedByPage" enum="BooleanEnabled">
   <owner>miu@chromium.org</owner>
   <summary>Tracks whether a web page allows content to be remoted.</summary>
@@ -89098,19 +89105,6 @@
   <affected-histogram name="Media.Timeline.Width"/>
 </histogram_suffixes>
 
-<histogram_suffixes name="MediaMtbrCategories" separator=".">
-  <suffix name="Audio.MSE" label="MTBR for MSE media with an audio track."/>
-  <suffix name="Audio.SRC" label="MTBR for SRC media with an audio track."/>
-  <suffix name="Audio.EME" label="MTBR for EME media with an audio track."/>
-  <suffix name="AudioVideo.MSE"
-      label="MTBR for MSE media with both an audio and video track."/>
-  <suffix name="AudioVideo.SRC"
-      label="MTBR for SRC media with both an audio and video track."/>
-  <suffix name="AudioVideo.EME"
-      label="MTBR for EME media with both an audio and video track."/>
-  <affected-histogram name="Media.MeanTimeBetweenRebuffers"/>
-</histogram_suffixes>
-
 <histogram_suffixes name="MediaPipelineStatusForStreams" separator=".">
   <suffix name="AudioVideo.Other"
       label="PipelineStatus for the codecs that dont have an explicit metric."/>
@@ -89161,6 +89155,20 @@
   <affected-histogram name="Media.PipelineStatus"/>
 </histogram_suffixes>
 
+<histogram_suffixes name="MediaRebufferingCategories" separator=".">
+  <suffix name="Audio.MSE" label="Metric for MSE media with an audio track."/>
+  <suffix name="Audio.SRC" label="Metric for SRC media with an audio track."/>
+  <suffix name="Audio.EME" label="Metric for EME media with an audio track."/>
+  <suffix name="AudioVideo.MSE"
+      label="Metric for MSE media with both an audio and video track."/>
+  <suffix name="AudioVideo.SRC"
+      label="Metric for SRC media with both an audio and video track."/>
+  <suffix name="AudioVideo.EME"
+      label="Metric for EME media with both an audio and video track."/>
+  <affected-histogram name="Media.MeanTimeBetweenRebuffers"/>
+  <affected-histogram name="Media.RebuffersCount"/>
+</histogram_suffixes>
+
 <histogram_suffixes name="MediaTimelineWidths" separator=".">
   <suffix name="32_47"/>
   <suffix name="48_79"/>
diff --git a/tools/perf/benchmark.csv b/tools/perf/benchmark.csv
index 3cb4162..91b908b9 100644
--- a/tools/perf/benchmark.csv
+++ b/tools/perf/benchmark.csv
@@ -4,22 +4,22 @@
 angle_perftests,jmadill@chromium.org,
 battor.steady_state,charliea@chromium.org,
 battor.trivial_pages,charliea@chromium.org,
-blink_perf.bindings,"yukishiino@chromium.org, bashi@chromium.org, haraken@chromium.org",
+blink_perf.bindings,"jbroman@chromium.org, yukishiino@chromium.org, haraken@chromium.org",
 blink_perf.canvas,junov@chromium.org,
 blink_perf.css,rune@opera.com,
-blink_perf.dom,"yukishiino@chromium.org, bashi@chromium.org, haraken@chromium.org",
+blink_perf.dom,"jbroman@chromium.org, yukishiino@chromium.org, haraken@chromium.org",
 blink_perf.events,hayato@chromium.org,
 blink_perf.layout,eae@chromium.org,
 blink_perf.paint,wangxianzhu@chromium.org,
-blink_perf.parser,"yukishiino@chromium.org, bashi@chromium.org, haraken@chromium.org",
+blink_perf.parser,"jbroman@chromium.org, yukishiino@chromium.org, haraken@chromium.org",
 blink_perf.shadow_dom,hayato@chromium.org,
 blink_perf.svg,"kouhei@chromium.org, fs@opera.com",
 blob_storage.blob_storage,,
 cc_perftests,enne@chromium.org,
-dromaeo.domcoreattr,"yukishiino@chromium.org, bashi@chromium.org, haraken@chromium.org",
-dromaeo.domcoremodify,"yukishiino@chromium.org, bashi@chromium.org, haraken@chromium.org",
-dromaeo.domcorequery,"yukishiino@chromium.org, bashi@chromium.org, haraken@chromium.org",
-dromaeo.domcoretraverse,"yukishiino@chromium.org, bashi@chromium.org, haraken@chromium.org",
+dromaeo.domcoreattr,"jbroman@chromium.org, yukishiino@chromium.org, haraken@chromium.org",
+dromaeo.domcoremodify,"jbroman@chromium.org, yukishiino@chromium.org, haraken@chromium.org",
+dromaeo.domcorequery,"jbroman@chromium.org, yukishiino@chromium.org, haraken@chromium.org",
+dromaeo.domcoretraverse,"jbroman@chromium.org, yukishiino@chromium.org, haraken@chromium.org",
 dummy_benchmark.noisy_benchmark_1,nednguyen@google.com,
 dummy_benchmark.stable_benchmark_1,nednguyen@google.com,
 gpu_perftests,reveman@chromium.org,
diff --git a/tools/perf/benchmarks/v8_browsing.py b/tools/perf/benchmarks/v8_browsing.py
index eeb39c3..1975580 100644
--- a/tools/perf/benchmarks/v8_browsing.py
+++ b/tools/perf/benchmarks/v8_browsing.py
@@ -229,6 +229,7 @@
     return 'v8.browsing_mobile_classic'
 
 
+@benchmark.Disabled('android')
 @benchmark.Owner(emails=['mythria@chromium.org'])
 class V8RuntimeStatsDesktopBrowsingBenchmark(
     _V8RuntimeStatsBrowsingBenchmark):
@@ -256,6 +257,7 @@
     return 'v8.runtimestats.browsing_desktop_turbo'
 
 
+@benchmark.Disabled('android')
 @benchmark.Owner(emails=['hablich@chromium.org'])
 class V8RuntimeStatsDesktopClassicBrowsingBenchmark(
     _V8RuntimeStatsBrowsingBenchmark):
@@ -271,7 +273,6 @@
     return 'v8.runtimestats.browsing_desktop_classic'
 
 
-
 @benchmark.Enabled('android')
 @benchmark.Owner(emails=['mythria@chromium.org'])
 class V8RuntimeStatsMobileBrowsingBenchmark(