diff --git a/android_webview/browser/net/aw_network_delegate.cc b/android_webview/browser/net/aw_network_delegate.cc
index e37915c3..144f5e5 100644
--- a/android_webview/browser/net/aw_network_delegate.cc
+++ b/android_webview/browser/net/aw_network_delegate.cc
@@ -147,8 +147,10 @@
                                                              options);
 }
 
-bool AwNetworkDelegate::OnCanAccessFile(const net::URLRequest& request,
-                                        const base::FilePath& path) const {
+bool AwNetworkDelegate::OnCanAccessFile(
+    const net::URLRequest& request,
+    const base::FilePath& original_path,
+    const base::FilePath& absolute_path) const {
   return true;
 }
 
diff --git a/android_webview/browser/net/aw_network_delegate.h b/android_webview/browser/net/aw_network_delegate.h
index 0954513..2ebd620 100644
--- a/android_webview/browser/net/aw_network_delegate.h
+++ b/android_webview/browser/net/aw_network_delegate.h
@@ -59,7 +59,8 @@
                       const std::string& cookie_line,
                       net::CookieOptions* options) override;
   bool OnCanAccessFile(const net::URLRequest& request,
-                       const base::FilePath& path) const override;
+                       const base::FilePath& original_path,
+                       const base::FilePath& absolute_path) const override;
 
   // Used to filter URL requests. Owned by AwBrowserContext.
   const policy::URLBlacklistManager* url_blacklist_manager_;
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 3eec53a..83f6dc1 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -523,8 +523,9 @@
     "//chrome/test/data/translate/",
     "//chrome/test/media_router/resources/",
     "//content/test/data/android/geolocation.html",
-    "//content/test/data/android/quota_permissions.html",
     "//content/test/data/android/media_permissions.html",
+    "//content/test/data/android/permission_navigation.html",
+    "//content/test/data/android/quota_permissions.html",
     "//content/test/data/android/webshare.html",
     "//content/test/data/media/bear.webm",
     "//content/test/data/media/getusermedia.html",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionDialogController.java b/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionDialogController.java
index d38dc9b..fcb0b97 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionDialogController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionDialogController.java
@@ -89,6 +89,7 @@
      */
     private void queueDialog(PermissionDialogDelegate delegate) {
         mRequestQueue.add(delegate);
+        delegate.setDialogController(this);
         scheduleDisplay();
     }
 
@@ -101,6 +102,11 @@
         return mDialog;
     }
 
+    @VisibleForTesting
+    public int getQueueLengthForTesting() {
+        return mRequestQueue.size();
+    }
+
     @Override
     public void onAndroidPermissionAccepted() {
         mDialogDelegate.onAccept(mSwitchView.isChecked());
@@ -193,9 +199,10 @@
         mDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
             @Override
             public void onDismiss(DialogInterface dialog) {
-                // For some reason this is ocassionally null. See crbug.com/708562.
+                // Null if dismiss initiated by C++, or for some unknown reason (crbug.com/708562).
                 if (mDialogDelegate == null) {
                     scheduleDisplay();
+                    return;
                 }
 
                 mDialog = null;
@@ -253,6 +260,19 @@
         return fullString;
     }
 
+    public void dismissFromNative(PermissionDialogDelegate delegate) {
+        if (mDialogDelegate == delegate) {
+            mDialogDelegate = null;
+            AlertDialog dialog = mDialog;
+            mDialog = null;
+            dialog.dismiss();
+        } else {
+            assert mRequestQueue.contains(delegate);
+            mRequestQueue.remove(delegate);
+        }
+        delegate.destroy();
+    }
+
     private void destroyDelegate() {
         mDialogDelegate.destroy();
         mDialogDelegate = null;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionDialogDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionDialogDelegate.java
index 3397d3f..01917fff 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionDialogDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionDialogDelegate.java
@@ -21,6 +21,9 @@
     /** The native-side counterpart of this class */
     private long mNativeDelegatePtr;
 
+    /** The controller for this class */
+    private PermissionDialogController mDialogController;
+
     /** The tab for which to create the dialog. */
     private Tab mTab;
 
@@ -103,6 +106,18 @@
         nativeLinkClicked(mNativeDelegatePtr);
     }
 
+    public void setDialogController(PermissionDialogController controller) {
+        mDialogController = controller;
+    }
+
+    /**
+     * Called from C++ by |nativeDelegatePtr| to destroy the dialog.
+     */
+    @CalledByNative
+    private void dismissFromNative() {
+        mDialogController.dismissFromNative(this);
+    }
+
     /**
      * Called from C++ by |nativeDelegatePtr| to instantiate this class.
      *
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 25586dc33..bfa009d8 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -1566,6 +1566,7 @@
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestUseStatsTest.java",
   "javatests/src/org/chromium/chrome/browser/permissions/GeolocationTest.java",
   "javatests/src/org/chromium/chrome/browser/permissions/MediaTest.java",
+  "javatests/src/org/chromium/chrome/browser/permissions/PermissionNavigationTest.java",
   "javatests/src/org/chromium/chrome/browser/permissions/PermissionTestCaseBase.java",
   "javatests/src/org/chromium/chrome/browser/permissions/QuotaTest.java",
   "javatests/src/org/chromium/chrome/browser/precache/MockPrecacheController.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionNavigationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionNavigationTest.java
new file mode 100644
index 0000000..fe0168b21
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionNavigationTest.java
@@ -0,0 +1,73 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.permissions;
+
+import android.support.test.filters.MediumTest;
+
+import org.junit.Assert;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.Feature;
+import org.chromium.base.test.util.RetryOnFailure;
+import org.chromium.chrome.browser.tab.EmptyTabObserver;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.content.browser.test.util.Criteria;
+import org.chromium.content.browser.test.util.CriteriaHelper;
+
+/**
+ * Test suite for interaction between permissions requests and navigation.
+ */
+@RetryOnFailure
+public class PermissionNavigationTest extends PermissionTestCaseBase {
+    private static final String TEST_FILE = "/content/test/data/android/permission_navigation.html";
+
+    public PermissionNavigationTest() {}
+
+    /**
+     * Check that modal permission prompts and queued permission requests are removed upon
+     * navigation.
+     *
+     * @throws Exception
+     */
+    @MediumTest
+    @Feature({"Permissions"})
+    @CommandLineFlags.Add({NO_GESTURE_FEATURE, FORCE_FIELDTRIAL, FORCE_FIELDTRIAL_PARAMS})
+    public void testNavigationDismissesModalPermissionPrompt() throws Exception {
+        setUpUrl(TEST_FILE);
+        runJavaScriptCodeInCurrentTab("requestGeolocationPermission()");
+        DialogShownCriteria criteriaShown = new DialogShownCriteria("Dialog not shown", true);
+        CriteriaHelper.pollUiThread(criteriaShown);
+        Assert.assertEquals(0, PermissionDialogController.getInstance().getQueueLengthForTesting());
+
+        runJavaScriptCodeInCurrentTab("requestNotificationPermission()");
+        CriteriaHelper.pollInstrumentationThread(new Criteria("Request not queued") {
+            @Override
+            public boolean isSatisfied() {
+                return PermissionDialogController.getInstance().getQueueLengthForTesting() == 1;
+            }
+        });
+
+        runJavaScriptCodeInCurrentTab("navigate()");
+
+        Tab tab = getActivity().getActivityTab();
+        final CallbackHelper callbackHelper = new CallbackHelper();
+        EmptyTabObserver navigationWaiter = new EmptyTabObserver() {
+            @Override
+            public void onDidFinishNavigation(Tab tab, String url, boolean isInMainFrame,
+                    boolean isErrorPage, boolean hasCommitted, boolean isSameDocument,
+                    boolean isFragmentNavigation, Integer pageTransition, int errorCode,
+                    int httpStatusCode) {
+                callbackHelper.notifyCalled();
+            }
+        };
+        tab.addObserver(navigationWaiter);
+        callbackHelper.waitForCallback(0);
+        tab.removeObserver(navigationWaiter);
+
+        DialogShownCriteria criteriaNotShown = new DialogShownCriteria("Dialog shown", false);
+        CriteriaHelper.pollUiThread(criteriaNotShown);
+    }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionTestCaseBase.java b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionTestCaseBase.java
index 063c362..43ace6a5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionTestCaseBase.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionTestCaseBase.java
@@ -88,11 +88,13 @@
     /**
      * Criteria class to detect whether the permission dialog is shown.
      */
-    private static class DialogShownCriteria extends Criteria {
+    protected static class DialogShownCriteria extends Criteria {
         private AlertDialog mDialog;
+        private boolean mExpectDialog;
 
-        public DialogShownCriteria(String error) {
+        public DialogShownCriteria(String error, boolean expectDialog) {
             super(error);
+            mExpectDialog = expectDialog;
         }
 
         public AlertDialog getDialog() {
@@ -107,7 +109,7 @@
                     public Boolean call() {
                         mDialog = PermissionDialogController.getInstance()
                                           .getCurrentDialogForTesting();
-                        return mDialog != null;
+                        return (mDialog != null) == mExpectDialog;
                     }
                 });
             } catch (ExecutionException e) {
@@ -136,6 +138,10 @@
         super.tearDown();
     }
 
+    protected void setUpUrl(final String url) throws InterruptedException {
+        loadUrl(mTestServer.getURL(url));
+    }
+
     /**
      * Simulates clicking a button on an AlertDialog.
      */
@@ -165,8 +171,7 @@
     protected void runAllowTest(PermissionUpdateWaiter updateWaiter, final String url,
             String javascript, int nUpdates, boolean withGesture, boolean isDialog,
             boolean hasSwitch, boolean toggleSwitch) throws Exception {
-        final String test_url = mTestServer.getURL(url);
-        loadUrl(test_url);
+        setUpUrl(url);
 
         if (withGesture) {
             runJavaScriptCodeInCurrentTab("functionToRun = '" + javascript + "'");
@@ -176,7 +181,7 @@
         }
 
         if (isDialog) {
-            DialogShownCriteria criteria = new DialogShownCriteria("Dialog not shown");
+            DialogShownCriteria criteria = new DialogShownCriteria("Dialog not shown", true);
             CriteriaHelper.pollUiThread(criteria);
             replyToDialogAndWaitForUpdates(
                     updateWaiter, criteria.getDialog(), nUpdates, true, hasSwitch, toggleSwitch);
diff --git a/chrome/browser/android/offline_pages/offline_page_request_job.cc b/chrome/browser/android/offline_pages/offline_page_request_job.cc
index 32a6685..3401b11 100644
--- a/chrome/browser/android/offline_pages/offline_page_request_job.cc
+++ b/chrome/browser/android/offline_pages/offline_page_request_job.cc
@@ -707,6 +707,13 @@
   URLRequestJob::NotifyHeadersComplete();
 }
 
+// Returns true to disable the file path checking for file: scheme in
+// URLRequestFileJob, that's not relevant for this class.
+bool OfflinePageRequestJob::CanAccessFile(const base::FilePath& original_path,
+                                          const base::FilePath& absolute_path) {
+  return true;
+}
+
 void OfflinePageRequestJob::SetDelegateForTesting(
     std::unique_ptr<Delegate> delegate) {
   delegate_ = std::move(delegate);
diff --git a/chrome/browser/android/offline_pages/offline_page_request_job.h b/chrome/browser/android/offline_pages/offline_page_request_job.h
index 500af977..b3fcf777 100644
--- a/chrome/browser/android/offline_pages/offline_page_request_job.h
+++ b/chrome/browser/android/offline_pages/offline_page_request_job.h
@@ -103,6 +103,10 @@
   void SetDelegateForTesting(std::unique_ptr<Delegate> delegate);
 
  private:
+  // net::URLRequestFileJob overrides:
+  bool CanAccessFile(const base::FilePath& original_path,
+                     const base::FilePath& absolute_path) override;
+
   OfflinePageRequestJob(net::URLRequest* request,
                         net::NetworkDelegate* network_delegate,
                         previews::PreviewsDecider* previews_decider);
diff --git a/chrome/browser/app_controller_mac.mm b/chrome/browser/app_controller_mac.mm
index a7c2bfb9..f6417c0 100644
--- a/chrome/browser/app_controller_mac.mm
+++ b/chrome/browser/app_controller_mac.mm
@@ -1017,7 +1017,6 @@
       break;
     }
     case IDC_SHOW_BOOKMARK_MANAGER:
-      base::RecordAction(UserMetricsAction("ShowBookmarkManager"));
       if (Browser* browser = ActivateBrowser(lastProfile)) {
         chrome::ShowBookmarkManager(browser);
       } else {
diff --git a/chrome/browser/bookmarks/bookmark_model_factory.cc b/chrome/browser/bookmarks/bookmark_model_factory.cc
index 46069a63..e6d62d6 100644
--- a/chrome/browser/bookmarks/bookmark_model_factory.cc
+++ b/chrome/browser/bookmarks/bookmark_model_factory.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/bookmarks/startup_task_runner_service_factory.h"
 #include "chrome/browser/profiles/incognito_helpers.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/md_bookmarks/md_bookmarks_ui.h"
 #include "chrome/browser/undo/bookmark_undo_service_factory.h"
 #include "chrome/common/chrome_switches.h"
 #include "components/bookmarks/browser/bookmark_model.h"
@@ -27,6 +28,21 @@
 
 using bookmarks::BookmarkModel;
 
+namespace {
+
+bool IsBookmarkUndoServiceEnabled() {
+  bool register_bookmark_undo_service_as_observer = true;
+#if !defined(OS_ANDROID)
+  register_bookmark_undo_service_as_observer =
+      base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kEnableBookmarkUndo) ||
+      MdBookmarksUI::IsEnabled();
+#endif  // !defined(OS_ANDROID)
+  return register_bookmark_undo_service_as_observer;
+}
+
+}  // namespace
+
 // static
 BookmarkModel* BookmarkModelFactory::GetForBrowserContext(
     content::BrowserContext* context) {
@@ -69,13 +85,7 @@
                            ->GetBookmarkTaskRunner(),
                        content::BrowserThread::GetTaskRunnerForThread(
                            content::BrowserThread::UI));
-  bool register_bookmark_undo_service_as_observer = true;
-#if !defined(OS_ANDROID)
-  register_bookmark_undo_service_as_observer =
-      base::CommandLine::ForCurrentProcess()->HasSwitch(
-          switches::kEnableBookmarkUndo);
-#endif  // !defined(OS_ANDROID)
-  if (register_bookmark_undo_service_as_observer)
+  if (IsBookmarkUndoServiceEnabled())
     BookmarkUndoServiceFactory::GetForProfile(profile)->Start(bookmark_model);
 
   return bookmark_model;
diff --git a/chrome/browser/net/chrome_network_delegate.cc b/chrome/browser/net/chrome_network_delegate.cc
index 6686bd3c..b11644ad 100644
--- a/chrome/browser/net/chrome_network_delegate.cc
+++ b/chrome/browser/net/chrome_network_delegate.cc
@@ -447,8 +447,10 @@
   return allow;
 }
 
-bool ChromeNetworkDelegate::OnCanAccessFile(const net::URLRequest& request,
-                                            const base::FilePath& path) const {
+bool ChromeNetworkDelegate::OnCanAccessFile(
+    const net::URLRequest& request,
+    const base::FilePath& original_path,
+    const base::FilePath& absolute_path) const {
 #if defined(OS_CHROMEOS)
   // browser_tests and interactive_ui_tests rely on the ability to open any
   // files via file: scheme.
@@ -456,7 +458,14 @@
     return true;
 #endif
 
-  return IsAccessAllowed(path, profile_path_);
+#if defined(OS_ANDROID)
+  // Android's whitelist relies on symbolic links (ex. /sdcard is whitelisted
+  // and commonly a symbolic link), thus do not check absolute paths.
+  return IsAccessAllowed(original_path, profile_path_);
+#else
+  return (IsAccessAllowed(original_path, profile_path_) &&
+          IsAccessAllowed(absolute_path, profile_path_));
+#endif
 }
 
 // static
diff --git a/chrome/browser/net/chrome_network_delegate.h b/chrome/browser/net/chrome_network_delegate.h
index d4e94a9e..8937c09 100644
--- a/chrome/browser/net/chrome_network_delegate.h
+++ b/chrome/browser/net/chrome_network_delegate.h
@@ -173,7 +173,8 @@
                       const std::string& cookie_line,
                       net::CookieOptions* options) override;
   bool OnCanAccessFile(const net::URLRequest& request,
-                       const base::FilePath& path) const override;
+                       const base::FilePath& original_path,
+                       const base::FilePath& absolute_path) const override;
   bool OnCanEnablePrivacyMode(
       const GURL& url,
       const GURL& first_party_for_cookies) const override;
diff --git a/chrome/browser/net/chrome_network_delegate_unittest.cc b/chrome/browser/net/chrome_network_delegate_unittest.cc
index 5020876..c7132e7 100644
--- a/chrome/browser/net/chrome_network_delegate_unittest.cc
+++ b/chrome/browser/net/chrome_network_delegate_unittest.cc
@@ -601,6 +601,8 @@
   // Chrome OS and Android don't have access to random files.
   EXPECT_FALSE(IsAccessAllowed("/", ""));
   EXPECT_FALSE(IsAccessAllowed("/foo.txt", ""));
+  // Empty path should not be allowed.
+  EXPECT_FALSE(IsAccessAllowed("", ""));
 #endif
 
 #if defined(OS_CHROMEOS)
diff --git a/chrome/browser/permissions/permission_dialog_delegate.cc b/chrome/browser/permissions/permission_dialog_delegate.cc
index 1c1e81c..d6082290 100644
--- a/chrome/browser/permissions/permission_dialog_delegate.cc
+++ b/chrome/browser/permissions/permission_dialog_delegate.cc
@@ -21,6 +21,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/chrome_features.h"
 #include "components/variations/variations_associated_data.h"
+#include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/web_contents.h"
 #include "jni/PermissionDialogController_jni.h"
 #include "jni/PermissionDialogDelegate_jni.h"
@@ -102,14 +103,12 @@
   return RegisterNativesImpl(env);
 }
 
-ScopedJavaLocalRef<jobject> PermissionDialogDelegate::CreateJavaDelegate(
-    JNIEnv* env) {
+void PermissionDialogDelegate::CreateJavaDelegate(JNIEnv* env) {
   std::vector<int> content_settings_types{
       infobar_delegate_->content_settings_types()};
 
-  return Java_PermissionDialogDelegate_create(
-      env, reinterpret_cast<uintptr_t>(this),
-      tab_->GetJavaObject(),
+  j_delegate_.Reset(Java_PermissionDialogDelegate_create(
+      env, reinterpret_cast<uintptr_t>(this), tab_->GetJavaObject(),
       base::android::ToJavaIntArray(env, content_settings_types).obj(),
       ResourceMapper::MapFromChromiumId(infobar_delegate_->GetIconId()),
       ConvertUTF16ToJavaString(env, infobar_delegate_->GetMessageText()),
@@ -119,7 +118,7 @@
       ConvertUTF16ToJavaString(env,
                                infobar_delegate_->GetButtonLabel(
                                    PermissionInfoBarDelegate::BUTTON_CANCEL)),
-      infobar_delegate_->ShouldShowPersistenceToggle());
+      infobar_delegate_->ShouldShowPersistenceToggle()));
 }
 
 void PermissionDialogDelegate::Accept(JNIEnv* env,
@@ -164,19 +163,40 @@
 PermissionDialogDelegate::PermissionDialogDelegate(
     TabAndroid* tab,
     std::unique_ptr<PermissionInfoBarDelegate> infobar_delegate)
-    : tab_(tab), infobar_delegate_(std::move(infobar_delegate)) {
+    : content::WebContentsObserver(tab->web_contents()),
+      tab_(tab),
+      infobar_delegate_(std::move(infobar_delegate)) {
   DCHECK(tab_);
   DCHECK(infobar_delegate_);
 
   // Create our Java counterpart, which manages our lifetime.
   JNIEnv* env = base::android::AttachCurrentThread();
-  base::android::ScopedJavaLocalRef<jobject> j_delegate =
-      CreateJavaDelegate(env);
+  CreateJavaDelegate(env);
 
   // Send the Java delegate to the Java PermissionDialogController for display.
   // The controller takes over lifetime management; when the Java delegate is no
   // longer needed it will in turn free the native delegate.
-  Java_PermissionDialogController_createDialog(env, j_delegate.obj());
+  Java_PermissionDialogController_createDialog(env, j_delegate_.obj());
 }
 
 PermissionDialogDelegate::~PermissionDialogDelegate() {}
+
+void PermissionDialogDelegate::DismissDialog() {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  Java_PermissionDialogDelegate_dismissFromNative(env, j_delegate_.obj());
+}
+
+void PermissionDialogDelegate::DidFinishNavigation(
+    content::NavigationHandle* navigation_handle) {
+  if (!navigation_handle->IsInMainFrame() ||
+      !navigation_handle->HasCommitted() ||
+      navigation_handle->IsSameDocument()) {
+    return;
+  }
+
+  DismissDialog();
+}
+
+void PermissionDialogDelegate::WebContentsDestroyed() {
+  DismissDialog();
+}
diff --git a/chrome/browser/permissions/permission_dialog_delegate.h b/chrome/browser/permissions/permission_dialog_delegate.h
index 76c4b6cd0..9b699eea 100644
--- a/chrome/browser/permissions/permission_dialog_delegate.h
+++ b/chrome/browser/permissions/permission_dialog_delegate.h
@@ -13,9 +13,9 @@
 #include "chrome/browser/media/webrtc/media_stream_devices_controller.h"
 #include "chrome/browser/permissions/permission_util.h"
 #include "components/content_settings/core/common/content_settings_types.h"
+#include "content/public/browser/web_contents_observer.h"
 
 using base::android::JavaParamRef;
-using base::android::ScopedJavaLocalRef;
 
 namespace content {
 class WebContents;
@@ -34,7 +34,7 @@
 // GroupedPermissionInfoBarDelegate, which will then source all of its data from
 // an underlying PermissionPromptAndroid object. At that time, this class will
 // also change to wrap a PermissionPromptAndroid.
-class PermissionDialogDelegate {
+class PermissionDialogDelegate : public content::WebContentsObserver {
  public:
   using PermissionSetCallback = base::Callback<void(bool, PermissionAction)>;
 
@@ -73,9 +73,19 @@
   PermissionDialogDelegate(
       TabAndroid* tab,
       std::unique_ptr<PermissionInfoBarDelegate> infobar_delegate_);
-  ~PermissionDialogDelegate();
+  ~PermissionDialogDelegate() override;
 
-  ScopedJavaLocalRef<jobject> CreateJavaDelegate(JNIEnv* env);
+  void CreateJavaDelegate(JNIEnv* env);
+
+  // On navigation or page destruction, hide the dialog.
+  void DismissDialog();
+
+  // WebContentsObserver:
+  void DidFinishNavigation(
+      content::NavigationHandle* navigation_handle) override;
+  void WebContentsDestroyed() override;
+
+  base::android::ScopedJavaGlobalRef<jobject> j_delegate_;
 
   TabAndroid* tab_;
 
diff --git a/chrome/browser/renderer_context_menu/spelling_menu_observer.cc b/chrome/browser/renderer_context_menu/spelling_menu_observer.cc
index 61de0fd2..355d84d 100644
--- a/chrome/browser/renderer_context_menu/spelling_menu_observer.cc
+++ b/chrome/browser/renderer_context_menu/spelling_menu_observer.cc
@@ -315,9 +315,7 @@
     typedef std::vector<SpellCheckResult> SpellCheckResults;
     for (SpellCheckResults::const_iterator it = results.begin();
          it != results.end(); ++it) {
-      // If there's more than one replacement, we can't automatically apply it
-      if (it->replacements.size() == 1)
-        result_.replace(it->location, it->length, it->replacements[0]);
+      result_.replace(it->location, it->length, it->replacement);
     }
     base::string16 result = base::i18n::ToLower(result_);
     for (std::vector<base::string16>::const_iterator it = suggestions_.begin();
diff --git a/chrome/browser/resources/md_bookmarks/command_manager.js b/chrome/browser/resources/md_bookmarks/command_manager.js
index 7dbf8d07..aef8c5a 100644
--- a/chrome/browser/resources/md_bookmarks/command_manager.js
+++ b/chrome/browser/resources/md_bookmarks/command_manager.js
@@ -58,6 +58,9 @@
           cr.isMac ? 'meta+enter' : 'ctrl+enter';
       this.shortcuts_[Command.OPEN_NEW_WINDOW] = 'shift+enter';
       this.shortcuts_[Command.OPEN] = cr.isMac ? 'meta+down' : 'enter';
+      this.shortcuts_[Command.UNDO] = cr.isMac ? 'meta+z' : 'ctrl+z';
+      this.shortcuts_[Command.REDO] =
+          cr.isMac ? 'meta+shift+z' : 'ctrl+y ctrl+shift+z';
     },
 
     detached: function() {
@@ -107,6 +110,9 @@
       switch (command) {
         case Command.OPEN:
           return itemIds.size > 0;
+        case Command.UNDO:
+        case Command.REDO:
+          return true;
         default:
           return this.isCommandVisible_(command, itemIds) &&
               this.isCommandEnabled_(command, itemIds);
@@ -178,6 +184,12 @@
                 // TODO(jiaxi): Add toast later.
               });
           break;
+        case Command.UNDO:
+          chrome.bookmarkManagerPrivate.undo();
+          break;
+        case Command.REDO:
+          chrome.bookmarkManagerPrivate.redo();
+          break;
         case Command.OPEN_NEW_TAB:
         case Command.OPEN_NEW_WINDOW:
         case Command.OPEN_INCOGNITO:
diff --git a/chrome/browser/resources/md_bookmarks/constants.js b/chrome/browser/resources/md_bookmarks/constants.js
index bcbee2b9..e93f0a43 100644
--- a/chrome/browser/resources/md_bookmarks/constants.js
+++ b/chrome/browser/resources/md_bookmarks/constants.js
@@ -26,6 +26,8 @@
   OPEN_NEW_TAB: 'open-new-tab',
   OPEN_NEW_WINDOW: 'open-new-window',
   OPEN_INCOGNITO: 'open-incognito',
+  UNDO: 'undo',
+  REDO: 'redo',
   // OPEN triggers when you double-click an item.
   OPEN: 'open',
 };
diff --git a/chrome/browser/spellchecker/spellcheck_message_filter_platform_mac_unittest.cc b/chrome/browser/spellchecker/spellcheck_message_filter_platform_mac_unittest.cc
index 1ad7b2b1..e9810e8 100644
--- a/chrome/browser/spellchecker/spellcheck_message_filter_platform_mac_unittest.cc
+++ b/chrome/browser/spellchecker/spellcheck_message_filter_platform_mac_unittest.cc
@@ -34,7 +34,7 @@
   // local & remote result - must be flagged SPELLING, uses remote suggestion.
   SpellCheckResult result(SpellCheckResult::SPELLING, 20, 5, local_suggestion);
   local_results.push_back(result);
-  result.replacements[0] = remote_suggestion;
+  result.replacement = remote_suggestion;
   remote_results.push_back(result);
 
   SpellCheckMessageFilterPlatform::CombineResults(&remote_results,
@@ -45,7 +45,7 @@
   EXPECT_EQ(0, remote_results[0].location);
   EXPECT_EQ(SpellCheckResult::SPELLING, remote_results[1].decoration);
   EXPECT_EQ(20, remote_results[1].location);
-  EXPECT_EQ(remote_suggestion, remote_results[1].replacements[0]);
+  EXPECT_EQ(remote_suggestion, remote_results[1].replacement);
 }
 
 TEST(SpellCheckMessageFilterPlatformMacTest, TestOverrideThread) {
diff --git a/chrome/browser/spellchecker/spellcheck_message_filter_unittest.cc b/chrome/browser/spellchecker/spellcheck_message_filter_unittest.cc
index 10477b8..62d445f9 100644
--- a/chrome/browser/spellchecker/spellcheck_message_filter_unittest.cc
+++ b/chrome/browser/spellchecker/spellcheck_message_filter_unittest.cc
@@ -121,7 +121,7 @@
   EXPECT_EQ(kDecoration, sent_results[0].decoration);
   EXPECT_EQ(kLocation, sent_results[0].location);
   EXPECT_EQ(kLength, sent_results[0].length);
-  EXPECT_EQ(kReplacement, sent_results[0].replacements[0]);
+  EXPECT_EQ(kReplacement, sent_results[0].replacement);
 }
 
 TEST(SpellCheckMessageFilterTest, OnTextCheckCompleteTest) {
diff --git a/chrome/browser/spellchecker/spelling_service_client_unittest.cc b/chrome/browser/spellchecker/spelling_service_client_unittest.cc
index e87056c..a90ea29 100644
--- a/chrome/browser/spellchecker/spelling_service_client_unittest.cc
+++ b/chrome/browser/spellchecker/spelling_service_client_unittest.cc
@@ -160,7 +160,7 @@
     base::string16 text(base::UTF8ToUTF16(sanitized_request_text_));
     for (std::vector<SpellCheckResult>::const_iterator it = results.begin();
          it != results.end(); ++it) {
-      text.replace(it->location, it->length, it->replacements[0]);
+      text.replace(it->location, it->length, it->replacement);
     }
     EXPECT_EQ(corrected_text_, text);
   }
diff --git a/chrome/browser/ui/bookmarks/bookmark_context_menu_controller.cc b/chrome/browser/ui/bookmarks/bookmark_context_menu_controller.cc
index 41b4582d..cc99120 100644
--- a/chrome/browser/ui/bookmarks/bookmark_context_menu_controller.cc
+++ b/chrome/browser/ui/bookmarks/bookmark_context_menu_controller.cc
@@ -273,7 +273,6 @@
     }
 
     case IDC_BOOKMARK_MANAGER: {
-      base::RecordAction(UserMetricsAction("ShowBookmarkManager"));
       if (selection_.size() != 1)
         chrome::ShowBookmarkManager(browser_);
       else if (selection_[0]->is_folder())
diff --git a/chrome/browser/ui/chrome_pages.cc b/chrome/browser/ui/chrome_pages.cc
index 78cf00f..ce47b609 100644
--- a/chrome/browser/ui/chrome_pages.cc
+++ b/chrome/browser/ui/chrome_pages.cc
@@ -14,6 +14,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "build/build_config.h"
+#include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/download/download_shelf.h"
 #include "chrome/browser/extensions/launch_util.h"
 #include "chrome/browser/profiles/profile.h"
@@ -28,11 +29,14 @@
 #include "chrome/browser/ui/settings_window_manager.h"
 #include "chrome/browser/ui/singleton_tabs.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#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"
+#include "components/bookmarks/browser/bookmark_node.h"
 #include "components/signin/core/browser/signin_header_helper.h"
 #include "components/signin/core/common/profile_management_switches.h"
 #include "content/public/browser/web_contents.h"
@@ -67,15 +71,12 @@
 
 const char kHashMark[] = "#";
 
-void OpenBookmarkManagerWithHash(Browser* browser,
-                                 const std::string& action,
-                                 int64_t node_id) {
-  base::RecordAction(UserMetricsAction("ShowBookmarkManager"));
-  base::RecordAction(UserMetricsAction("ShowBookmarks"));
-  NavigateParams params(GetSingletonTabNavigateParams(
-      browser,
-      GURL(kChromeUIBookmarksURL).Resolve(base::StringPrintf(
-          "/#%s%s", action.c_str(), base::Int64ToString(node_id).c_str()))));
+void OpenBookmarkManagerForNode(Browser* browser, int64_t node_id) {
+  GURL url = GURL(kChromeUIBookmarksURL)
+                 .Resolve(base::StringPrintf(
+                     MdBookmarksUI::IsEnabled() ? "/?id=%s" : "/#%s",
+                     base::Int64ToString(node_id).c_str()));
+  NavigateParams params(GetSingletonTabNavigateParams(browser, url));
   params.path_behavior = NavigateParams::IGNORE_AND_NAVIGATE;
   ShowSingletonTabOverwritingNTP(browser, params);
 }
@@ -185,14 +186,22 @@
 
 void ShowBookmarkManager(Browser* browser) {
   base::RecordAction(UserMetricsAction("ShowBookmarkManager"));
-  base::RecordAction(UserMetricsAction("ShowBookmarks"));
+  if (MdBookmarksUI::IsEnabled()) {
+    const bookmarks::BookmarkNode* bookmark_bar_node =
+        BookmarkModelFactory::GetForBrowserContext(browser->profile())
+            ->bookmark_bar_node();
+    OpenBookmarkManagerForNode(browser, bookmark_bar_node->id());
+    return;
+  }
+
   ShowSingletonTabOverwritingNTP(
       browser,
       GetSingletonTabNavigateParams(browser, GURL(kChromeUIBookmarksURL)));
 }
 
 void ShowBookmarkManagerForNode(Browser* browser, int64_t node_id) {
-  OpenBookmarkManagerWithHash(browser, std::string(), node_id);
+  base::RecordAction(UserMetricsAction("ShowBookmarkManager"));
+  OpenBookmarkManagerForNode(browser, node_id);
 }
 
 void ShowHistory(Browser* browser) {
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 4181b412..d341329 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -284,7 +284,7 @@
 
 // Enables the pref service. See https://crbug.com/654988.
 const base::Feature kPrefService{"PrefService",
-                                 base::FEATURE_DISABLED_BY_DEFAULT};
+                                 base::FEATURE_ENABLED_BY_DEFAULT};
 
 #if defined(OS_CHROMEOS)
 // The lock screen will be preloaded so it is instantly available when the user
diff --git a/chrome/test/data/webui/md_bookmarks/command_manager_test.js b/chrome/test/data/webui/md_bookmarks/command_manager_test.js
index e4d82ea..a862fbd 100644
--- a/chrome/test/data/webui/md_bookmarks/command_manager_test.js
+++ b/chrome/test/data/webui/md_bookmarks/command_manager_test.js
@@ -113,6 +113,19 @@
     commandManager.assertLastCommand('edit', ['11']);
   });
 
+  test('undo and redo commands trigger', function() {
+    var undoModifier = cr.isMac ? 'meta' : 'ctrl';
+    var undoKey = 'z';
+    var redoModifier = cr.isMac ? ['meta', 'shift'] : 'ctrl'
+    var redoKey = cr.isMac ? 'z' : 'y';
+
+    MockInteractions.pressAndReleaseKeyOn(document, '', undoModifier, undoKey);
+    commandManager.assertLastCommand('undo');
+
+    MockInteractions.pressAndReleaseKeyOn(document, '', redoModifier, redoKey);
+    commandManager.assertLastCommand('redo');
+  });
+
   test('does not delete children at same time as ancestor', function() {
     var lastDelete = null;
     chrome.bookmarkManagerPrivate.removeTrees = function(idArray) {
diff --git a/chromecast/browser/cast_network_delegate.cc b/chromecast/browser/cast_network_delegate.cc
index 3e004a0..3be211f 100644
--- a/chromecast/browser/cast_network_delegate.cc
+++ b/chromecast/browser/cast_network_delegate.cc
@@ -18,14 +18,16 @@
 CastNetworkDelegate::~CastNetworkDelegate() {
 }
 
-bool CastNetworkDelegate::OnCanAccessFile(const net::URLRequest& request,
-                                          const base::FilePath& path) const {
+bool CastNetworkDelegate::OnCanAccessFile(
+    const net::URLRequest& request,
+    const base::FilePath& original_path,
+    const base::FilePath& absolute_path) const {
   if (base::CommandLine::ForCurrentProcess()->
       HasSwitch(switches::kEnableLocalFileAccesses)) {
     return true;
   }
 
-  LOG(WARNING) << "Could not access file " << path.value()
+  LOG(WARNING) << "Could not access file " << original_path.value()
                << ". All file accesses are forbidden.";
   return false;
 }
diff --git a/chromecast/browser/cast_network_delegate.h b/chromecast/browser/cast_network_delegate.h
index c573a6c..2a7f08d 100644
--- a/chromecast/browser/cast_network_delegate.h
+++ b/chromecast/browser/cast_network_delegate.h
@@ -31,7 +31,8 @@
  private:
   // net::NetworkDelegate implementation:
   bool OnCanAccessFile(const net::URLRequest& request,
-                       const base::FilePath& path) const override;
+                       const base::FilePath& original_path,
+                       const base::FilePath& absolute_path) const override;
 
   DISALLOW_COPY_AND_ASSIGN(CastNetworkDelegate);
 };
diff --git a/components/cronet/android/cronet_url_request_context_adapter.cc b/components/cronet/android/cronet_url_request_context_adapter.cc
index 9e1ebfe..10c1361 100644
--- a/components/cronet/android/cronet_url_request_context_adapter.cc
+++ b/components/cronet/android/cronet_url_request_context_adapter.cc
@@ -394,7 +394,8 @@
   }
 
   bool OnCanAccessFile(const net::URLRequest& request,
-                       const base::FilePath& path) const override {
+                       const base::FilePath& original_path,
+                       const base::FilePath& absolute_path) const override {
     return false;
   }
 
diff --git a/components/spellcheck/common/BUILD.gn b/components/spellcheck/common/BUILD.gn
index c03f2e4..4806ceef 100644
--- a/components/spellcheck/common/BUILD.gn
+++ b/components/spellcheck/common/BUILD.gn
@@ -13,7 +13,6 @@
     "spellcheck_message_generator.cc",
     "spellcheck_message_generator.h",
     "spellcheck_messages.h",
-    "spellcheck_result.cc",
     "spellcheck_result.h",
     "spellcheck_switches.cc",
     "spellcheck_switches.h",
diff --git a/components/spellcheck/common/spellcheck_messages.h b/components/spellcheck/common/spellcheck_messages.h
index a56d772..e8e83a8 100644
--- a/components/spellcheck/common/spellcheck_messages.h
+++ b/components/spellcheck/common/spellcheck_messages.h
@@ -23,7 +23,7 @@
   IPC_STRUCT_TRAITS_MEMBER(decoration)
   IPC_STRUCT_TRAITS_MEMBER(location)
   IPC_STRUCT_TRAITS_MEMBER(length)
-  IPC_STRUCT_TRAITS_MEMBER(replacements)
+  IPC_STRUCT_TRAITS_MEMBER(replacement)
 IPC_STRUCT_TRAITS_END()
 
 // Messages sent from the browser to the renderer.
diff --git a/components/spellcheck/common/spellcheck_result.cc b/components/spellcheck/common/spellcheck_result.cc
deleted file mode 100644
index ba5e0c9..0000000
--- a/components/spellcheck/common/spellcheck_result.cc
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 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 "components/spellcheck/common/spellcheck_result.h"
-#include <vector>
-
-SpellCheckResult::SpellCheckResult(Decoration d,
-                                   int loc,
-                                   int len,
-                                   const std::vector<base::string16>& rep)
-    : decoration(d), location(loc), length(len), replacements(rep) {}
-
-SpellCheckResult::SpellCheckResult(Decoration d,
-                                   int loc,
-                                   int len,
-                                   const base::string16& rep)
-    : decoration(d),
-      location(loc),
-      length(len),
-      replacements(std::vector<base::string16>({rep})) {}
-
-SpellCheckResult::~SpellCheckResult() = default;
-
-SpellCheckResult::SpellCheckResult(const SpellCheckResult&) = default;
diff --git a/components/spellcheck/common/spellcheck_result.h b/components/spellcheck/common/spellcheck_result.h
index 1937a8f..0c4fb45 100644
--- a/components/spellcheck/common/spellcheck_result.h
+++ b/components/spellcheck/common/spellcheck_result.h
@@ -6,7 +6,6 @@
 #define COMPONENTS_SPELLCHECK_COMMON_SPELLCHECK_RESULT_H_
 
 #include <stdint.h>
-#include <vector>
 
 #include "base/strings/string16.h"
 
@@ -23,25 +22,16 @@
     GRAMMAR = 1 << 2,
   };
 
-  // Default values are so we have a default constructor for IPC::ReadParam()
-  explicit SpellCheckResult(
-      Decoration d = SPELLING,
-      int loc = 0,
-      int len = 0,
-      const std::vector<base::string16>& rep = std::vector<base::string16>());
-
-  explicit SpellCheckResult(Decoration d,
-                            int loc,
-                            int len,
-                            const base::string16& rep);
-
-  ~SpellCheckResult();
-  SpellCheckResult(const SpellCheckResult&);
+  explicit SpellCheckResult(Decoration d = SPELLING,
+                            int loc = 0,
+                            int len = 0,
+                            const base::string16& rep = base::string16())
+      : decoration(d), location(loc), length(len), replacement(rep) {}
 
   Decoration decoration;
   int location;
   int length;
-  std::vector<base::string16> replacements;
+  base::string16 replacement;
 };
 
 #endif  // COMPONENTS_SPELLCHECK_COMMON_SPELLCHECK_RESULT_H_
diff --git a/components/spellcheck/renderer/spellcheck.cc b/components/spellcheck/renderer/spellcheck.cc
index e895342f..4ef53a06 100644
--- a/components/spellcheck/renderer/spellcheck.cc
+++ b/components/spellcheck/renderer/spellcheck.cc
@@ -473,8 +473,7 @@
 
     const base::string16& misspelled_word =
         line_text.substr(spellcheck_result.location, spellcheck_result.length);
-    const std::vector<base::string16>& replacements =
-        spellcheck_result.replacements;
+    base::string16 replacement = spellcheck_result.replacement;
     SpellCheckResult::Decoration decoration = spellcheck_result.decoration;
 
     // Ignore words in custom dictionary.
@@ -483,20 +482,11 @@
       continue;
     }
 
-    std::vector<WebString> replacements_adjusted;
-    for (base::string16 replacement : replacements) {
-      // Use the same types of appostrophes as in the mispelled word.
-      PreserveOriginalApostropheTypes(misspelled_word, &replacement);
+    // Use the same types of appostrophes as in the mispelled word.
+    PreserveOriginalApostropheTypes(misspelled_word, &replacement);
 
-      // Ignore suggestions that are just changing the apostrophe type
-      // (straight vs. typographical)
-      if (replacement == misspelled_word)
-        continue;
-
-      replacements_adjusted.push_back(WebString::FromUTF16(replacement));
-    }
-
-    if (replacements_adjusted.empty())
+    // Ignore misspellings due the typographical apostrophe.
+    if (misspelled_word == replacement)
       continue;
 
     if (filter == USE_NATIVE_CHECKER) {
@@ -514,10 +504,10 @@
       }
     }
 
-    results.push_back(
-        WebTextCheckingResult(static_cast<WebTextDecorationType>(decoration),
-                              line_offset + spellcheck_result.location,
-                              spellcheck_result.length, replacements_adjusted));
+    results.push_back(WebTextCheckingResult(
+        static_cast<WebTextDecorationType>(decoration),
+        line_offset + spellcheck_result.location, spellcheck_result.length,
+        blink::WebString::FromUTF16(replacement)));
   }
 
   textcheck_results->Assign(results);
diff --git a/components/spellcheck/renderer/spellcheck_provider_test.cc b/components/spellcheck/renderer/spellcheck_provider_test.cc
index 88882d2f..ffba9d1 100644
--- a/components/spellcheck/renderer/spellcheck_provider_test.cc
+++ b/components/spellcheck/renderer/spellcheck_provider_test.cc
@@ -83,9 +83,8 @@
   text_.assign(text);
   text_check_completions_.Remove(identifier);
   std::vector<blink::WebTextCheckingResult> results;
-  results.push_back(
-      blink::WebTextCheckingResult(blink::kWebTextDecorationTypeSpelling, 0, 5,
-                                   std::vector<blink::WebString>({"hello"})));
+  results.push_back(blink::WebTextCheckingResult(
+      blink::kWebTextDecorationTypeSpelling, 0, 5, blink::WebString("hello")));
   completion->DidFinishCheckingText(results);
   last_request_ = text;
   last_results_ = results;
diff --git a/components/spellcheck/renderer/spellcheck_provider_unittest.cc b/components/spellcheck/renderer/spellcheck_provider_unittest.cc
index 02684c0..478fe21 100644
--- a/components/spellcheck/renderer/spellcheck_provider_unittest.cc
+++ b/components/spellcheck/renderer/spellcheck_provider_unittest.cc
@@ -29,9 +29,8 @@
 
   blink::WebVector<blink::WebTextCheckingResult> last_results;
   std::vector<blink::WebTextCheckingResult> results;
-  results.push_back(
-      blink::WebTextCheckingResult(blink::kWebTextDecorationTypeSpelling, 5, 3,
-                                   std::vector<blink::WebString>({"isq"})));
+  results.push_back(blink::WebTextCheckingResult(
+      blink::kWebTextDecorationTypeSpelling, 5, 3, blink::WebString("isq")));
   last_results.Assign(results);
   provider_.SetLastResults(base::ASCIIToUTF16("This isq a test"), last_results);
   EXPECT_TRUE(provider_.SatisfyRequestFromCache(
diff --git a/components/spellcheck/renderer/spellcheck_unittest.cc b/components/spellcheck/renderer/spellcheck_unittest.cc
index 0fdb7dd9..ef73f2d 100644
--- a/components/spellcheck/renderer/spellcheck_unittest.cc
+++ b/components/spellcheck/renderer/spellcheck_unittest.cc
@@ -1201,10 +1201,9 @@
       SpellCheckResult::SPELLING, 6, 6,
       base::WideToUTF16(L"haven" TYPOGRAPHICAL_APOSTROPHE L"t")));
   spellcheck_results.push_back(SpellCheckResult(
-      SpellCheckResult::SPELLING, 13, 10,
-      base::WideToUTF16(
-          L"in" TYPOGRAPHICAL_APOSTROPHE L"n" TYPOGRAPHICAL_APOSTROPHE L"ou"
-          L"t" TYPOGRAPHICAL_APOSTROPHE L"s")));
+        SpellCheckResult::SPELLING, 13, 10, base::WideToUTF16(
+            L"in" TYPOGRAPHICAL_APOSTROPHE L"n" TYPOGRAPHICAL_APOSTROPHE L"out"
+            TYPOGRAPHICAL_APOSTROPHE L"s")));
 
   // Replacements that differ only by apostrophe type should be ignored.
   spellcheck_results.push_back(
@@ -1214,66 +1213,26 @@
       SpellCheckResult(SpellCheckResult::SPELLING, 29, 4,
                        base::WideToUTF16(L"I" TYPOGRAPHICAL_APOSTROPHE L"ve")));
 
-  // If we have multiple replacements that all differ only by apostrophe type,
-  // we should ignore this misspelling.
-  spellcheck_results.push_back(SpellCheckResult(
-      SpellCheckResult::SPELLING, 0, 11,
-      std::vector<base::string16>(
-          {base::UTF8ToUTF16("Ik've havn'"),
-           base::WideToUTF16(L"Ik" TYPOGRAPHICAL_APOSTROPHE
-                             "ve havn" TYPOGRAPHICAL_APOSTROPHE)})));
-
-  // If we have multiple replacements where some only differ by apostrophe type
-  // and some don't, we should keep this misspelling, but remove the
-  // replacements that only differ by apostrophe type.
-  spellcheck_results.push_back(SpellCheckResult(
-      SpellCheckResult::SPELLING, 0, 5,
-      std::vector<base::string16>(
-          {base::UTF8ToUTF16("I've"), base::UTF8ToUTF16("Ive"),
-           base::WideToUTF16(L"Ik" TYPOGRAPHICAL_APOSTROPHE "ve")})));
-
-  // Similar to the previous case except with the apostrophe changing from
-  // typographical to straight instead of the other direction
-  spellcheck_results.push_back(SpellCheckResult(
-      SpellCheckResult::SPELLING, 6, 6,
-      std::vector<base::string16>({base::UTF8ToUTF16("havn't"),
-                                   base::UTF8ToUTF16("havnt"),
-                                   base::UTF8ToUTF16("haven't")})));
-
-  // If we have multiple replacements, none of which differ only by apostrophe
-  // type, we should keep this misspelling.
-  spellcheck_results.push_back(SpellCheckResult(
-      SpellCheckResult::SPELLING, 6, 6,
-      std::vector<base::string16>(
-          {base::UTF8ToUTF16("have"), base::UTF8ToUTF16("haven't")})));
-
   blink::WebVector<blink::WebTextCheckingResult> textcheck_results;
   spell_check()->CreateTextCheckingResults(SpellCheck::USE_NATIVE_CHECKER, 0,
                                            text, spellcheck_results,
                                            &textcheck_results);
 
-  static std::vector<std::vector<const wchar_t*>> kExpectedReplacements = {
-      {L"I've"},
-      {L"haven" TYPOGRAPHICAL_APOSTROPHE L"t"},
-      {L"in'n" TYPOGRAPHICAL_APOSTROPHE L"out's"},
-      {L"I've"},
-      {L"haven" TYPOGRAPHICAL_APOSTROPHE L"t"},
-      {L"in'n" TYPOGRAPHICAL_APOSTROPHE L"out" TYPOGRAPHICAL_APOSTROPHE L"s"},
-      {L"I've", L"Ive"},
-      {L"havnt", L"haven" TYPOGRAPHICAL_APOSTROPHE "t"},
-      {L"have", L"haven" TYPOGRAPHICAL_APOSTROPHE "t"},
+  static const wchar_t* kExpectedReplacements[] = {
+      L"I've",
+      L"haven" TYPOGRAPHICAL_APOSTROPHE L"t",
+      L"in'n" TYPOGRAPHICAL_APOSTROPHE L"out's",
+      L"I've",
+      L"haven" TYPOGRAPHICAL_APOSTROPHE L"t",
+      L"in'n" TYPOGRAPHICAL_APOSTROPHE L"out" TYPOGRAPHICAL_APOSTROPHE L"s",
   };
 
-  ASSERT_EQ(kExpectedReplacements.size(), textcheck_results.size());
-  for (size_t i = 0; i < kExpectedReplacements.size(); ++i) {
-    EXPECT_EQ(kExpectedReplacements[i].size(),
-              textcheck_results[i].replacements.size());
-    for (size_t j = 0; j < kExpectedReplacements[i].size(); ++j) {
-      EXPECT_EQ(base::WideToUTF16(kExpectedReplacements[i][j]),
-                textcheck_results[i].replacements[j].Utf16())
-          << "i=" << i << "\nj=" << j << "\nactual: \""
-          << textcheck_results[i].replacements[j].Utf16() << "\"";
-    }
+  ASSERT_EQ(arraysize(kExpectedReplacements), textcheck_results.size());
+  for (size_t i = 0; i < arraysize(kExpectedReplacements); ++i) {
+    EXPECT_EQ(base::WideToUTF16(kExpectedReplacements[i]),
+              textcheck_results[i].replacement.Utf16())
+        << "i=" << i << "\nactual: \""
+        << textcheck_results[i].replacement.Utf16() << "\"";
   }
 }
 
diff --git a/content/browser/permissions/permission_service_impl.cc b/content/browser/permissions/permission_service_impl.cc
index 50f13c5..a62913c 100644
--- a/content/browser/permissions/permission_service_impl.cc
+++ b/content/browser/permissions/permission_service_impl.cc
@@ -106,44 +106,11 @@
     const url::Origin& origin,
     bool user_gesture,
     const PermissionStatusCallback& callback) {
-  // This condition is valid if the call is coming from a ChildThread instead of
-  // a RenderFrame. Some consumers of the service run in Workers and some in
-  // Frames. In the context of a Worker, it is not possible to show a
-  // permission prompt because there is no tab. In the context of a Frame, we
-  // can. Even if the call comes from a context where it is not possible to show
-  // any UI, we want to still return something relevant so the current
-  // permission status is returned.
-  BrowserContext* browser_context = context_->GetBrowserContext();
-  DCHECK(browser_context);
-  if (!context_->render_frame_host() ||
-      !browser_context->GetPermissionManager()) {
-    callback.Run(GetPermissionStatus(permission, origin));
-    return;
-  }
-
-  int pending_request_id =
-      pending_requests_.Add(base::MakeUnique<PendingRequest>(
-          base::Bind(&PermissionRequestResponseCallbackWrapper, callback), 1));
-  int id = browser_context->GetPermissionManager()->RequestPermission(
-      PermissionDescriptorToPermissionType(permission),
-      context_->render_frame_host(), origin.GetURL(), user_gesture,
-      base::Bind(&PermissionServiceImpl::OnRequestPermissionResponse,
-                 weak_factory_.GetWeakPtr(), pending_request_id));
-
-  // Check if the request still exists. It might have been removed by the
-  // callback if it was run synchronously.
-  PendingRequest* pending_request = pending_requests_.Lookup(
-      pending_request_id);
-  if (!pending_request)
-      return;
-  pending_request->id = id;
-}
-
-void PermissionServiceImpl::OnRequestPermissionResponse(
-    int pending_request_id,
-    PermissionStatus status) {
-  OnRequestPermissionsResponse(pending_request_id,
-                               std::vector<PermissionStatus>(1, status));
+  std::vector<PermissionDescriptorPtr> permissions;
+  permissions.push_back(std::move(permission));
+  RequestPermissions(
+      std::move(permissions), origin, user_gesture,
+      base::Bind(&PermissionRequestResponseCallbackWrapper, callback));
 }
 
 void PermissionServiceImpl::RequestPermissions(
diff --git a/content/browser/permissions/permission_service_impl.h b/content/browser/permissions/permission_service_impl.h
index cfce915..9d8430d 100644
--- a/content/browser/permissions/permission_service_impl.h
+++ b/content/browser/permissions/permission_service_impl.h
@@ -67,8 +67,6 @@
       blink::mojom::PermissionStatus last_known_status,
       blink::mojom::PermissionObserverPtr observer) override;
 
-  void OnRequestPermissionResponse(int pending_request_id,
-                                   blink::mojom::PermissionStatus status);
   void OnRequestPermissionsResponse(
       int pending_request_id,
       const std::vector<blink::mojom::PermissionStatus>& result);
diff --git a/content/shell/browser/shell_network_delegate.cc b/content/shell/browser/shell_network_delegate.cc
index 9565d26..46252eb 100644
--- a/content/shell/browser/shell_network_delegate.cc
+++ b/content/shell/browser/shell_network_delegate.cc
@@ -109,8 +109,10 @@
   return rv == net::OK;
 }
 
-bool ShellNetworkDelegate::OnCanAccessFile(const net::URLRequest& request,
-                                           const base::FilePath& path) const {
+bool ShellNetworkDelegate::OnCanAccessFile(
+    const net::URLRequest& request,
+    const base::FilePath& original_path,
+    const base::FilePath& absolute_path) const {
   return true;
 }
 
diff --git a/content/shell/browser/shell_network_delegate.h b/content/shell/browser/shell_network_delegate.h
index 1c809751..184bbb9 100644
--- a/content/shell/browser/shell_network_delegate.h
+++ b/content/shell/browser/shell_network_delegate.h
@@ -54,7 +54,8 @@
                       const std::string& cookie_line,
                       net::CookieOptions* options) override;
   bool OnCanAccessFile(const net::URLRequest& request,
-                       const base::FilePath& path) const override;
+                       const base::FilePath& original_path,
+                       const base::FilePath& absolute_path) const override;
   bool OnAreExperimentalCookieFeaturesEnabled() const override;
   bool OnCancelURLRequestWithPolicyViolatingReferrerHeader(
       const net::URLRequest& request,
diff --git a/content/shell/browser/shell_permission_manager.cc b/content/shell/browser/shell_permission_manager.cc
index 5311d5d..aed1bd3 100644
--- a/content/shell/browser/shell_permission_manager.cc
+++ b/content/shell/browser/shell_permission_manager.cc
@@ -47,7 +47,7 @@
     bool user_gesture,
     const base::Callback<
         void(const std::vector<blink::mojom::PermissionStatus>&)>& callback) {
-  std::vector<blink::mojom::PermissionStatus> result(permissions.size());
+  std::vector<blink::mojom::PermissionStatus> result;
   for (const auto& permission : permissions) {
     result.push_back(IsWhitelistedPermissionType(permission)
                          ? blink::mojom::PermissionStatus::GRANTED
diff --git a/content/shell/test_runner/mock_spell_check.cc b/content/shell/test_runner/mock_spell_check.cc
index 141dc40..52c7bca 100644
--- a/content/shell/test_runner/mock_spell_check.cc
+++ b/content/shell/test_runner/mock_spell_check.cc
@@ -114,12 +114,12 @@
     const blink::WebString& text,
     std::vector<blink::WebTextCheckingResult>* results) {
   if (text == "Helllo wordl.") {
-    results->push_back(blink::WebTextCheckingResult(
-        blink::kWebTextDecorationTypeSpelling, 0, 6,
-        std::vector<blink::WebString>({"Hello"})));
-    results->push_back(blink::WebTextCheckingResult(
-        blink::kWebTextDecorationTypeSpelling, 7, 5,
-        std::vector<blink::WebString>({"world"})));
+    results->push_back(
+        blink::WebTextCheckingResult(blink::kWebTextDecorationTypeSpelling, 0,
+                                     6, blink::WebString("Hello")));
+    results->push_back(
+        blink::WebTextCheckingResult(blink::kWebTextDecorationTypeSpelling, 7,
+                                     5, blink::WebString("world")));
     return true;
   }
   return false;
diff --git a/content/shell/test_runner/spell_check_client.cc b/content/shell/test_runner/spell_check_client.cc
index 19ed128..e190811 100644
--- a/content/shell/test_runner/spell_check_client.cc
+++ b/content/shell/test_runner/spell_check_client.cc
@@ -114,7 +114,8 @@
           &suggestions);
       results.push_back(blink::WebTextCheckingResult(
           blink::kWebTextDecorationTypeSpelling, offset + misspelled_position,
-          misspelled_length, suggestions));
+          misspelled_length,
+          suggestions.IsEmpty() ? blink::WebString() : suggestions[0]));
       text = text.substr(misspelled_position + misspelled_length);
       offset += misspelled_position + misspelled_length;
     }
diff --git a/content/test/data/android/permission_navigation.html b/content/test/data/android/permission_navigation.html
new file mode 100644
index 0000000..6a73e3aaa
--- /dev/null
+++ b/content/test/data/android/permission_navigation.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+
+<script>
+function navigate() {
+  window.location = "about:blank";
+}
+
+function requestGeolocationPermission() {
+  navigator.geolocation.getCurrentPosition(function() { });
+}
+
+function requestNotificationPermission() {
+  Notification.requestPermission();
+}
+</script>
diff --git a/extensions/browser/extension_protocols.cc b/extensions/browser/extension_protocols.cc
index c1ea0a9..cc5d704 100644
--- a/extensions/browser/extension_protocols.cc
+++ b/extensions/browser/extension_protocols.cc
@@ -284,6 +284,13 @@
                           request_timer_->Elapsed());
   }
 
+  bool CanAccessFile(const base::FilePath& original_path,
+                     const base::FilePath& absolute_path) override {
+    // The access checks for the file are performed before the job is
+    // created, so we should know that this is safe.
+    return true;
+  }
+
   void OnFilePathAndLastModifiedTimeRead(base::FilePath* read_file_path,
                                          base::Time* last_modified_time) {
     file_path_ = *read_file_path;
diff --git a/headless/lib/browser/headless_network_delegate.cc b/headless/lib/browser/headless_network_delegate.cc
index f58a566..2cc325f 100644
--- a/headless/lib/browser/headless_network_delegate.cc
+++ b/headless/lib/browser/headless_network_delegate.cc
@@ -86,7 +86,8 @@
 
 bool HeadlessNetworkDelegate::OnCanAccessFile(
     const net::URLRequest& request,
-    const base::FilePath& path) const {
+    const base::FilePath& original_path,
+    const base::FilePath& absolute_path) const {
   return true;
 }
 
diff --git a/headless/lib/browser/headless_network_delegate.h b/headless/lib/browser/headless_network_delegate.h
index 5395d38..f2a550ad 100644
--- a/headless/lib/browser/headless_network_delegate.h
+++ b/headless/lib/browser/headless_network_delegate.h
@@ -64,7 +64,8 @@
                       net::CookieOptions* options) override;
 
   bool OnCanAccessFile(const net::URLRequest& request,
-                       const base::FilePath& path) const override;
+                       const base::FilePath& original_path,
+                       const base::FilePath& absolute_path) const override;
 
   DISALLOW_COPY_AND_ASSIGN(HeadlessNetworkDelegate);
 };
diff --git a/ios/chrome/browser/net/ios_chrome_network_delegate.cc b/ios/chrome/browser/net/ios_chrome_network_delegate.cc
index 85a4b0d..da8bfda 100644
--- a/ios/chrome/browser/net/ios_chrome_network_delegate.cc
+++ b/ios/chrome/browser/net/ios_chrome_network_delegate.cc
@@ -146,7 +146,8 @@
 
 bool IOSChromeNetworkDelegate::OnCanAccessFile(
     const net::URLRequest& request,
-    const base::FilePath& path) const {
+    const base::FilePath& original_path,
+    const base::FilePath& absolute_path) const {
   return true;
 }
 
diff --git a/ios/chrome/browser/net/ios_chrome_network_delegate.h b/ios/chrome/browser/net/ios_chrome_network_delegate.h
index ca3ba85..5521baa 100644
--- a/ios/chrome/browser/net/ios_chrome_network_delegate.h
+++ b/ios/chrome/browser/net/ios_chrome_network_delegate.h
@@ -64,7 +64,8 @@
                       const std::string& cookie_line,
                       net::CookieOptions* options) override;
   bool OnCanAccessFile(const net::URLRequest& request,
-                       const base::FilePath& path) const override;
+                       const base::FilePath& original_path,
+                       const base::FilePath& absolute_path) const override;
   bool OnCanEnablePrivacyMode(
       const GURL& url,
       const GURL& first_party_for_cookies) const override;
diff --git a/ios/web/shell/shell_network_delegate.cc b/ios/web/shell/shell_network_delegate.cc
index 421aa76..717aef9 100644
--- a/ios/web/shell/shell_network_delegate.cc
+++ b/ios/web/shell/shell_network_delegate.cc
@@ -78,8 +78,10 @@
   return true;
 }
 
-bool ShellNetworkDelegate::OnCanAccessFile(const net::URLRequest& request,
-                                           const base::FilePath& path) const {
+bool ShellNetworkDelegate::OnCanAccessFile(
+    const net::URLRequest& request,
+    const base::FilePath& original_path,
+    const base::FilePath& absolute_path) const {
   return true;
 }
 
diff --git a/ios/web/shell/shell_network_delegate.h b/ios/web/shell/shell_network_delegate.h
index 0929c15..ab7d47d 100644
--- a/ios/web/shell/shell_network_delegate.h
+++ b/ios/web/shell/shell_network_delegate.h
@@ -51,7 +51,8 @@
                       const std::string& cookie_line,
                       net::CookieOptions* options) override;
   bool OnCanAccessFile(const net::URLRequest& request,
-                       const base::FilePath& path) const override;
+                       const base::FilePath& original_path,
+                       const base::FilePath& absolute_path) const override;
 
   DISALLOW_COPY_AND_ASSIGN(ShellNetworkDelegate);
 };
diff --git a/ios/web_view/internal/web_view_network_delegate.cc b/ios/web_view/internal/web_view_network_delegate.cc
index 761aa7b..d875343 100644
--- a/ios/web_view/internal/web_view_network_delegate.cc
+++ b/ios/web_view/internal/web_view_network_delegate.cc
@@ -75,8 +75,10 @@
   return true;
 }
 
-bool WebViewNetworkDelegate::OnCanAccessFile(const net::URLRequest& request,
-                                             const base::FilePath& path) const {
+bool WebViewNetworkDelegate::OnCanAccessFile(
+    const net::URLRequest& request,
+    const base::FilePath& original_path,
+    const base::FilePath& absolute_path) const {
   return true;
 }
 
diff --git a/ios/web_view/internal/web_view_network_delegate.h b/ios/web_view/internal/web_view_network_delegate.h
index bcc1253..119bca36 100644
--- a/ios/web_view/internal/web_view_network_delegate.h
+++ b/ios/web_view/internal/web_view_network_delegate.h
@@ -51,7 +51,8 @@
                       const std::string& cookie_line,
                       net::CookieOptions* options) override;
   bool OnCanAccessFile(const net::URLRequest& request,
-                       const base::FilePath& path) const override;
+                       const base::FilePath& original_path,
+                       const base::FilePath& absolute_path) const override;
 
   DISALLOW_COPY_AND_ASSIGN(WebViewNetworkDelegate);
 };
diff --git a/mojo/public/cpp/system/wait_set.cc b/mojo/public/cpp/system/wait_set.cc
index 75ce93e..2081309 100644
--- a/mojo/public/cpp/system/wait_set.cc
+++ b/mojo/public/cpp/system/wait_set.cc
@@ -32,6 +32,8 @@
   void ShutDown() {
     // NOTE: This may immediately invoke Notify for every context.
     watcher_handle_.reset();
+
+    cancelled_contexts_.clear();
   }
 
   MojoResult AddEvent(base::WaitableEvent* event) {
@@ -93,6 +95,11 @@
     scoped_refptr<Context> context;
     {
       base::AutoLock lock(lock_);
+
+      // Always clear |cancelled_contexts_| in case it's accumulated any more
+      // entries since the last time we ran.
+      cancelled_contexts_.clear();
+
       auto it = handle_to_context_.find(handle);
       if (it == handle_to_context_.end())
         return MOJO_RESULT_NOT_FOUND;
@@ -116,13 +123,6 @@
     // to |cancelled_contexts_|.
     DCHECK(rv == MOJO_RESULT_OK || rv == MOJO_RESULT_NOT_FOUND);
 
-    {
-      // Always clear |cancelled_contexts_| in case it's accumulated any more
-      // entries since the last time we ran.
-      base::AutoLock lock(lock_);
-      cancelled_contexts_.clear();
-    }
-
     return rv;
   }
 
diff --git a/net/base/directory_lister.cc b/net/base/directory_lister.cc
index 51cbd12..e5ca942 100644
--- a/net/base/directory_lister.cc
+++ b/net/base/directory_lister.cc
@@ -144,6 +144,7 @@
     DirectoryListerData data;
     data.info = file_enum.GetInfo();
     data.path = path;
+    data.absolute_path = base::MakeAbsoluteFilePath(path);
     directory_list->push_back(data);
 
     /* TODO(brettw) bug 24107: It would be nice to send incremental updates.
diff --git a/net/base/directory_lister.h b/net/base/directory_lister.h
index 4491c09..0489341 100644
--- a/net/base/directory_lister.h
+++ b/net/base/directory_lister.h
@@ -32,6 +32,7 @@
   struct DirectoryListerData {
     base::FileEnumerator::FileInfo info;
     base::FilePath path;
+    base::FilePath absolute_path;
   };
 
   // Implement this class to receive directory entries.
diff --git a/net/base/layered_network_delegate.cc b/net/base/layered_network_delegate.cc
index 41c22c1..4718c4b 100644
--- a/net/base/layered_network_delegate.cc
+++ b/net/base/layered_network_delegate.cc
@@ -203,16 +203,19 @@
     CookieOptions* options) {
 }
 
-bool LayeredNetworkDelegate::OnCanAccessFile(const URLRequest& request,
-                                             const base::FilePath& path) const {
-  OnCanAccessFileInternal(request, path);
-  return nested_network_delegate_->CanAccessFile(request, path);
+bool LayeredNetworkDelegate::OnCanAccessFile(
+    const URLRequest& request,
+    const base::FilePath& original_path,
+    const base::FilePath& absolute_path) const {
+  OnCanAccessFileInternal(request, original_path, absolute_path);
+  return nested_network_delegate_->CanAccessFile(request, original_path,
+                                                 absolute_path);
 }
 
 void LayeredNetworkDelegate::OnCanAccessFileInternal(
     const URLRequest& request,
-    const base::FilePath& path) const {
-}
+    const base::FilePath& original_path,
+    const base::FilePath& absolute_path) const {}
 
 bool LayeredNetworkDelegate::OnCanEnablePrivacyMode(
     const GURL& url,
diff --git a/net/base/layered_network_delegate.h b/net/base/layered_network_delegate.h
index 562ca0f7..033ae357 100644
--- a/net/base/layered_network_delegate.h
+++ b/net/base/layered_network_delegate.h
@@ -78,7 +78,8 @@
                       const std::string& cookie_line,
                       CookieOptions* options) final;
   bool OnCanAccessFile(const URLRequest& request,
-                       const base::FilePath& path) const final;
+                       const base::FilePath& original_path,
+                       const base::FilePath& absolute_path) const final;
   bool OnCanEnablePrivacyMode(const GURL& url,
                               const GURL& first_party_for_cookies) const final;
   bool OnAreExperimentalCookieFeaturesEnabled() const final;
@@ -153,8 +154,10 @@
                                       const AuthCallback& callback,
                                       AuthCredentials* credentials);
 
-  virtual void OnCanAccessFileInternal(const URLRequest& request,
-                                       const base::FilePath& path) const;
+  virtual void OnCanAccessFileInternal(
+      const URLRequest& request,
+      const base::FilePath& original_path,
+      const base::FilePath& absolute_path) const;
 
   virtual void OnCanEnablePrivacyModeInternal(
       const GURL& url,
diff --git a/net/base/layered_network_delegate_unittest.cc b/net/base/layered_network_delegate_unittest.cc
index b92ee03..3ba85add 100644
--- a/net/base/layered_network_delegate_unittest.cc
+++ b/net/base/layered_network_delegate_unittest.cc
@@ -128,7 +128,8 @@
   }
 
   bool OnCanAccessFile(const URLRequest& request,
-                       const base::FilePath& path) const override {
+                       const base::FilePath& original_path,
+                       const base::FilePath& absolute_path) const override {
     IncrementAndCompareCounter("on_can_access_file_count");
     return false;
   }
@@ -205,7 +206,7 @@
         OnAuthRequired(request.get(), *auth_challenge, AuthCallback(), NULL));
     EXPECT_FALSE(OnCanGetCookies(*request, CookieList()));
     EXPECT_FALSE(OnCanSetCookie(*request, std::string(), NULL));
-    EXPECT_FALSE(OnCanAccessFile(*request, base::FilePath()));
+    EXPECT_FALSE(OnCanAccessFile(*request, base::FilePath(), base::FilePath()));
     EXPECT_FALSE(OnCanEnablePrivacyMode(GURL(), GURL()));
     EXPECT_FALSE(OnCancelURLRequestWithPolicyViolatingReferrerHeader(
         *request, GURL(), GURL()));
@@ -310,8 +311,10 @@
     EXPECT_EQ(1, (*counters_)["on_can_set_cookie_count"]);
   }
 
-  void OnCanAccessFileInternal(const URLRequest& request,
-                               const base::FilePath& path) const override {
+  void OnCanAccessFileInternal(
+      const URLRequest& request,
+      const base::FilePath& original_path,
+      const base::FilePath& absolute_path) const override {
     ++(*counters_)["on_can_access_file_count"];
     EXPECT_EQ(1, (*counters_)["on_can_access_file_count"]);
   }
diff --git a/net/base/network_delegate.cc b/net/base/network_delegate.cc
index 6344e4d..a40f7d6 100644
--- a/net/base/network_delegate.cc
+++ b/net/base/network_delegate.cc
@@ -159,9 +159,10 @@
 }
 
 bool NetworkDelegate::CanAccessFile(const URLRequest& request,
-                                    const base::FilePath& path) const {
+                                    const base::FilePath& original_path,
+                                    const base::FilePath& absolute_path) const {
   DCHECK(CalledOnValidThread());
-  return OnCanAccessFile(request, path);
+  return OnCanAccessFile(request, original_path, absolute_path);
 }
 
 bool NetworkDelegate::CanEnablePrivacyMode(
diff --git a/net/base/network_delegate.h b/net/base/network_delegate.h
index eaa1e30..e13ebed 100644
--- a/net/base/network_delegate.h
+++ b/net/base/network_delegate.h
@@ -105,7 +105,8 @@
                     const std::string& cookie_line,
                     CookieOptions* options);
   bool CanAccessFile(const URLRequest& request,
-                     const base::FilePath& path) const;
+                     const base::FilePath& original_path,
+                     const base::FilePath& absolute_path) const;
   bool CanEnablePrivacyMode(const GURL& url,
                             const GURL& first_party_for_cookies) const;
 
@@ -278,10 +279,13 @@
                               CookieOptions* options) = 0;
 
   // Called when a file access is attempted to allow the network delegate to
-  // allow or block access to the given file path.  Returns true if access is
-  // allowed.
+  // allow or block access to the given file path, provided in the original
+  // and absolute forms (i.e. symbolic link is resolved). It's up to
+  // subclasses of NetworkDelegate to decide which path to use for
+  // checking. Returns true if access is allowed.
   virtual bool OnCanAccessFile(const URLRequest& request,
-                               const base::FilePath& path) const = 0;
+                               const base::FilePath& original_path,
+                               const base::FilePath& absolute_path) const = 0;
 
   // Returns true if the given |url| has to be requested over connection that
   // is not tracked by the server. Usually is false, unless user privacy
diff --git a/net/base/network_delegate_impl.cc b/net/base/network_delegate_impl.cc
index 3ca66c1..323c8bd6 100644
--- a/net/base/network_delegate_impl.cc
+++ b/net/base/network_delegate_impl.cc
@@ -92,8 +92,10 @@
   return true;
 }
 
-bool NetworkDelegateImpl::OnCanAccessFile(const URLRequest& request,
-                                          const base::FilePath& path) const {
+bool NetworkDelegateImpl::OnCanAccessFile(
+    const URLRequest& request,
+    const base::FilePath& original_path,
+    const base::FilePath& absolute_path) const {
   return false;
 }
 
diff --git a/net/base/network_delegate_impl.h b/net/base/network_delegate_impl.h
index 16f2d603..0ccaf82d 100644
--- a/net/base/network_delegate_impl.h
+++ b/net/base/network_delegate_impl.h
@@ -90,7 +90,8 @@
                       CookieOptions* options) override;
 
   bool OnCanAccessFile(const URLRequest& request,
-                       const base::FilePath& path) const override;
+                       const base::FilePath& original_path,
+                       const base::FilePath& absolute_path) const override;
 
   bool OnCanEnablePrivacyMode(
       const GURL& url,
diff --git a/net/proxy/network_delegate_error_observer_unittest.cc b/net/proxy/network_delegate_error_observer_unittest.cc
index 4e38b620..d2833aa 100644
--- a/net/proxy/network_delegate_error_observer_unittest.cc
+++ b/net/proxy/network_delegate_error_observer_unittest.cc
@@ -73,7 +73,8 @@
     return true;
   }
   bool OnCanAccessFile(const URLRequest& request,
-                       const base::FilePath& path) const override {
+                       const base::FilePath& original_path,
+                       const base::FilePath& absolute_path) const override {
     return true;
   }
 
diff --git a/net/proxy/proxy_script_fetcher_impl_unittest.cc b/net/proxy/proxy_script_fetcher_impl_unittest.cc
index a1a7e5e..a57e0de 100644
--- a/net/proxy/proxy_script_fetcher_impl_unittest.cc
+++ b/net/proxy/proxy_script_fetcher_impl_unittest.cc
@@ -203,7 +203,8 @@
   }
 
   bool OnCanAccessFile(const URLRequest& request,
-                       const base::FilePath& path) const override {
+                       const base::FilePath& original_path,
+                       const base::FilePath& absolute_path) const override {
     return true;
   }
 
diff --git a/net/url_request/file_protocol_handler.cc b/net/url_request/file_protocol_handler.cc
index ceed930..ff19d16ee 100644
--- a/net/url_request/file_protocol_handler.cc
+++ b/net/url_request/file_protocol_handler.cc
@@ -26,12 +26,6 @@
   base::FilePath file_path;
   const bool is_file = FileURLToFilePath(request->url(), &file_path);
 
-  // Check file access permissions.
-  if (!network_delegate ||
-      !network_delegate->CanAccessFile(*request, file_path)) {
-    return new URLRequestErrorJob(request, network_delegate, ERR_ACCESS_DENIED);
-  }
-
   // We need to decide whether to create URLRequestFileJob for file access or
   // URLRequestFileDirJob for directory access. To avoid accessing the
   // filesystem, we only look at the path string here.
diff --git a/net/url_request/url_request_context_builder.cc b/net/url_request/url_request_context_builder.cc
index 605ff7e..5937f2a6 100644
--- a/net/url_request/url_request_context_builder.cc
+++ b/net/url_request/url_request_context_builder.cc
@@ -125,7 +125,8 @@
   }
 
   bool OnCanAccessFile(const URLRequest& request,
-                       const base::FilePath& path) const override {
+                       const base::FilePath& original_path,
+                       const base::FilePath& absolute_path) const override {
     return true;
   }
 
diff --git a/net/url_request/url_request_file_dir_job.cc b/net/url_request/url_request_file_dir_job.cc
index 435ccb00..27d7ec7 100644
--- a/net/url_request/url_request_file_dir_job.cc
+++ b/net/url_request/url_request_file_dir_job.cc
@@ -6,10 +6,12 @@
 
 #include "base/bind.h"
 #include "base/compiler_specific.h"
+#include "base/files/file_util.h"
 #include "base/location.h"
 #include "base/single_thread_task_runner.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/task_scheduler/post_task.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "net/base/directory_listing.h"
@@ -37,8 +39,12 @@
       weak_factory_(this) {}
 
 void URLRequestFileDirJob::StartAsync() {
-  lister_.Start();
-  NotifyHeadersComplete();
+  base::PostTaskWithTraitsAndReplyWithResult(
+      FROM_HERE,
+      {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+      base::Bind(&base::MakeAbsoluteFilePath, dir_path_),
+      base::Bind(&URLRequestFileDirJob::DidMakeAbsolutePath,
+                 weak_factory_.GetWeakPtr()));
 }
 
 void URLRequestFileDirJob::Start() {
@@ -136,6 +142,18 @@
 
 URLRequestFileDirJob::~URLRequestFileDirJob() {}
 
+void URLRequestFileDirJob::DidMakeAbsolutePath(
+    const base::FilePath& absolute_path) {
+  if (network_delegate() && !network_delegate()->CanAccessFile(
+                                *request(), dir_path_, absolute_path)) {
+    NotifyStartError(URLRequestStatus::FromError(ERR_ACCESS_DENIED));
+    return;
+  }
+
+  lister_.Start();
+  NotifyHeadersComplete();
+}
+
 void URLRequestFileDirJob::CompleteRead(Error error) {
   DCHECK_LE(error, OK);
   DCHECK_NE(error, ERR_IO_PENDING);
diff --git a/net/url_request/url_request_file_dir_job.h b/net/url_request/url_request_file_dir_job.h
index 85209ad..458faac 100644
--- a/net/url_request/url_request_file_dir_job.h
+++ b/net/url_request/url_request_file_dir_job.h
@@ -42,7 +42,8 @@
   ~URLRequestFileDirJob() override;
 
  private:
-  void CloseLister();
+  // Called after the target directory path is resolved to an absolute path.
+  void DidMakeAbsolutePath(const base::FilePath& absolute_path);
 
   // When we have data and a read has been pending, this function
   // will fill the response buffer and notify the request
diff --git a/net/url_request/url_request_file_job.cc b/net/url_request/url_request_file_job.cc
index 4924e02..7d68f087 100644
--- a/net/url_request/url_request_file_job.cc
+++ b/net/url_request/url_request_file_job.cc
@@ -196,6 +196,12 @@
   return GzipSourceStream::Create(std::move(source), SourceStream::TYPE_GZIP);
 }
 
+bool URLRequestFileJob::CanAccessFile(const base::FilePath& original_path,
+                                      const base::FilePath& absolute_path) {
+  return !network_delegate() || network_delegate()->CanAccessFile(
+                                    *request(), original_path, absolute_path);
+}
+
 void URLRequestFileJob::FetchMetaInfo(const base::FilePath& file_path,
                                       FileMetaInfo* meta_info) {
   base::File::Info file_info;
@@ -208,6 +214,7 @@
   // done in WorkerPool.
   meta_info->mime_type_result = GetMimeTypeFromFile(file_path,
                                                     &meta_info->mime_type);
+  meta_info->absolute_path = base::MakeAbsoluteFilePath(file_path);
 }
 
 void URLRequestFileJob::DidFetchMetaInfo(const FileMetaInfo* meta_info) {
@@ -230,6 +237,11 @@
     return;
   }
 
+  if (!CanAccessFile(file_path_, meta_info->absolute_path)) {
+    DidOpen(ERR_ACCESS_DENIED);
+    return;
+  }
+
   int flags = base::File::FLAG_OPEN |
               base::File::FLAG_READ |
               base::File::FLAG_ASYNC;
diff --git a/net/url_request/url_request_file_job.h b/net/url_request/url_request_file_job.h
index ec90767..64638ac 100644
--- a/net/url_request/url_request_file_job.h
+++ b/net/url_request/url_request_file_job.h
@@ -72,6 +72,11 @@
   base::FilePath file_path_;
 
  private:
+  // This class checks if a path is accessible via file: scheme, with
+  // NetworkDelegate. Subclasses can disable the check if needed.
+  virtual bool CanAccessFile(const base::FilePath& original_path,
+                             const base::FilePath& absolute_path);
+
   // Meta information about the file. It's used as a member in the
   // URLRequestFileJob and also passed between threads because disk access is
   // necessary to obtain it.
@@ -89,6 +94,8 @@
     bool file_exists;
     // Flag showing whether the file name actually refers to a directory.
     bool is_directory;
+    // Absolute path of the file (i.e. symbolic link is resolved).
+    base::FilePath absolute_path;
   };
 
   // Fetches file info on a background thread.
diff --git a/net/url_request/url_request_test_util.cc b/net/url_request/url_request_test_util.cc
index 5f7f123..4c602012 100644
--- a/net/url_request/url_request_test_util.cc
+++ b/net/url_request/url_request_test_util.cc
@@ -648,8 +648,10 @@
   return allow;
 }
 
-bool TestNetworkDelegate::OnCanAccessFile(const URLRequest& request,
-                                          const base::FilePath& path) const {
+bool TestNetworkDelegate::OnCanAccessFile(
+    const URLRequest& request,
+    const base::FilePath& original_path,
+    const base::FilePath& absolute_path) const {
   return can_access_files_;
 }
 
diff --git a/net/url_request/url_request_test_util.h b/net/url_request/url_request_test_util.h
index 201f07b..9868bcbb 100644
--- a/net/url_request/url_request_test_util.h
+++ b/net/url_request/url_request_test_util.h
@@ -356,7 +356,8 @@
                       const std::string& cookie_line,
                       CookieOptions* options) override;
   bool OnCanAccessFile(const URLRequest& request,
-                       const base::FilePath& path) const override;
+                       const base::FilePath& original_path,
+                       const base::FilePath& absolute_path) const override;
   bool OnAreExperimentalCookieFeaturesEnabled() const override;
   bool OnCancelURLRequestWithPolicyViolatingReferrerHeader(
       const URLRequest& request,
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index 2d158a04..5d299633 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -835,6 +835,41 @@
   TestURLRequestContext default_context_;
 };
 
+// This NetworkDelegate is picky about what files are accessible. Only
+// whitelisted files are allowed.
+class CookieBlockingNetworkDelegate : public TestNetworkDelegate {
+ public:
+  CookieBlockingNetworkDelegate(){};
+
+  // Adds |directory| to the access white list.
+  void AddToWhitelist(const base::FilePath& directory) {
+    whitelist_.insert(directory);
+  }
+
+ private:
+  // Returns true if |path| matches the white list.
+  bool OnCanAccessFileInternal(const base::FilePath& path) const {
+    for (const auto& directory : whitelist_) {
+      if (directory == path || directory.IsParent(path))
+        return true;
+    }
+    return false;
+  }
+
+  // Returns true only if both |original_path| and |absolute_path| match the
+  // white list.
+  bool OnCanAccessFile(const URLRequest& request,
+                       const base::FilePath& original_path,
+                       const base::FilePath& absolute_path) const override {
+    return (OnCanAccessFileInternal(original_path) &&
+            OnCanAccessFileInternal(absolute_path));
+  }
+
+  std::set<base::FilePath> whitelist_;
+
+  DISALLOW_COPY_AND_ASSIGN(CookieBlockingNetworkDelegate);
+};
+
 TEST_F(URLRequestTest, AboutBlankTest) {
   TestDelegate d;
   {
@@ -1083,39 +1118,176 @@
 TEST_F(URLRequestTest, AllowFileURLs) {
   base::ScopedTempDir temp_dir;
   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+  // Get an absolute path since |temp_dir| can contain a symbolic link. As of
+  // now, Mac and Android bots return a path with a symbolic link.
+  base::FilePath absolute_temp_dir =
+      base::MakeAbsoluteFilePath(temp_dir.GetPath());
+
   base::FilePath test_file;
-  ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir.GetPath(), &test_file));
+  ASSERT_TRUE(base::CreateTemporaryFileInDir(absolute_temp_dir, &test_file));
+  // The directory part of the path returned from CreateTemporaryFileInDir()
+  // can be slightly different from |absolute_temp_dir| on Windows.
+  // Example: C:\\Users\\CHROME~2 -> C:\\Users\\chrome-bot
+  // Hence the test should use the directory name of |test_file|, rather than
+  // |absolute_temp_dir|, for whitelisting.
+  base::FilePath real_temp_dir = test_file.DirName();
   std::string test_data("monkey");
   base::WriteFile(test_file, test_data.data(), test_data.size());
   GURL test_file_url = FilePathToFileURL(test_file);
-
   {
     TestDelegate d;
-    TestNetworkDelegate network_delegate;
-    network_delegate.set_can_access_files(true);
+    CookieBlockingNetworkDelegate network_delegate;
+    network_delegate.AddToWhitelist(real_temp_dir);
     default_context_.set_network_delegate(&network_delegate);
     std::unique_ptr<URLRequest> r(default_context_.CreateRequest(
         test_file_url, DEFAULT_PRIORITY, &d, TRAFFIC_ANNOTATION_FOR_TESTS));
     r->Start();
     base::RunLoop().Run();
+    // This should be allowed as the file path is whitelisted.
     EXPECT_FALSE(d.request_failed());
     EXPECT_EQ(test_data, d.data_received());
   }
 
   {
     TestDelegate d;
-    TestNetworkDelegate network_delegate;
-    network_delegate.set_can_access_files(false);
+    CookieBlockingNetworkDelegate network_delegate;
     default_context_.set_network_delegate(&network_delegate);
     std::unique_ptr<URLRequest> r(default_context_.CreateRequest(
         test_file_url, DEFAULT_PRIORITY, &d, TRAFFIC_ANNOTATION_FOR_TESTS));
     r->Start();
     base::RunLoop().Run();
+    // This should be rejected as the file path is not whitelisted.
     EXPECT_TRUE(d.request_failed());
     EXPECT_EQ("", d.data_received());
+    EXPECT_EQ(ERR_ACCESS_DENIED, d.request_status());
   }
 }
 
+#if defined(OS_POSIX)  // Bacause of symbolic links.
+
+TEST_F(URLRequestTest, SymlinksToFiles) {
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+  // Get an absolute path since temp_dir can contain a symbolic link.
+  base::FilePath absolute_temp_dir =
+      base::MakeAbsoluteFilePath(temp_dir.GetPath());
+
+  // Create a good directory (will be whitelisted) and a good file.
+  base::FilePath good_dir = absolute_temp_dir.AppendASCII("good");
+  ASSERT_TRUE(base::CreateDirectory(good_dir));
+  base::FilePath good_file;
+  ASSERT_TRUE(base::CreateTemporaryFileInDir(good_dir, &good_file));
+  std::string good_data("good");
+  base::WriteFile(good_file, good_data.data(), good_data.size());
+  // See the comment in AllowFileURLs() for why this is done.
+  base::FilePath real_good_dir = good_file.DirName();
+
+  // Create a bad directory (will not be whitelisted) and a bad file.
+  base::FilePath bad_dir = absolute_temp_dir.AppendASCII("bad");
+  ASSERT_TRUE(base::CreateDirectory(bad_dir));
+  base::FilePath bad_file;
+  ASSERT_TRUE(base::CreateTemporaryFileInDir(bad_dir, &bad_file));
+  std::string bad_data("bad");
+  base::WriteFile(bad_file, bad_data.data(), bad_data.size());
+
+  // This symlink will point to the good file. Access to the symlink will be
+  // allowed as both the symlink and the destination file are in the same
+  // good directory.
+  base::FilePath good_symlink = good_dir.AppendASCII("good_symlink");
+  ASSERT_TRUE(base::CreateSymbolicLink(good_file, good_symlink));
+  GURL good_file_url = FilePathToFileURL(good_symlink);
+  // This symlink will point to the bad file. Even though the symlink is in
+  // the good directory, access to the symlink will be rejected since it
+  // points to the bad file.
+  base::FilePath bad_symlink = good_dir.AppendASCII("bad_symlink");
+  ASSERT_TRUE(base::CreateSymbolicLink(bad_file, bad_symlink));
+  GURL bad_file_url = FilePathToFileURL(bad_symlink);
+
+  CookieBlockingNetworkDelegate network_delegate;
+  network_delegate.AddToWhitelist(real_good_dir);
+  {
+    TestDelegate d;
+    default_context_.set_network_delegate(&network_delegate);
+    std::unique_ptr<URLRequest> r(
+        default_context_.CreateRequest(good_file_url, DEFAULT_PRIORITY, &d));
+    r->Start();
+    base::RunLoop().Run();
+    // good_file_url should be allowed.
+    EXPECT_FALSE(d.request_failed());
+    EXPECT_EQ(good_data, d.data_received());
+  }
+
+  {
+    TestDelegate d;
+    default_context_.set_network_delegate(&network_delegate);
+    std::unique_ptr<URLRequest> r(
+        default_context_.CreateRequest(bad_file_url, DEFAULT_PRIORITY, &d));
+    r->Start();
+    base::RunLoop().Run();
+    // bad_file_url should be rejected.
+    EXPECT_TRUE(d.request_failed());
+    EXPECT_EQ("", d.data_received());
+    EXPECT_EQ(ERR_ACCESS_DENIED, d.request_status());
+  }
+}
+
+TEST_F(URLRequestTest, SymlinksToDirs) {
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+  // Get an absolute path since temp_dir can contain a symbolic link.
+  base::FilePath absolute_temp_dir =
+      base::MakeAbsoluteFilePath(temp_dir.GetPath());
+
+  // Create a good directory (will be whitelisted).
+  base::FilePath good_dir = absolute_temp_dir.AppendASCII("good");
+  ASSERT_TRUE(base::CreateDirectory(good_dir));
+
+  // Create a bad directory (will not be whitelisted).
+  base::FilePath bad_dir = absolute_temp_dir.AppendASCII("bad");
+  ASSERT_TRUE(base::CreateDirectory(bad_dir));
+
+  // This symlink will point to the good directory. Access to the symlink
+  // will be allowed as the symlink is in the good dir that'll be white
+  // listed.
+  base::FilePath good_symlink = good_dir.AppendASCII("good_symlink");
+  ASSERT_TRUE(base::CreateSymbolicLink(good_dir, good_symlink));
+  GURL good_file_url = FilePathToFileURL(good_symlink);
+  // This symlink will point to the bad directory. Even though the symlink is
+  // in the good directory, access to the symlink will be rejected since it
+  // points to the bad directory.
+  base::FilePath bad_symlink = good_dir.AppendASCII("bad_symlink");
+  ASSERT_TRUE(base::CreateSymbolicLink(bad_dir, bad_symlink));
+  GURL bad_file_url = FilePathToFileURL(bad_symlink);
+
+  CookieBlockingNetworkDelegate network_delegate;
+  network_delegate.AddToWhitelist(good_dir);
+  {
+    TestDelegate d;
+    default_context_.set_network_delegate(&network_delegate);
+    std::unique_ptr<URLRequest> r(
+        default_context_.CreateRequest(good_file_url, DEFAULT_PRIORITY, &d));
+    r->Start();
+    base::RunLoop().Run();
+    // good_file_url should be allowed.
+    EXPECT_FALSE(d.request_failed());
+    ASSERT_NE(d.data_received().find("good_symlink"), std::string::npos);
+  }
+
+  {
+    TestDelegate d;
+    default_context_.set_network_delegate(&network_delegate);
+    std::unique_ptr<URLRequest> r(
+        default_context_.CreateRequest(bad_file_url, DEFAULT_PRIORITY, &d));
+    r->Start();
+    base::RunLoop().Run();
+    // bad_file_url should be rejected.
+    EXPECT_TRUE(d.request_failed());
+    EXPECT_EQ("", d.data_received());
+    EXPECT_EQ(ERR_ACCESS_DENIED, d.request_status());
+  }
+}
+
+#endif  // defined(OS_POSIX)
 
 TEST_F(URLRequestTest, FileDirCancelTest) {
   // Put in mock resource provider.
@@ -1167,9 +1339,8 @@
   EXPECT_GT(info.size, 0);
   std::string sentinel_output = GetDirectoryListingEntry(
       base::string16(sentinel_name, sentinel_name + strlen(sentinel_name)),
-      std::string(sentinel_name),
-      false /* is_dir */,
-      info.size,
+      std::string(sentinel_name), false /* is_dir */, info.size,
+
       info.last_modified);
 
   ASSERT_LT(0, d.bytes_received());
diff --git a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/unregister-then-register.https.html b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/unregister-then-register.https.html
index d75904d..6c0a0aec 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/unregister-then-register.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/unregister-then-register.https.html
@@ -56,6 +56,45 @@
          'registration is in use.');
 
 async_test(function(t) {
+    var scope = 'resources/scope/complete-unregistration-followed-by-' +
+                'reloading-controllee-iframe';
+    var registration;
+    var frame;
+    var service_worker;
+    service_worker_unregister_and_register(t, worker_url, scope)
+      .then(function(r) {
+          registration = r;
+          return wait_for_state(t, r.installing, 'activated');
+        })
+      .then(function() {
+          return with_iframe(scope);
+        })
+      .then(function(f) {
+          frame = f;
+          return registration.unregister();
+        })
+      .then(function() {
+          return new Promise(function(resolve) {
+              frame.onload = resolve;
+              frame.contentWindow.location.reload();
+            });
+        })
+      .then(function() {
+          var c = frame.contentWindow.navigator.serviceWorker.controller;
+          assert_equals(c, null, 'a page after unregistration should not be ' +
+                                 'controlled by service worker');
+          return navigator.serviceWorker.getRegistration(scope);
+        })
+      .then(function(r) {
+          assert_equals(r, undefined, 'getRegistration should return ' +
+                                      'undefined after unregistration');
+          service_worker_unregister_and_done(t, scope);
+        })
+      .catch(unreached_rejection(t));
+}, 'Reloading the last controlled iframe after unregistration should ensure ' +
+   'the deletion of the registration');
+
+async_test(function(t) {
     var scope = 'resources/scope/re-register-does-not-affect-existing-controllee';
     var iframe;
     var registration;
@@ -116,8 +155,6 @@
           return with_iframe(scope);
         })
       .then(function(frame) {
-          // FIXME: When crbug.com/400602 is fixed, assert that controller
-          // equals the original worker.
           assert_not_equals(
               frame.contentWindow.navigator.serviceWorker.controller, null,
               'document should have a controller');
diff --git a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/update.https.html b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/update.https.html
index 213b72ac..6717d4d 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/update.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/update.https.html
@@ -5,12 +5,14 @@
 <script src="resources/testharness-helpers.js"></script>
 <script src="resources/test-helpers.sub.js"></script>
 <script>
+'use strict';
+
 promise_test(function(t) {
     var scope = 'resources/simple.txt';
     var worker_url = 'resources/update-worker.py';
     var expected_url = normalizeURL(worker_url);
     var registration;
-    var iframe;
+
     return service_worker_unregister_and_register(t, worker_url, scope)
       .then(function(r) {
           registration = r;
@@ -41,13 +43,11 @@
       .then(function() {
           assert_equals(registration.installing, null,
                         'installing should be null after installing.');
-          if (registration.waiting) {
-            assert_equals(registration.waiting.scriptURL, expected_url,
-                          'waiting should be set after installing.');
-            assert_equals(registration.active.scriptURL, expected_url,
-                          'active should still exist after installing.');
-            return wait_for_state(t, registration.waiting, 'activated');
-          }
+          assert_equals(registration.waiting.scriptURL, expected_url,
+                        'waiting should be set after installing.');
+          assert_equals(registration.active.scriptURL, expected_url,
+                        'active should still exist after installing.');
+          return wait_for_state(t, registration.waiting, 'activated');
         })
       .then(function() {
           assert_equals(registration.installing, null,
@@ -107,17 +107,15 @@
           return with_iframe(scope);
         })
       .then(function(frame) {
-          iframe = frame;
+          t.add_cleanup(function() {
+              frame.remove();
+            });
 
           return assert_promise_rejects(
               Promise.all([registration.unregister(), registration.update()]),
               new TypeError(),
               'Calling update() while the uninstalling flag is set ' +
               'should return a promise that rejects with a TypeError.');
-        })
-      .then(function() {
-        iframe.remove();
-        return t.done();
         });
   }, 'Update a registration.');
 </script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/update-served-from-cache.html b/third_party/WebKit/LayoutTests/http/tests/serviceworker/chromium.update-served-from-cache.html
similarity index 88%
rename from third_party/WebKit/LayoutTests/http/tests/serviceworker/update-served-from-cache.html
rename to third_party/WebKit/LayoutTests/http/tests/serviceworker/chromium.update-served-from-cache.html
index d6f77e7b..3efd693 100644
--- a/third_party/WebKit/LayoutTests/http/tests/serviceworker/update-served-from-cache.html
+++ b/third_party/WebKit/LayoutTests/http/tests/serviceworker/chromium.update-served-from-cache.html
@@ -1,4 +1,7 @@
 <!DOCTYPE html>
+<!-- This test is prefixed with `chromium.` because it asserts the behavior of
+  the HTTP cache. This behavior does not violate any standard but neither is it
+  required. -->
 <title>Service Worker: Registration update() served from cache</title>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/reject-install-worker.js b/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/reject-install-worker.js
deleted file mode 100644
index 41f07fd..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/reject-install-worker.js
+++ /dev/null
@@ -1,3 +0,0 @@
-self.oninstall = function(event) {
-  event.waitUntil(Promise.reject());
-};
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/unregister-controller-page.html b/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/unregister-controller-page.html
deleted file mode 100644
index 18a95ee..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/unregister-controller-page.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<!DOCTYPE html>
-<script>
-function fetch_url(url) {
-    return new Promise(function(resolve, reject) {
-        var request = new XMLHttpRequest();
-        request.addEventListener('load', function(event) {
-            if (request.status == 200)
-                resolve(request.response);
-            else
-                reject(Error(request.statusText));
-        });
-        request.open('GET', url);
-        request.send();
-    });
-}
-</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/update-worker.php b/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/update-worker.php
deleted file mode 100644
index 81bdd1bf..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/update-worker.php
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-if(!isset($_COOKIE['mode']))
-  $mode = 'init'; // Set mode to 'init' for initial fetch.
-else
-  $mode = $_COOKIE['mode']; // $_COOKIE['mode'] is either 'normal' or 'error'.
-
-// no-cache itself to ensure the user agent finds a new version for each update.
-header("Cache-Control: no-cache, must-revalidate");
-header("Pragma: no-cache");
-
-$extra_body = '';
-
-if ($mode == 'init') {
-  // Set a normal mimetype.
-  // Set cookie value to 'normal' so the next fetch will work in 'normal' mode.
-  header('Content-Type:application/javascript');
-  setcookie('mode', 'normal');
-} else if ($mode == 'normal') {
-  // Set a normal mimetype.
-  // Set cookie value to 'error' so the next fetch will work in 'error' mode.
-  header('Content-Type:application/javascript');
-  setcookie('mode', 'error');
-} else if ($mode == 'error') {
-  // Set a disallowed mimetype.
-  // Set cookie value to 'syntax-error' so the next fetch will work in 'syntax-error' mode.
-  header('Content-Type:text/html');
-  setcookie('mode', 'syntax-error');
-} else if ($mode == 'syntax-error') {
-  // Set cookie value to 'throw-install' so the next fetch will work in 'throw-install' mode.
-  header('Content-Type:application/javascript');
-  setcookie('mode', 'throw-install');
-  $extra_body = 'badsyntax(isbad;';
-} else if ($mode == 'throw-install') {
-  // Unset and delete cookie to clean up the test setting.
-  header('Content-Type:application/javascript');
-  unset($_COOKIE['mode']);
-  setcookie('mode', '', time() - 3600);
-  $extra_body = "addEventListener('install', function(e) { throw new Error('boom'); });";
-}
-// Return a different script for each access.
-echo '/* ', microtime(), ' */ ', $extra_body;
-?>
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/unregister-controller.html b/third_party/WebKit/LayoutTests/http/tests/serviceworker/unregister-controller.html
deleted file mode 100644
index fc05f77..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/serviceworker/unregister-controller.html
+++ /dev/null
@@ -1,101 +0,0 @@
-<!DOCTYPE html>
-<script src="../resources/testharness.js"></script>
-<script src="../resources/testharnessreport.js"></script>
-<script src="resources/test-helpers.js"></script>
-<script>
-var worker_url = 'resources/simple-intercept-worker.js';
-
-async_test(function(t) {
-    var scope =
-        'resources/unregister-controller-page.html?load-before-unregister';
-    var frame_window;
-    var controller;
-    var registration;
-
-    service_worker_unregister_and_register(t, worker_url, scope)
-      .then(function(r) {
-          registration = r;
-          return wait_for_state(t, r.installing, 'activated');
-        })
-      .then(function() {
-          return with_iframe(scope);
-        })
-      .then(function(frame) {
-          frame_window = frame.contentWindow;
-          controller = frame_window.navigator.serviceWorker.controller;
-          assert_true(controller instanceof frame_window.ServiceWorker,
-                      'document should load with a controller');
-          return registration.unregister();
-        })
-      .then(function() {
-          assert_equals(frame_window.navigator.serviceWorker.controller,
-                        controller,
-                        'unregistration should not modify controller');
-          return frame_window.fetch_url('simple.txt');
-        })
-      .then(function(response) {
-          assert_equals(response, 'intercepted by service worker',
-                        'controller should intercept requests');
-          t.done();
-        })
-      .catch(unreached_rejection(t));
-  }, 'Unregister does not affect existing controller');
-
-async_test(function(t) {
-    var scope =
-        'resources/unregister-controller-page.html?load-after-unregister';
-    var registration;
-
-    service_worker_unregister_and_register(t, worker_url, scope)
-      .then(function(r) {
-          registration = r;
-          return wait_for_state(t, r.installing, 'activated');
-        })
-      .then(function() {
-          return registration.unregister();
-        })
-      .then(function() {
-          return with_iframe(scope);
-        })
-      .then(function(frame) {
-          var frame_window = frame.contentWindow;
-          assert_equals(frame_window.navigator.serviceWorker.controller, null,
-                        'document should not have a controller');
-          return frame_window.fetch_url('simple.txt');
-        })
-      .then(function(response) {
-          assert_equals(response, 'a simple text file\n',
-                        'requests should not be intercepted');
-          t.done();
-        })
-      .catch(unreached_rejection(t));
-  }, 'Unregister prevents control of subsequent navigations');
-
-async_test(function(t) {
-    var scope =
-        'resources/scope/no-new-controllee-even-if-registration-is-still-used';
-    var registration;
-
-    service_worker_unregister_and_register(t, worker_url, scope)
-      .then(function(r) {
-          registration = r;
-          return wait_for_state(t, r.installing, 'activated');
-        })
-      .then(function() {
-          return with_iframe(scope);
-        })
-      .then(function(frame) {
-          return registration.unregister();
-        })
-      .then(function() {
-          return with_iframe(scope);
-        })
-      .then(function(frame) {
-          assert_equals(frame.contentWindow.navigator.serviceWorker.controller,
-                        null,
-                        'document should not have a controller');
-          t.done();
-        })
-      .catch(unreached_rejection(t));
-  }, 'Unregister prevents new controllee even if registration is still in use');
-</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/unregister-then-register-new-script.html b/third_party/WebKit/LayoutTests/http/tests/serviceworker/unregister-then-register-new-script.html
deleted file mode 100644
index cc5ee793..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/serviceworker/unregister-then-register-new-script.html
+++ /dev/null
@@ -1,159 +0,0 @@
-<!DOCTYPE html>
-<script src="../resources/testharness.js"></script>
-<script src="../resources/testharnessreport.js"></script>
-<script src="resources/test-helpers.js"></script>
-<script>
-var worker_url = 'resources/empty-worker.js';
-
-async_test(function(t) {
-    var scope = 'resources/scope/unregister-then-register-new-script-that-exists';
-    var new_worker_url = worker_url + '?new';
-    var iframe;
-    var registration;
-    var new_registration;
-
-    service_worker_unregister_and_register(t, worker_url, scope)
-      .then(function(r) {
-          registration = r;
-          return wait_for_state(t, r.installing, 'activated');
-        })
-      .then(function() {
-          return with_iframe(scope);
-        })
-      .then(function(frame) {
-          iframe = frame;
-          return registration.unregister();
-        })
-      .then(function() {
-          return navigator.serviceWorker.register(new_worker_url,
-                                                  { scope: scope });
-        })
-      .then(function(r) {
-          new_registration = r;
-          assert_equals(registration.installing.scriptURL,
-                        normalizeURL(new_worker_url),
-                        'before activated registration.installing');
-          assert_equals(registration.waiting, null,
-                        'before activated registration.waiting');
-          assert_equals(registration.active.scriptURL, normalizeURL(worker_url),
-                        'before activated registration.active');
-          assert_equals(new_registration.installing.scriptURL,
-                        normalizeURL(new_worker_url),
-                        'before activated new_registration.installing');
-          assert_equals(new_registration.waiting, null,
-                        'before activated new_registration.waiting');
-          assert_equals(new_registration.active.scriptURL,
-                        normalizeURL(worker_url),
-                        'before activated new_registration.active');
-          iframe.remove();
-          return wait_for_state(t, registration.installing, 'activated');
-        })
-      .then(function() {
-          assert_equals(new_registration.installing, null,
-                        'after activated new_registration.installing');
-          assert_equals(new_registration.waiting, null,
-                        'after activated new_registration.waiting');
-          assert_equals(new_registration.active.scriptURL,
-                        normalizeURL(new_worker_url),
-                        'after activated new_registration.active');
-          return with_iframe(scope);
-        })
-      .then(function(frame) {
-          assert_equals(
-              frame.contentWindow.navigator.serviceWorker.controller.scriptURL,
-              normalizeURL(new_worker_url),
-              'the new worker should control a new document');
-          frame.remove();
-          return registration.unregister();
-        })
-      .then(function() {
-          t.done();
-        })
-      .catch(unreached_rejection(t));
-}, 'Registering a new script URL while an unregistered registration is in use');
-
-async_test(function(t) {
-    var scope = 'resources/scope/unregister-then-register-new-script-that-404s';
-    var iframe;
-    var registration;
-
-    service_worker_unregister_and_register(t, worker_url, scope)
-      .then(function(r) {
-          registration = r;
-          return wait_for_state(t, r.installing, 'activated');
-        })
-      .then(function() {
-          return with_iframe(scope);
-        })
-      .then(function(frame) {
-          iframe = frame;
-          return registration.unregister();
-        })
-      .then(function() {
-          var promise = navigator.serviceWorker.register('this-will-404',
-                                                         { scope: scope });
-          iframe.remove();
-          return promise;
-        })
-      .then(
-        function() {
-          assert_unreached('register should reject the promise');
-        },
-        function() {
-          return with_iframe(scope);
-        })
-      .then(function(frame) {
-          assert_equals(frame.contentWindow.navigator.serviceWorker.controller,
-                        null,
-                        'document should not load with a controller');
-          frame.remove();
-          t.done();
-        })
-      .catch(unreached_rejection(t));
-}, 'Registering a new script URL that 404s does not resurrect an ' +
-       'unregistered registration');
-
-async_test(function(t) {
-    var scope = 'resources/scope/unregister-then-register-reject-install-worker';
-    var iframe;
-    var registration;
-
-    service_worker_unregister_and_register(t, worker_url, scope)
-      .then(function(r) {
-          registration = r;
-          return wait_for_state(t, r.installing, 'activated');
-        })
-      .then(function() {
-          return with_iframe(scope);
-        })
-      .then(function(frame) {
-          iframe = frame;
-          return registration.unregister();
-        })
-      .then(function() {
-          var promise = navigator.serviceWorker.register(
-              'resources/reject-install-worker.js', { scope: scope });
-          iframe.remove();
-          return promise;
-        })
-      .then(function(r) {
-          registration = r;
-          return wait_for_state(t, r.installing, 'redundant');
-        })
-      .then(function() {
-          return with_iframe(scope);
-        })
-      .then(function(frame) {
-          assert_equals(frame.contentWindow.navigator.serviceWorker.controller,
-                        null,
-                        'document should not load with a controller');
-          frame.remove();
-          return registration.unregister();
-        })
-      .then(function() {
-          t.done();
-        })
-      .catch(unreached_rejection(t));
-  }, 'Registering a new script URL that fails to install does not resurrect ' +
-       'an unregistered registration');
-</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/unregister-then-register.html b/third_party/WebKit/LayoutTests/http/tests/serviceworker/unregister-then-register.html
deleted file mode 100644
index 75db25c..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/serviceworker/unregister-then-register.html
+++ /dev/null
@@ -1,167 +0,0 @@
-<!DOCTYPE html>
-<script src="../resources/testharness.js"></script>
-<script src="../resources/testharnessreport.js"></script>
-<script src="resources/test-helpers.js"></script>
-<script>
-var worker_url = 'resources/empty-worker.js';
-
-async_test(function(t) {
-    var scope = 'resources/scope/re-register-resolves-to-new-value';
-    var iframe;
-    var registration;
-
-    service_worker_unregister_and_register(t, worker_url, scope)
-      .then(function(r) {
-          registration = r;
-          return wait_for_state(t, r.installing, 'activated');
-        })
-      .then(function() {
-          return registration.unregister();
-        })
-      .then(function() {
-          return navigator.serviceWorker.register(worker_url, { scope: scope });
-        })
-      .then(function(new_registration) {
-          assert_not_equals(registration, new_registration,
-                            'register should resolve to a new value');
-          service_worker_unregister_and_done(t, scope);
-        })
-      .catch(unreached_rejection(t));
-  }, 'Unregister then register resolves to a new value');
-
-async_test(function(t) {
-    var scope = 'resources/scope/re-register-while-old-registration-in-use';
-    var registration;
-
-    service_worker_unregister_and_register(t, worker_url, scope)
-      .then(function(r) {
-          registration = r;
-          return wait_for_state(t, r.installing, 'activated');
-        })
-      .then(function() {
-          return with_iframe(scope);
-        })
-      .then(function(frame) {
-          return registration.unregister();
-        })
-      .then(function() {
-          return navigator.serviceWorker.register(worker_url, { scope: scope });
-        })
-      .then(function(new_registration) {
-          assert_equals(registration, new_registration,
-                        'register should resolve to the same registration');
-          service_worker_unregister_and_done(t, scope);
-        })
-      .catch(unreached_rejection(t));
-  }, 'Unregister then register resolves to the original value if the ' +
-         'registration is in use.');
-
-async_test(function(t) {
-    var scope = 'resources/scope/complete-unregistration-followed-by-' +
-                'reloading-controllee-iframe';
-    var registration;
-    var frame;
-    var service_worker;
-    service_worker_unregister_and_register(t, worker_url, scope)
-      .then(function(r) {
-          registration = r;
-          return wait_for_state(t, r.installing, 'activated');
-        })
-      .then(function() {
-          return with_iframe(scope);
-        })
-      .then(function(f) {
-          frame = f;
-          return registration.unregister();
-        })
-      .then(function() {
-          return new Promise(function(resolve) {
-              frame.onload = resolve;
-              frame.contentWindow.location.reload();
-            });
-        })
-      .then(function() {
-          var c = frame.contentWindow.navigator.serviceWorker.controller;
-          assert_equals(c, null, 'a page after unregistration should not be ' +
-                                 'controlled by service worker');
-          return navigator.serviceWorker.getRegistration(scope);
-        })
-      .then(function(r) {
-          assert_equals(r, undefined, 'getRegistration should return ' +
-                                      'undefined after unregistration');
-          service_worker_unregister_and_done(t, scope);
-        })
-      .catch(unreached_rejection(t));
-}, 'Reloading the last controlled iframe after unregistration should ensure ' +
-   'the deletion of the registration');
-
-async_test(function(t) {
-    var scope = 'resources/scope/re-register-does-not-affect-existing-controllee';
-    var iframe;
-    var registration;
-    var controller;
-
-    service_worker_unregister_and_register(t, worker_url, scope)
-      .then(function(r) {
-          registration = r;
-          return wait_for_state(t, r.installing, 'activated');
-        })
-      .then(function() {
-          return with_iframe(scope);
-        })
-      .then(function(frame) {
-          iframe = frame;
-          controller = iframe.contentWindow.navigator.serviceWorker.controller;
-          return registration.unregister();
-        })
-      .then(function() {
-          return navigator.serviceWorker.register(worker_url, { scope: scope });
-        })
-      .then(function(registration) {
-          assert_equals(registration.installing, null,
-                        'installing version is null');
-          assert_equals(registration.waiting, null, 'waiting version is null');
-          assert_equals(
-              iframe.contentWindow.navigator.serviceWorker.controller,
-              controller,
-              'the worker from the first registration is the controller');
-          service_worker_unregister_and_done(t, scope);
-        })
-      .catch(unreached_rejection(t));
-  }, 'Unregister then register does not affect existing controllee');
-
-async_test(function(t) {
-    var scope = 'resources/scope/resurrection';
-    var iframe;
-    var registration;
-
-    service_worker_unregister_and_register(t, worker_url, scope)
-      .then(function(r) {
-          registration = r;
-          return wait_for_state(t, r.installing, 'activated');
-        })
-      .then(function() {
-          return with_iframe(scope);
-        })
-      .then(function(frame) {
-          iframe = frame;
-          return registration.unregister();
-        })
-      .then(function() {
-          return navigator.serviceWorker.register(worker_url, { scope: scope });
-        })
-      .then(function() {
-          iframe.remove();
-          return with_iframe(scope);
-        })
-      .then(function(frame) {
-          // FIXME: When crbug.com/400602 is fixed, assert that controller
-          // equals the original worker.
-          assert_not_equals(
-              frame.contentWindow.navigator.serviceWorker.controller, null,
-              'document should have a controller');
-          service_worker_unregister_and_done(t, scope);
-        })
-      .catch(unreached_rejection(t));
-  }, 'Unregister then register resurrects the registration');
-</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/unregister.html b/third_party/WebKit/LayoutTests/http/tests/serviceworker/unregister.html
deleted file mode 100644
index 54be479..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/serviceworker/unregister.html
+++ /dev/null
@@ -1,40 +0,0 @@
-<!DOCTYPE html>
-<script src="../resources/testharness.js"></script>
-<script src="../resources/testharnessreport.js"></script>
-<script src="resources/test-helpers.js"></script>
-<script>
-async_test(function(t) {
-    var scope = 'resources/scope/unregister-twice';
-    var registration;
-    navigator.serviceWorker.register('resources/empty-worker.js',
-                                     {scope: scope})
-      .then(function(r) {
-          registration = r;
-          return registration.unregister();
-        })
-      .then(function() {
-          return registration.unregister();
-        })
-      .then(function(value) {
-          assert_equals(value, false,
-                        'unregistering twice should resolve with false');
-          t.done();
-        })
-      .catch(unreached_rejection(t));
-  }, 'Unregister twice');
-
-async_test(function(t) {
-    var scope = 'resources/scope/successful-unregister/';
-    navigator.serviceWorker.register('resources/empty-worker.js',
-                                     {scope: scope})
-      .then(function(registration) {
-          return registration.unregister();
-        })
-      .then(function(value) {
-          assert_equals(value, true,
-                        'unregistration should resolve with true');
-          t.done();
-        })
-      .catch(unreached_rejection(t));
-  }, 'Register then unregister');
-</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/update.html b/third_party/WebKit/LayoutTests/http/tests/serviceworker/update.html
deleted file mode 100644
index 82db192..0000000
--- a/third_party/WebKit/LayoutTests/http/tests/serviceworker/update.html
+++ /dev/null
@@ -1,119 +0,0 @@
-<!DOCTYPE html>
-<title>Service Worker: Registration update()</title>
-<script src="../resources/testharness.js"></script>
-<script src="../resources/testharnessreport.js"></script>
-<script src="resources/test-helpers.js"></script>
-<script>
-promise_test(function(t) {
-    var scope = 'resources/scope/update';
-    var worker_url = 'resources/update-worker.php';
-    var expected_url = normalizeURL(worker_url);
-    var registration;
-    return service_worker_unregister_and_register(t, worker_url, scope)
-      .then(function(r) {
-          registration = r;
-          return wait_for_state(t, registration.installing, 'activated');
-        })
-      .then(function() {
-          assert_equals(registration.installing, null,
-                        'installing should be null in the initial state.');
-          assert_equals(registration.waiting, null,
-                        'waiting should be null in the initial state.');
-          assert_equals(registration.active.scriptURL, expected_url,
-                        'active should exist in the initial state.');
-
-          // A new worker (generated by update-worker.php) should be found.
-          // The returned promise should resolve when a new worker script is
-          // fetched and starts installing.
-          return Promise.all([registration.update(),
-                              wait_for_update(t, registration)]);
-        })
-      .then(function() {
-          assert_equals(registration.installing.scriptURL, expected_url,
-                        'new installing should be set after update resolves.');
-          assert_equals(registration.waiting, null,
-                        'waiting should still be null after update resolves.');
-          assert_equals(registration.active.scriptURL, expected_url,
-                        'active should still exist after update found.');
-          return wait_for_state(t, registration.installing, 'installed');
-        })
-      .then(function() {
-          assert_equals(registration.installing, null,
-                        'installing should be null after installing.');
-          assert_equals(registration.waiting.scriptURL, expected_url,
-                        'waiting should be set after installing.');
-          assert_equals(registration.active.scriptURL, expected_url,
-                        'active should still exist after installing.');
-          return wait_for_state(t, registration.waiting, 'activated');
-        })
-      .then(function() {
-          assert_equals(registration.installing, null,
-                        'installing should be null after activated.');
-          assert_equals(registration.waiting, null,
-                        'waiting should be null after activated.');
-          assert_equals(registration.active.scriptURL, expected_url,
-                        'new worker should be promoted to active.');
-        })
-      .then(function() {
-          // A new worker(generated by update-worker.php) should be found.
-          // The returned promise should reject as update-worker.php sets the
-          // mimetype to a disallowed value for this attempt.
-          return registration.update();
-        })
-      .then(
-        function() { assert_unreached("update() should reject."); },
-        function(e) {
-          assert_throws('SecurityError', function() { throw e; },
-                        'Using a disallowed mimetype should make update() ' +
-                        'promise reject with a SecurityError.');
-          assert_equals(registration.active.scriptURL, expected_url,
-                        'active should still exist after update failure.');
-
-          // A new worker(generated by update-worker.py) should be found.
-          // The returned promise should reject as update-worker.py returns
-          // a worker script with a syntax error.
-          return registration.update();
-        })
-      .then(
-        function() { assert_unreached("update() should reject."); },
-        function(e) {
-          assert_throws({name: 'TypeError'}, function () { throw e; },
-                        'A script syntax error should make update() ' +
-                        'promise reject with a TypeError.');
-          assert_equals(registration.active.scriptURL, expected_url,
-                        'active should still exist after update failure.');
-
-          // A new worker(generated by update-worker.py) should be found.
-          // The returned promise should not reject, even though
-          // update-worker.py returns a worker script that throws in the
-          // install event handler.
-          return Promise.all([registration.update(),
-                              wait_for_update(t, registration)]);
-        })
-      .then(function() {
-          assert_equals(registration.installing.scriptURL, expected_url,
-                        'installing should be set after update resolves (throw-install).');
-          assert_equals(registration.waiting, null,
-                        'waiting should still be null after update resolves (throw-install).');
-          assert_equals(registration.active.scriptURL, expected_url,
-                        'active should still exist after update found (throw-install).');
-
-          // We need to hold a client alive so that unregister() below doesn't
-          // remove the registration before update() has had a chance to look
-          // at the pending uninstall flag.
-          return with_iframe(scope);
-        })
-      .then(function(frame) {
-          return Promise.all([registration.unregister(),
-                              registration.update()]);
-        })
-      .then(
-        function() { assert_unreached("update() should reject."); },
-        function(e) {
-          assert_throws({name: 'TypeError'}, function () { throw e; },
-                        'Calling update() while the uninstalling flag is ' +
-                        'set should return a promise that rejects with an ' +
-                        'TypeError.');
-        });
-  }, 'Update a registration.');
-</script>
diff --git a/third_party/WebKit/LayoutTests/typedcssom/computedstyle/computedStylePropertyMap-expected.txt b/third_party/WebKit/LayoutTests/typedcssom/computedstyle/computedStylePropertyMap-expected.txt
deleted file mode 100644
index 728c57e..0000000
--- a/third_party/WebKit/LayoutTests/typedcssom/computedstyle/computedStylePropertyMap-expected.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-This is a testharness.js-based test.
-PASS Test that passing null to getComputedStyleMap does not crash 
-PASS Computed StyleMap.getProperties returns the same list of property names as ComputedStyle 
-PASS Unsupported but serializable property returns a base CSSStyleValue. 
-PASS Unsupported and unserializable property returns null. 
-FAIL get() throws for an invalid property. Test bug: need to pass exception to assert_throws()
-PASS has() return false for an unsupported property. 
-FAIL has() throws for an invalid property. Test bug: need to pass exception to assert_throws()
-PASS has() returns true for an unsupported but serializable shorthand property. 
-PASS has() return false for unsupported and unserializable shorthand properties. 
-PASS has() returns true for a supported property. 
-PASS get() returns correct values for an element with display: none. 
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/typedcssom/computedstyle/computedStylePropertyMap.html b/third_party/WebKit/LayoutTests/typedcssom/computedstyle/computedStylePropertyMap.html
index 7755f5bf..6e3fbab 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/computedstyle/computedStylePropertyMap.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/computedstyle/computedStylePropertyMap.html
@@ -37,7 +37,7 @@
 }, 'Unsupported and unserializable property returns null.');
 
 test(function() {
-  assert_throws(null, function() { computedStyleMap.get('bananas'); });
+  assert_throws(new TypeError(), function() { computedStyleMap.get('bananas'); });
 }, 'get() throws for an invalid property.');
 
 test(function() {
@@ -45,7 +45,7 @@
 }, 'has() return false for an unsupported property.');
 
 test(function() {
-  assert_throws(null, function() { computedStyleMap.has('bananas'); });
+  assert_throws(new TypeError(), function() { computedStyleMap.has('bananas'); });
 }, 'has() throws for an invalid property.');
 
 test(function() {
diff --git a/third_party/WebKit/LayoutTests/typedcssom/cssCalcLength-expected.txt b/third_party/WebKit/LayoutTests/typedcssom/cssCalcLength-expected.txt
deleted file mode 100644
index 995a6f8..0000000
--- a/third_party/WebKit/LayoutTests/typedcssom/cssCalcLength-expected.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-This is a testharness.js-based test.
-FAIL Passing invalid arguments to CSSCalcLength throws an exception. Test bug: need to pass exception to assert_throws()
-PASS Adding two CSSCalcLengths produces a new CSSCalcLength with the correct value. 
-PASS Subtracting two CSSCalcLengths produces a new CSSCalcLength with the correct values. 
-PASS Multiplying a CSSCalcLength produces a new CSSCalcLength with the correct values. 
-PASS Dividing a CSSCalcLength produces a new CSSCalcLength with the correct values. 
-PASS Dividing by zero throws a RangeError 
-PASS cssText produces the correct string 
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/typedcssom/cssMatrixComponent-expected.txt b/third_party/WebKit/LayoutTests/typedcssom/cssMatrixComponent-expected.txt
deleted file mode 100644
index d6d0490f..0000000
--- a/third_party/WebKit/LayoutTests/typedcssom/cssMatrixComponent-expected.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-This is a testharness.js-based test.
-PASS Test that the (a, ... , f) and (m11, ... , m44) attributes for CSSMatrixComponent are correct. 
-PASS Test that the is2D values for CSSMatrixComponent are correct. 
-PASS Test that the toString for CSSMatrixComponent is correct. 
-FAIL Test that invalid number of arguments for CSSMatrixComponent throws an exception. Test bug: need to pass exception to assert_throws()
-PASS Test that asMatrix has all the same properties as the original CSSMatrixComponent. 
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/typedcssom/cssMatrixComponent.html b/third_party/WebKit/LayoutTests/typedcssom/cssMatrixComponent.html
index 0c7a5183..0669296 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/cssMatrixComponent.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/cssMatrixComponent.html
@@ -57,11 +57,11 @@
 }, "Test that the toString for CSSMatrixComponent is correct.");
 
 test(function() {
-  assert_throws(null, function() { new CSSMatrixComponent() });
-  assert_throws(null, function() { new CSSMatrixComponent(0) });
-  assert_throws(null, function() { new CSSMatrixComponent("string") });
-  assert_throws(null, function() { new CSSMatrixComponent(None) });
-  assert_throws(null, function() { new CSSMatrixComponent(undefined) });
+  assert_throws(new TypeError(), function() { new CSSMatrixComponent() });
+  assert_throws(new TypeError(), function() { new CSSMatrixComponent(0) });
+  assert_throws(new TypeError(), function() { new CSSMatrixComponent("string") });
+  assert_throws(new TypeError(), function() { new CSSMatrixComponent(null) });
+  assert_throws(new TypeError(), function() { new CSSMatrixComponent(undefined) });
 }, "Test that invalid number of arguments for CSSMatrixComponent throws an exception.");
 
 test(function() {
diff --git a/third_party/WebKit/LayoutTests/typedcssom/cssScale-expected.txt b/third_party/WebKit/LayoutTests/typedcssom/cssScale-expected.txt
deleted file mode 100644
index 6d863576..0000000
--- a/third_party/WebKit/LayoutTests/typedcssom/cssScale-expected.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-This is a testharness.js-based test.
-PASS Test that the (x, y, z) values for CSSScale are correct. 
-PASS Test that the is2D values for CSSScale is correct. 
-PASS Test that the toString for CSSScale is correct. 
-FAIL Test that invalid number of arguments for CSSScale throws an exception. Test bug: need to pass exception to assert_throws()
-FAIL Test that invalid input throws an exception. Test bug: need to pass exception to assert_throws()
-PASS Test that asMatrix is constructed correctly for CSSScale. 
-PASS Test that x, y, z are mutable attributes. 
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/typedcssom/cssScale.html b/third_party/WebKit/LayoutTests/typedcssom/cssScale.html
index d67fd22..01075f0 100644
--- a/third_party/WebKit/LayoutTests/typedcssom/cssScale.html
+++ b/third_party/WebKit/LayoutTests/typedcssom/cssScale.html
@@ -42,35 +42,35 @@
 }, "Test that the toString for CSSScale is correct.");
 
 test(function() {
-  assert_throws(null, () => { new CSSScale(); });
-  assert_throws(null, () => { new CSSScale(1); });
+  assert_throws(new TypeError(), () => { new CSSScale(); });
+  assert_throws(new TypeError(), () => { new CSSScale(1); });
 }, "Test that invalid number of arguments for CSSScale throws an exception.");
 
 test(function() {
-  assert_throws(null, () => { new CSSScale(NaN, 0); });
-  assert_throws(null, () => { new CSSScale(0, NaN); });
-  assert_throws(null, () => { new CSSScale(NaN, NaN); });
-  assert_throws(null, () => { new CSSScale(Infinity, 0); });
-  assert_throws(null, () => { new CSSScale(-Infinity, 0); });
-  assert_throws(null, () => { new CSSScale("hello", 0); });
-  assert_throws(null, () => { new CSSScale(0, "world"); });
-  assert_throws(null, () => { new CSSScale(undefined, 0); });
-  assert_throws(null, () => { new CSSScale({}, {}); });
+  assert_throws(new TypeError(), () => { new CSSScale(NaN, 0); });
+  assert_throws(new TypeError(), () => { new CSSScale(0, NaN); });
+  assert_throws(new TypeError(), () => { new CSSScale(NaN, NaN); });
+  assert_throws(new TypeError(), () => { new CSSScale(Infinity, 0); });
+  assert_throws(new TypeError(), () => { new CSSScale(-Infinity, 0); });
+  assert_throws(new TypeError(), () => { new CSSScale("hello", 0); });
+  assert_throws(new TypeError(), () => { new CSSScale(0, "world"); });
+  assert_throws(new TypeError(), () => { new CSSScale(undefined, 0); });
+  assert_throws(new TypeError(), () => { new CSSScale({}, {}); });
 
-  assert_throws(null, () => { new CSSScale("hello", 0, 0); });
-  assert_throws(null, () => { new CSSScale(0, NaN, 0); });
-  assert_throws(null, () => { new CSSScale(0, Infinity, 0); });
-  assert_throws(null, () => { new CSSScale(0, 0, NaN); });
-  assert_throws(null, () => { new CSSScale(0, 0, Infinity); });
-  assert_throws(null, () => { new CSSScale(0, 0, -Infinity); });
-  assert_throws(null, () => { new CSSScale(0, 0, undefined); });
-  assert_throws(null, () => { new CSSScale(undefined, undefined, 0); });
-  assert_throws(null, () => { new CSSScale(NaN, undefined, 0); });
-  assert_throws(null, () => { new CSSScale(NaN, 0, NaN); });
-  assert_throws(null, () => { new CSSScale(0, "hello", "world"); });
-  assert_throws(null, () => { new CSSScale(0, {}, {}); });
-  assert_throws(null, () => { new CSSScale({}, {}, {}); });
-  assert_throws(null, () => { new CSSScale(NaN, NaN, NaN); });
+  assert_throws(new TypeError(), () => { new CSSScale("hello", 0, 0); });
+  assert_throws(new TypeError(), () => { new CSSScale(0, NaN, 0); });
+  assert_throws(new TypeError(), () => { new CSSScale(0, Infinity, 0); });
+  assert_throws(new TypeError(), () => { new CSSScale(0, 0, NaN); });
+  assert_throws(new TypeError(), () => { new CSSScale(0, 0, Infinity); });
+  assert_throws(new TypeError(), () => { new CSSScale(0, 0, -Infinity); });
+  assert_throws(new TypeError(), () => { new CSSScale(0, 0, undefined); });
+  assert_throws(new TypeError(), () => { new CSSScale(undefined, undefined, 0); });
+  assert_throws(new TypeError(), () => { new CSSScale(NaN, undefined, 0); });
+  assert_throws(new TypeError(), () => { new CSSScale(NaN, 0, NaN); });
+  assert_throws(new TypeError(), () => { new CSSScale(0, "hello", "world"); });
+  assert_throws(new TypeError(), () => { new CSSScale(0, {}, {}); });
+  assert_throws(new TypeError(), () => { new CSSScale({}, {}, {}); });
+  assert_throws(new TypeError(), () => { new CSSScale(NaN, NaN, NaN); });
 }, "Test that invalid input throws an exception.");
 
 test(function() {
diff --git a/third_party/WebKit/LayoutTests/typedcssom/cssSimpleLength-expected.txt b/third_party/WebKit/LayoutTests/typedcssom/cssSimpleLength-expected.txt
deleted file mode 100644
index f29ca88..0000000
--- a/third_party/WebKit/LayoutTests/typedcssom/cssSimpleLength-expected.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-This is a testharness.js-based test.
-PASS CSSSimpleLengths are immutable 
-PASS Each unit type is returned according to the spec 
-PASS Adding CSSSimpleLengths with the same unit produces a new CSSSimpleLength with the correct value. 
-PASS Adding CSSSimpleLengths with different units produces a calc length with the correct values. 
-PASS Subtracting CSSSimpleLengths with the same unit produces a new CSSSimpleLength with the correct value. 
-PASS Subtracting CSSSimpleLengths with different units produces a calc length with the correct values. 
-PASS Multiplying a CSSSimpleLength produces a new CSSSimpleLength with the correct value. 
-PASS Dividing a CSSSimpleLength produces a new CSSSimpleLength with the correct value. 
-PASS Dividing by zero throws a RangeError 
-PASS cssText is generated correctly for each unit type. 
-FAIL Invalid input throws an exception. Test bug: need to pass exception to assert_throws()
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/PerformanceTests/Editing/move-down-with-hidden-elements.html b/third_party/WebKit/PerformanceTests/DOM/move-down-with-hidden-elements.html
similarity index 100%
rename from third_party/WebKit/PerformanceTests/Editing/move-down-with-hidden-elements.html
rename to third_party/WebKit/PerformanceTests/DOM/move-down-with-hidden-elements.html
diff --git a/third_party/WebKit/Source/build/scripts/make_computed_style_base.py b/third_party/WebKit/Source/build/scripts/make_computed_style_base.py
index e126ba0..50dc4e57 100755
--- a/third_party/WebKit/Source/build/scripts/make_computed_style_base.py
+++ b/third_party/WebKit/Source/build/scripts/make_computed_style_base.py
@@ -120,7 +120,7 @@
     """
 
     def __init__(self, field_role, name_for_methods, property_name, type_name, wrapper_pointer_name,
-                 field_template, field_group, size, default_value, has_custom_compare_and_copy,
+                 field_template, field_group, size, default_value, custom_copy, custom_compare,
                  getter_method_name, setter_method_name, initial_method_name, **kwargs):
         """Creates a new field."""
         self.name = class_member_name(name_for_methods)
@@ -133,7 +133,8 @@
         self.group_member_name = class_member_name(join_name(field_group, 'data')) if field_group else None
         self.size = size
         self.default_value = default_value
-        self.has_custom_compare_and_copy = has_custom_compare_and_copy
+        self.custom_copy = custom_copy
+        self.custom_compare = custom_compare
 
         # Field role: one of these must be true
         self.is_property = field_role == 'property'
@@ -298,7 +299,8 @@
         field_group=property_['field_group'],
         size=size,
         default_value=default_value,
-        has_custom_compare_and_copy=property_['has_custom_compare_and_copy'],
+        custom_copy=property_['custom_copy'],
+        custom_compare=property_['custom_compare'],
         getter_method_name=property_['getter'],
         setter_method_name=property_['setter'],
         initial_method_name=property_['initial'],
@@ -321,7 +323,8 @@
         field_group=property_['field_group'],
         size=1,
         default_value='true',
-        has_custom_compare_and_copy=False,
+        custom_copy=False,
+        custom_compare=False,
         getter_method_name=method_name(name_for_methods),
         setter_method_name=method_name(join_name('set', name_for_methods)),
         initial_method_name=method_name(join_name('initial', name_for_methods)),
@@ -410,8 +413,9 @@
         css_properties = [value for value in self._properties.values() if not value['longhands']]
 
         for property_ in css_properties:
-            # All CSS properties that are generated do not have custom comparison and copy logic.
-            property_['has_custom_compare_and_copy'] = False
+            # All CSS properties from CSSProperties.json5 do not have custom comparison and copy logic.
+            property_['custom_copy'] = False
+            property_['custom_compare'] = False
 
         # Read extra fields using the parameter specification from the CSS properties file.
         extra_fields = json5_generator.Json5File.load_from_files(
diff --git a/third_party/WebKit/Source/build/scripts/templates/ComputedStyleBase.h.tmpl b/third_party/WebKit/Source/build/scripts/templates/ComputedStyleBase.h.tmpl
index 123c699..8ff8877 100644
--- a/third_party/WebKit/Source/build/scripts/templates/ComputedStyleBase.h.tmpl
+++ b/third_party/WebKit/Source/build/scripts/templates/ComputedStyleBase.h.tmpl
@@ -121,7 +121,6 @@
     return (
         {{fieldwise_compare(computed_style, computed_style.all_fields
             |selectattr("is_property")
-            |rejectattr("has_custom_compare_and_copy")
             |rejectattr("is_inherited")
             |list
           )|indent(8)}}
diff --git a/third_party/WebKit/Source/build/scripts/templates/fields/field.tmpl b/third_party/WebKit/Source/build/scripts/templates/fields/field.tmpl
index 1538c97..74fcdeb 100644
--- a/third_party/WebKit/Source/build/scripts/templates/fields/field.tmpl
+++ b/third_party/WebKit/Source/build/scripts/templates/fields/field.tmpl
@@ -78,28 +78,28 @@
 {% for subgroup in group.subgroups %}
   {# If every field in this subgroup is to be compared, we can compare the
      group pointer instead. #}
-  {% if subgroup.all_fields|reject("in", fields_to_compare)|list|length == 0 -%}
+  {% if subgroup.all_fields|rejectattr("custom_compare")|reject("in", fields_to_compare)|list|length == 0 -%}
     {{subgroup.member_name}} == o.{{subgroup.member_name}} &&
   {# Otherwise, we would have to recursively generate comparison operations
      on fields in the subgroup. #}
-  {% elif subgroup.fields|select("in", fields_to_compare)|list|length > 0 -%}
+  {% elif subgroup.fields|rejectattr("custom_compare")|select("in", fields_to_compare)|list|length > 0 -%}
     {{fieldwise_compare(subgroup, fields_to_compare)}}
   {% endif %}
 {% endfor %}
-{% for field in group.fields|select("in", fields_to_compare) -%}
+{% for field in group.fields|rejectattr("custom_compare")|select("in", fields_to_compare) -%}
   {{compare(field.wrapper_pointer_name, getter_expression(field), "o")}} &&
 {% endfor %}
 {% endmacro %}
 
 {% macro fieldwise_copy(group, fields_to_copy) %}
 {% for subgroup in group.subgroups %}
-  {% if subgroup.all_fields|reject("in", fields_to_copy)|list|length == 0 -%}
+  {% if subgroup.all_fields|rejectattr("custom_copy")|reject("in", fields_to_copy)|list|length == 0 -%}
     {{subgroup.member_name}} = other.{{subgroup.member_name}};
-  {% elif subgroup.fields|select("in", fields_to_copy)|list|length > 0 -%}
+  {% elif subgroup.fields|rejectattr("custom_copy")|select("in", fields_to_copy)|list|length > 0 -%}
     {{fieldwise_copy(subgroup, fields_to_copy)}}
   {% endif %}
 {% endfor %}
-{% for field in group.fields|select("in", fields_to_copy) -%}
+{% for field in group.fields|rejectattr("custom_copy")|select("in", fields_to_copy) -%}
   {{setter_expression(field)}} = other.{{getter_expression(field)}};
 {% endfor %}
 {% endmacro %}
diff --git a/third_party/WebKit/Source/core/animation/CSSInterpolationEnvironment.h b/third_party/WebKit/Source/core/animation/CSSInterpolationEnvironment.h
new file mode 100644
index 0000000..27e3caf49
--- /dev/null
+++ b/third_party/WebKit/Source/core/animation/CSSInterpolationEnvironment.h
@@ -0,0 +1,54 @@
+// 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 CSSInterpolationEnvironment_h
+#define CSSInterpolationEnvironment_h
+
+#include "core/animation/InterpolationEnvironment.h"
+#include "core/css/resolver/StyleResolverState.h"
+
+namespace blink {
+
+class ComputedStyle;
+
+class CSSInterpolationEnvironment : public InterpolationEnvironment {
+ public:
+  explicit CSSInterpolationEnvironment(const InterpolationTypesMap& map,
+                                       StyleResolverState& state)
+      : InterpolationEnvironment(map), state_(&state), style_(state.Style()) {}
+
+  explicit CSSInterpolationEnvironment(const InterpolationTypesMap& map,
+                                       const ComputedStyle& style)
+      : InterpolationEnvironment(map), style_(&style) {}
+
+  bool IsCSS() const final { return true; }
+
+  StyleResolverState& GetState() {
+    DCHECK(state_);
+    return *state_;
+  }
+  const StyleResolverState& GetState() const {
+    DCHECK(state_);
+    return *state_;
+  }
+
+  const ComputedStyle& Style() const {
+    DCHECK(style_);
+    return *style_;
+  }
+
+ private:
+  StyleResolverState* state_ = nullptr;
+  const ComputedStyle* style_ = nullptr;
+};
+
+DEFINE_TYPE_CASTS(CSSInterpolationEnvironment,
+                  InterpolationEnvironment,
+                  value,
+                  value->IsCSS(),
+                  value.IsCSS());
+
+}  // namespace blink
+
+#endif  // CSSInterpolationEnvironment_h
diff --git a/third_party/WebKit/Source/core/animation/CSSInterpolationType.cpp b/third_party/WebKit/Source/core/animation/CSSInterpolationType.cpp
index 70b70b3c..ff48065 100644
--- a/third_party/WebKit/Source/core/animation/CSSInterpolationType.cpp
+++ b/third_party/WebKit/Source/core/animation/CSSInterpolationType.cpp
@@ -6,6 +6,7 @@
 
 #include <memory>
 #include "core/StylePropertyShorthand.h"
+#include "core/animation/CSSInterpolationEnvironment.h"
 #include "core/animation/StringKeyframe.h"
 #include "core/css/CSSCustomPropertyDeclaration.h"
 #include "core/css/CSSValue.h"
@@ -128,7 +129,8 @@
     const InterpolationValue& underlying,
     ConversionCheckers& conversion_checkers) const {
   const CSSValue* value = ToCSSPropertySpecificKeyframe(keyframe).Value();
-  const StyleResolverState& state = environment.GetState();
+  const StyleResolverState& state =
+      ToCSSInterpolationEnvironment(environment).GetState();
 
   if (!value)
     return MaybeConvertNeutral(underlying, conversion_checkers);
@@ -243,7 +245,8 @@
 
 InterpolationValue CSSInterpolationType::MaybeConvertUnderlyingValue(
     const InterpolationEnvironment& environment) const {
-  const ComputedStyle& style = environment.Style();
+  const ComputedStyle& style =
+      ToCSSInterpolationEnvironment(environment).Style();
   if (!GetProperty().IsCSSCustomProperty()) {
     return MaybeConvertStandardPropertyUnderlyingValue(style);
   }
@@ -271,7 +274,8 @@
     const InterpolableValue& interpolable_value,
     const NonInterpolableValue* non_interpolable_value,
     InterpolationEnvironment& environment) const {
-  StyleResolverState& state = environment.GetState();
+  StyleResolverState& state =
+      ToCSSInterpolationEnvironment(environment).GetState();
 
   if (GetProperty().IsCSSCustomProperty()) {
     ApplyCustomPropertyValue(interpolable_value, non_interpolable_value, state);
diff --git a/third_party/WebKit/Source/core/animation/CSSInterpolationType.h b/third_party/WebKit/Source/core/animation/CSSInterpolationType.h
index 7e72a40..c2eb7df 100644
--- a/third_party/WebKit/Source/core/animation/CSSInterpolationType.h
+++ b/third_party/WebKit/Source/core/animation/CSSInterpolationType.h
@@ -5,13 +5,15 @@
 #ifndef CSSInterpolationType_h
 #define CSSInterpolationType_h
 
-#include "core/animation/InterpolationEnvironment.h"
+#include "core/animation/CSSInterpolationEnvironment.h"
 #include "core/animation/InterpolationType.h"
 
 namespace blink {
 
 class CSSCustomPropertyDeclaration;
+class ComputedStyle;
 class PropertyRegistration;
+class StyleResolverState;
 
 class CSSInterpolationType : public InterpolationType {
  public:
@@ -21,7 +23,8 @@
    public:
     bool IsValid(const InterpolationEnvironment& environment,
                  const InterpolationValue& underlying) const final {
-      return IsValid(environment.GetState(), underlying);
+      return IsValid(ToCSSInterpolationEnvironment(environment).GetState(),
+                     underlying);
     }
 
    protected:
diff --git a/third_party/WebKit/Source/core/animation/InterpolationEnvironment.h b/third_party/WebKit/Source/core/animation/InterpolationEnvironment.h
index 431b1d4..0af68d3 100644
--- a/third_party/WebKit/Source/core/animation/InterpolationEnvironment.h
+++ b/third_party/WebKit/Source/core/animation/InterpolationEnvironment.h
@@ -6,77 +6,28 @@
 #define InterpolationEnvironment_h
 
 #include "core/animation/InterpolationTypesMap.h"
-#include "core/css/resolver/StyleResolverState.h"
-#include "platform/heap/Handle.h"
 #include "platform/wtf/Allocator.h"
 
 namespace blink {
 
-class ComputedStyle;
-class SVGPropertyBase;
-class SVGElement;
-
 class InterpolationEnvironment {
   STACK_ALLOCATED();
-
  public:
-  explicit InterpolationEnvironment(const InterpolationTypesMap& map,
-                                    StyleResolverState& state)
-      : interpolation_types_map_(map), state_(&state), style_(state.Style()) {}
-
-  explicit InterpolationEnvironment(const InterpolationTypesMap& map,
-                                    const ComputedStyle& style)
-      : interpolation_types_map_(map), style_(&style) {}
-
-  explicit InterpolationEnvironment(const InterpolationTypesMap& map,
-                                    SVGElement& svg_element,
-                                    const SVGPropertyBase& svg_base_value)
-      : interpolation_types_map_(map),
-        svg_element_(&svg_element),
-        svg_base_value_(&svg_base_value) {}
+  virtual bool IsCSS() const { return false; }
+  virtual bool IsSVG() const { return false; }
 
   const InterpolationTypesMap& GetInterpolationTypesMap() const {
     return interpolation_types_map_;
   }
 
-  StyleResolverState& GetState() {
-    DCHECK(state_);
-    return *state_;
-  }
-  const StyleResolverState& GetState() const {
-    DCHECK(state_);
-    return *state_;
-  }
+ protected:
+  virtual ~InterpolationEnvironment() {}
 
-  const ComputedStyle& Style() const {
-    DCHECK(style_);
-    return *style_;
-  }
-
-  SVGElement& SvgElement() {
-    DCHECK(svg_element_);
-    return *svg_element_;
-  }
-  const SVGElement& SvgElement() const {
-    DCHECK(svg_element_);
-    return *svg_element_;
-  }
-
-  const SVGPropertyBase& SvgBaseValue() const {
-    DCHECK(svg_base_value_);
-    return *svg_base_value_;
-  }
+  explicit InterpolationEnvironment(const InterpolationTypesMap& map)
+      : interpolation_types_map_(map) {}
 
  private:
   const InterpolationTypesMap& interpolation_types_map_;
-
-  // CSSInterpolationType environment
-  StyleResolverState* state_ = nullptr;
-  const ComputedStyle* style_ = nullptr;
-
-  // SVGInterpolationType environment
-  Member<SVGElement> svg_element_ = nullptr;
-  Member<const SVGPropertyBase> svg_base_value_ = nullptr;
 };
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/animation/InvalidatableInterpolation.cpp b/third_party/WebKit/Source/core/animation/InvalidatableInterpolation.cpp
index 9a3569e5..29a96ef 100644
--- a/third_party/WebKit/Source/core/animation/InvalidatableInterpolation.cpp
+++ b/third_party/WebKit/Source/core/animation/InvalidatableInterpolation.cpp
@@ -4,10 +4,10 @@
 
 #include "core/animation/InvalidatableInterpolation.h"
 
-#include "core/animation/InterpolationEnvironment.h"
+#include <memory>
+#include "core/animation/CSSInterpolationEnvironment.h"
 #include "core/animation/StringKeyframe.h"
 #include "core/css/resolver/StyleResolverState.h"
-#include <memory>
 
 namespace blink {
 
@@ -198,7 +198,9 @@
     InterpolationEnvironment& environment) const {
   if (!property_.IsCSSProperty() && !property_.IsPresentationAttribute())
     return;
-  if (!environment.GetState().ParentStyle())
+  StyleResolverState& state =
+      ToCSSInterpolationEnvironment(environment).GetState();
+  if (!state.ParentStyle())
     return;
   const CSSValue* start_value =
       ToCSSPropertySpecificKeyframe(*start_keyframe_).Value();
@@ -206,7 +208,7 @@
       ToCSSPropertySpecificKeyframe(*end_keyframe_).Value();
   if ((start_value && start_value->IsInheritedValue()) ||
       (end_value && end_value->IsInheritedValue())) {
-    environment.GetState().ParentStyle()->SetHasExplicitlyInheritedProperties();
+    state.ParentStyle()->SetHasExplicitlyInheritedProperties();
   }
 }
 
diff --git a/third_party/WebKit/Source/core/animation/SVGInterpolationEnvironment.h b/third_party/WebKit/Source/core/animation/SVGInterpolationEnvironment.h
new file mode 100644
index 0000000..e373a7d8
--- /dev/null
+++ b/third_party/WebKit/Source/core/animation/SVGInterpolationEnvironment.h
@@ -0,0 +1,54 @@
+// 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 SVGInterpolationEnvironment_h
+#define SVGInterpolationEnvironment_h
+
+#include "core/animation/InterpolationEnvironment.h"
+#include "platform/wtf/Assertions.h"
+
+namespace blink {
+
+class SVGPropertyBase;
+class SVGElement;
+
+class SVGInterpolationEnvironment : public InterpolationEnvironment {
+ public:
+  explicit SVGInterpolationEnvironment(const InterpolationTypesMap& map,
+                                       SVGElement& svg_element,
+                                       const SVGPropertyBase& svg_base_value)
+      : InterpolationEnvironment(map),
+        svg_element_(&svg_element),
+        svg_base_value_(&svg_base_value) {}
+
+  bool IsSVG() const final { return true; }
+
+  SVGElement& SvgElement() {
+    DCHECK(svg_element_);
+    return *svg_element_;
+  }
+  const SVGElement& SvgElement() const {
+    DCHECK(svg_element_);
+    return *svg_element_;
+  }
+
+  const SVGPropertyBase& SvgBaseValue() const {
+    DCHECK(svg_base_value_);
+    return *svg_base_value_;
+  }
+
+ private:
+  Member<SVGElement> svg_element_ = nullptr;
+  Member<const SVGPropertyBase> svg_base_value_ = nullptr;
+};
+
+DEFINE_TYPE_CASTS(SVGInterpolationEnvironment,
+                  InterpolationEnvironment,
+                  value,
+                  value->IsSVG(),
+                  value.IsSVG());
+
+}  // namespace blink
+
+#endif  // SVGInterpolationEnvironment_h
diff --git a/third_party/WebKit/Source/core/animation/SVGInterpolationType.cpp b/third_party/WebKit/Source/core/animation/SVGInterpolationType.cpp
index ad5415f..2fe4100 100644
--- a/third_party/WebKit/Source/core/animation/SVGInterpolationType.cpp
+++ b/third_party/WebKit/Source/core/animation/SVGInterpolationType.cpp
@@ -4,7 +4,7 @@
 
 #include "core/animation/SVGInterpolationType.h"
 
-#include "core/animation/InterpolationEnvironment.h"
+#include "core/animation/SVGInterpolationEnvironment.h"
 #include "core/animation/StringKeyframe.h"
 #include "core/svg/SVGElement.h"
 #include "core/svg/properties/SVGProperty.h"
@@ -19,22 +19,28 @@
   if (keyframe.IsNeutral())
     return MaybeConvertNeutral(underlying, conversion_checkers);
 
-  SVGPropertyBase* svg_value = environment.SvgBaseValue().CloneForAnimation(
-      ToSVGPropertySpecificKeyframe(keyframe).Value());
+  SVGPropertyBase* svg_value =
+      ToSVGInterpolationEnvironment(environment)
+          .SvgBaseValue()
+          .CloneForAnimation(ToSVGPropertySpecificKeyframe(keyframe).Value());
   return MaybeConvertSVGValue(*svg_value);
 }
 
 InterpolationValue SVGInterpolationType::MaybeConvertUnderlyingValue(
     const InterpolationEnvironment& environment) const {
-  return MaybeConvertSVGValue(environment.SvgBaseValue());
+  return MaybeConvertSVGValue(
+      ToSVGInterpolationEnvironment(environment).SvgBaseValue());
 }
 
 void SVGInterpolationType::Apply(
     const InterpolableValue& interpolable_value,
     const NonInterpolableValue* non_interpolable_value,
     InterpolationEnvironment& environment) const {
-  environment.SvgElement().SetWebAnimatedAttribute(
-      Attribute(), AppliedSVGValue(interpolable_value, non_interpolable_value));
+  ToSVGInterpolationEnvironment(environment)
+      .SvgElement()
+      .SetWebAnimatedAttribute(
+          Attribute(),
+          AppliedSVGValue(interpolable_value, non_interpolable_value));
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/animation/SVGLengthInterpolationType.cpp b/third_party/WebKit/Source/core/animation/SVGLengthInterpolationType.cpp
index f1224eb..cffb3a6 100644
--- a/third_party/WebKit/Source/core/animation/SVGLengthInterpolationType.cpp
+++ b/third_party/WebKit/Source/core/animation/SVGLengthInterpolationType.cpp
@@ -4,13 +4,13 @@
 
 #include "core/animation/SVGLengthInterpolationType.h"
 
-#include "core/animation/InterpolationEnvironment.h"
+#include <memory>
+#include "core/animation/SVGInterpolationEnvironment.h"
 #include "core/animation/StringKeyframe.h"
 #include "core/css/CSSHelper.h"
 #include "core/svg/SVGElement.h"
 #include "core/svg/SVGLength.h"
 #include "core/svg/SVGLengthContext.h"
-#include <memory>
 
 namespace blink {
 
@@ -115,7 +115,7 @@
     const InterpolableValue& interpolable_value,
     const NonInterpolableValue* non_interpolable_value,
     InterpolationEnvironment& environment) const {
-  SVGElement& element = environment.SvgElement();
+  SVGElement& element = ToSVGInterpolationEnvironment(environment).SvgElement();
   SVGLengthContext length_context(&element);
   element.SetWebAnimatedAttribute(
       Attribute(),
diff --git a/third_party/WebKit/Source/core/animation/SVGLengthListInterpolationType.cpp b/third_party/WebKit/Source/core/animation/SVGLengthListInterpolationType.cpp
index eff8136..65a3228 100644
--- a/third_party/WebKit/Source/core/animation/SVGLengthListInterpolationType.cpp
+++ b/third_party/WebKit/Source/core/animation/SVGLengthListInterpolationType.cpp
@@ -4,11 +4,11 @@
 
 #include "core/animation/SVGLengthListInterpolationType.h"
 
-#include "core/animation/InterpolationEnvironment.h"
+#include <memory>
+#include "core/animation/SVGInterpolationEnvironment.h"
 #include "core/animation/SVGLengthInterpolationType.h"
 #include "core/animation/UnderlyingLengthChecker.h"
 #include "core/svg/SVGLengthList.h"
-#include <memory>
 
 namespace blink {
 
@@ -85,7 +85,7 @@
     const InterpolableValue& interpolable_value,
     const NonInterpolableValue* non_interpolable_value,
     InterpolationEnvironment& environment) const {
-  SVGElement& element = environment.SvgElement();
+  SVGElement& element = ToSVGInterpolationEnvironment(environment).SvgElement();
   SVGLengthContext length_context(&element);
 
   SVGLengthList* result = SVGLengthList::Create(unit_mode_);
diff --git a/third_party/WebKit/Source/core/animation/SVGTransformListInterpolationType.cpp b/third_party/WebKit/Source/core/animation/SVGTransformListInterpolationType.cpp
index 086ba10..c3a09d9 100644
--- a/third_party/WebKit/Source/core/animation/SVGTransformListInterpolationType.cpp
+++ b/third_party/WebKit/Source/core/animation/SVGTransformListInterpolationType.cpp
@@ -6,8 +6,8 @@
 
 #include <memory>
 #include "core/animation/InterpolableValue.h"
-#include "core/animation/InterpolationEnvironment.h"
 #include "core/animation/NonInterpolableValue.h"
+#include "core/animation/SVGInterpolationEnvironment.h"
 #include "core/animation/StringKeyframe.h"
 #include "core/svg/SVGTransform.h"
 #include "core/svg/SVGTransformList.h"
@@ -252,8 +252,10 @@
   }
 
   if (!keyframe.IsNeutral()) {
-    SVGPropertyBase* svg_value = environment.SvgBaseValue().CloneForAnimation(
-        ToSVGPropertySpecificKeyframe(keyframe).Value());
+    SVGPropertyBase* svg_value =
+        ToSVGInterpolationEnvironment(environment)
+            .SvgBaseValue()
+            .CloneForAnimation(ToSVGPropertySpecificKeyframe(keyframe).Value());
     InterpolationValue value = MaybeConvertSVGValue(*svg_value);
     if (!value)
       return nullptr;
diff --git a/third_party/WebKit/Source/core/animation/TransitionInterpolation.cpp b/third_party/WebKit/Source/core/animation/TransitionInterpolation.cpp
index 1b5f803d..b2f0a6d 100644
--- a/third_party/WebKit/Source/core/animation/TransitionInterpolation.cpp
+++ b/third_party/WebKit/Source/core/animation/TransitionInterpolation.cpp
@@ -47,7 +47,7 @@
 
 void TransitionInterpolation::Apply(StyleResolverState& state) const {
   CSSInterpolationTypesMap map(state.GetDocument().GetPropertyRegistry());
-  InterpolationEnvironment environment(map, state);
+  CSSInterpolationEnvironment environment(map, state);
   type_.Apply(CurrentInterpolableValue(), CurrentNonInterpolableValue(),
               environment);
 }
diff --git a/third_party/WebKit/Source/core/animation/css/CSSAnimations.cpp b/third_party/WebKit/Source/core/animation/css/CSSAnimations.cpp
index 6a536ab43..f3a6055 100644
--- a/third_party/WebKit/Source/core/animation/css/CSSAnimations.cpp
+++ b/third_party/WebKit/Source/core/animation/css/CSSAnimations.cpp
@@ -683,8 +683,8 @@
   }
 
   CSSInterpolationTypesMap map(registry);
-  InterpolationEnvironment old_environment(map, state.old_style);
-  InterpolationEnvironment new_environment(map, state.style);
+  CSSInterpolationEnvironment old_environment(map, state.old_style);
+  CSSInterpolationEnvironment new_environment(map, state.style);
   InterpolationValue start = nullptr;
   InterpolationValue end = nullptr;
   const InterpolationType* transition_type = nullptr;
diff --git a/third_party/WebKit/Source/core/css/ComputedStyleExtraFields.json5 b/third_party/WebKit/Source/core/css/ComputedStyleExtraFields.json5
index 0c8a386..cd899447 100644
--- a/third_party/WebKit/Source/core/css/ComputedStyleExtraFields.json5
+++ b/third_party/WebKit/Source/core/css/ComputedStyleExtraFields.json5
@@ -3,9 +3,15 @@
 // generate, but are not CSS properties.
 
   parameters: {
-    // If the field has_custom_compare_and_copy, then it does not appear in
-    // ComputedStyle::operator== and ComputedStyle::CopyNonInheritedFromCached.
-    has_custom_compare_and_copy: {
+    // If true, we do not include this field in ComputedStyleBase::InheritFrom
+    // and ComputedStyleBase::CopyNonInheritedFromCached.
+    custom_copy: {
+      default: false,
+    },
+
+    // If true, we do not include this field in ComputedStyleBase::InheritedEqual
+    // and ComputedStyleBase::NonInheritedEqual.
+    custom_compare: {
       default: false,
     },
 
@@ -20,7 +26,8 @@
       name: "IsLink",
       field_template: "monotonic_flag",
       default_value: "false",
-      has_custom_compare_and_copy: true,
+      custom_copy: true,
+      custom_compare: true,
     },
     {
       name: "BorderLeftColorIsCurrentColor",
@@ -67,59 +74,66 @@
       default_value: "not-inside-link",
       keywords: ["not-inside-link", "inside-unvisited-link", "inside-visited-link"],
       inherited: true,
-      has_custom_compare_and_copy: true,
     },
     // Style can not be shared.
     {
       name: "Unique",
       field_template: "monotonic_flag",
       default_value: "false",
-      has_custom_compare_and_copy: true,
+      custom_copy: true,
+      custom_compare: true,
     },
     // Whether this style is affected by these pseudo-classes.
     {
       name: "AffectedByFocus",
       field_template: "monotonic_flag",
       default_value: "false",
-      has_custom_compare_and_copy: true,
+      custom_copy: true,
+      custom_compare: true,
     },
     {
       name: "AffectedByFocusWithin",
       field_template: "monotonic_flag",
       default_value: "false",
-      has_custom_compare_and_copy: true,
+      custom_copy: true,
+      custom_compare: true,
     },
     {
       name: "AffectedByHover",
       field_template: "monotonic_flag",
       default_value: "false",
-      has_custom_compare_and_copy: true,
+      custom_copy: true,
+      custom_compare: true,
     },
     {
       name: "AffectedByActive",
       field_template: "monotonic_flag",
       default_value: "false",
-      has_custom_compare_and_copy: true,
+      custom_copy: true,
+      custom_compare: true,
     },
     {
       name: "AffectedByDrag",
       field_template: "monotonic_flag",
       default_value: "false",
-      has_custom_compare_and_copy: true,
+      custom_copy: true,
+      custom_compare: true,
     },
     // A non-inherited property references a variable or @apply is used
     {
       name: "HasVariableReferenceFromNonInheritedProperty",
       field_template: "monotonic_flag",
       default_value: "false",
-      has_custom_compare_and_copy: true,
+      custom_copy: true,
+      custom_compare: true,
     },
     // Explicitly inherits a non-inherited property
     {
       name: "HasExplicitlyInheritedProperties",
       field_template: "monotonic_flag",
       default_value: "false",
-      has_custom_compare_and_copy: true,
+      custom_copy: true,
+      custom_compare: true,
     },
     // These are set if we used viewport or rem units when resolving a length.
     // TODO(shend): HasViewportUnits should be a monotonic_flag.
@@ -128,13 +142,13 @@
       field_template: "primitive",
       default_value: "false",
       type_name: "bool",
-      has_custom_compare_and_copy: true,
+      custom_compare: true,
     },
     {
       name: "HasRemUnits",
       field_template: "monotonic_flag",
       default_value: "false",
-      has_custom_compare_and_copy: true,
+      custom_compare: true,
     },
     // These properties only have generated storage, and their methods are handwritten in ComputedStyle.
     // TODO(shend): Remove these fields and delete the 'storage_only' template.
@@ -144,7 +158,8 @@
       field_size: 1,
       default_value: "false",
       type_name: "bool",
-      has_custom_compare_and_copy: true,
+      custom_copy: true,
+      custom_compare: true,
     },
     {
       name: "StyleType",
@@ -152,7 +167,8 @@
       field_size: 6,
       default_value: "0",
       type_name: "PseudoId",
-      has_custom_compare_and_copy: true,
+      custom_copy: true,
+      custom_compare: true,
     },
     {
       name: "PseudoBits",
@@ -160,7 +176,8 @@
       field_size: 8,
       default_value: "kPseudoIdNone",
       type_name: "PseudoId",
-      has_custom_compare_and_copy: true,
+      custom_copy: true,
+      custom_compare: true,
     },
     // True if 'underline solid' is the only text decoration on this element.
     {
@@ -170,7 +187,6 @@
       default_value: "false",
       type_name: "bool",
       inherited: true,
-      has_custom_compare_and_copy: true,
     },
     // TODO(shend): vertical align is actually a CSS property, but since we don't support union fields
     // which can be either a keyword or Length, this is specified in this file for now. Remove this
diff --git a/third_party/WebKit/Source/core/css/resolver/StyleResolver.cpp b/third_party/WebKit/Source/core/css/resolver/StyleResolver.cpp
index 40edfb05..27323df 100644
--- a/third_party/WebKit/Source/core/css/resolver/StyleResolver.cpp
+++ b/third_party/WebKit/Source/core/css/resolver/StyleResolver.cpp
@@ -35,9 +35,9 @@
 #include "core/MediaTypeNames.h"
 #include "core/StylePropertyShorthand.h"
 #include "core/animation/AnimationTimeline.h"
+#include "core/animation/CSSInterpolationEnvironment.h"
 #include "core/animation/CSSInterpolationTypesMap.h"
 #include "core/animation/ElementAnimations.h"
-#include "core/animation/InterpolationEnvironment.h"
 #include "core/animation/InvalidatableInterpolation.h"
 #include "core/animation/KeyframeEffect.h"
 #include "core/animation/LegacyStyleInterpolation.h"
@@ -1234,7 +1234,7 @@
     const Interpolation& interpolation = *entry.value.front();
     if (interpolation.IsInvalidatableInterpolation()) {
       CSSInterpolationTypesMap map(state.GetDocument().GetPropertyRegistry());
-      InterpolationEnvironment environment(map, state);
+      CSSInterpolationEnvironment environment(map, state);
       InvalidatableInterpolation::ApplyStack(entry.value, environment);
     } else if (interpolation.IsTransitionInterpolation()) {
       ToTransitionInterpolation(interpolation).Apply(state);
diff --git a/third_party/WebKit/Source/core/dom/AXObject.cpp b/third_party/WebKit/Source/core/dom/AXObject.cpp
new file mode 100644
index 0000000..54224a18
--- /dev/null
+++ b/third_party/WebKit/Source/core/dom/AXObject.cpp
@@ -0,0 +1,99 @@
+// 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/dom/AXObject.h"
+
+#include "core/HTMLElementTypeHelpers.h"
+#include "core/dom/Element.h"
+#include "core/dom/Node.h"
+#include "platform/wtf/HashSet.h"
+#include "platform/wtf/text/StringHash.h"
+#include "platform/wtf/text/WTFString.h"
+
+namespace blink {
+
+namespace {
+
+typedef HashSet<String, CaseFoldingHash> ARIAWidgetSet;
+
+const char* g_aria_widgets[] = {
+    // From http://www.w3.org/TR/wai-aria/roles#widget_roles
+    "alert", "alertdialog", "button", "checkbox", "dialog", "gridcell", "link",
+    "log", "marquee", "menuitem", "menuitemcheckbox", "menuitemradio", "option",
+    "progressbar", "radio", "scrollbar", "slider", "spinbutton", "status",
+    "tab", "tabpanel", "textbox", "timer", "tooltip", "treeitem",
+    // Composite user interface widgets.
+    // This list is also from the w3.org site referenced above.
+    "combobox", "grid", "listbox", "menu", "menubar", "radiogroup", "tablist",
+    "tree", "treegrid"};
+
+static ARIAWidgetSet* CreateARIARoleWidgetSet() {
+  ARIAWidgetSet* widget_set = new HashSet<String, CaseFoldingHash>();
+  for (size_t i = 0; i < WTF_ARRAY_LENGTH(g_aria_widgets); ++i)
+    widget_set->insert(String(g_aria_widgets[i]));
+  return widget_set;
+}
+
+bool IncludesARIAWidgetRole(const String& role) {
+  static const HashSet<String, CaseFoldingHash>* role_set =
+      CreateARIARoleWidgetSet();
+
+  Vector<String> role_vector;
+  role.Split(' ', role_vector);
+  for (const auto& child : role_vector) {
+    if (role_set->Contains(child))
+      return true;
+  }
+  return false;
+}
+
+const char* g_aria_interactive_widget_attributes[] = {
+    // These attributes implicitly indicate the given widget is interactive.
+    // From http://www.w3.org/TR/wai-aria/states_and_properties#attrs_widgets
+    // clang-format off
+    "aria-activedescendant",
+    "aria-checked",
+    "aria-controls",
+    "aria-disabled",  // If it's disabled, it can be made interactive.
+    "aria-expanded",
+    "aria-haspopup",
+    "aria-multiselectable",
+    "aria-pressed",
+    "aria-required",
+    "aria-selected"
+    // clang-format on
+};
+
+bool HasInteractiveARIAAttribute(const Element& element) {
+  for (size_t i = 0; i < WTF_ARRAY_LENGTH(g_aria_interactive_widget_attributes);
+       ++i) {
+    const char* attribute = g_aria_interactive_widget_attributes[i];
+    if (element.hasAttribute(attribute)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+}  // namespace
+
+bool AXObject::IsInsideFocusableElementOrARIAWidget(const Node& node) {
+  const Node* cur_node = &node;
+  do {
+    if (cur_node->IsElementNode()) {
+      const Element* element = ToElement(cur_node);
+      if (element->IsFocusable())
+        return true;
+      String role = element->getAttribute("role");
+      if (!role.IsEmpty() && IncludesARIAWidgetRole(role))
+        return true;
+      if (HasInteractiveARIAAttribute(*element))
+        return true;
+    }
+    cur_node = cur_node->parentNode();
+  } while (cur_node && !isHTMLBodyElement(node));
+  return false;
+}
+
+}  // namespace blink
diff --git a/third_party/WebKit/Source/core/dom/AXObject.h b/third_party/WebKit/Source/core/dom/AXObject.h
index 4acb8a5..ae51855 100644
--- a/third_party/WebKit/Source/core/dom/AXObject.h
+++ b/third_party/WebKit/Source/core/dom/AXObject.h
@@ -9,6 +9,8 @@
 
 namespace blink {
 
+class Node;
+
 enum AccessibilityRole {
   kUnknownRole = 0,  // Not mapped in platform APIs, generally indicates a bug
   kAbbrRole,         // No mapping to ARIA role.
@@ -284,7 +286,11 @@
 
 // TODO(sashab): Add pure virtual methods to this class to remove dependencies
 // on AXObjectImpl from outside of modules/.
-class CORE_EXPORT AXObject {};
+class CORE_EXPORT AXObject {
+ public:
+  // Static helper functions.
+  static bool IsInsideFocusableElementOrARIAWidget(const Node&);
+};
 
 }  // namespace blink
 
diff --git a/third_party/WebKit/Source/core/dom/BUILD.gn b/third_party/WebKit/Source/core/dom/BUILD.gn
index ce8315d..4a34c98 100644
--- a/third_party/WebKit/Source/core/dom/BUILD.gn
+++ b/third_party/WebKit/Source/core/dom/BUILD.gn
@@ -8,6 +8,7 @@
   split_count = 5
 
   sources = [
+    "AXObject.cpp",
     "AXObject.h",
     "AXObjectCache.cpp",
     "AXObjectCache.h",
diff --git a/third_party/WebKit/Source/core/dom/DOMTokenList.cpp b/third_party/WebKit/Source/core/dom/DOMTokenList.cpp
index c11a6ff..b68b08c 100644
--- a/third_party/WebKit/Source/core/dom/DOMTokenList.cpp
+++ b/third_party/WebKit/Source/core/dom/DOMTokenList.cpp
@@ -96,9 +96,7 @@
   if (!ValidateTokens(tokens, exception_state))
     return;
 
-  // TODO(tkent): Add a member function for AutoReset + setValue.
-  AutoReset<bool> updating(&is_in_update_step_, true);
-  setValue(AddTokens(tokens));
+  AddTokens(tokens);
 }
 
 void DOMTokenList::remove(const AtomicString& token,
@@ -120,9 +118,7 @@
   // See https://github.com/whatwg/dom/issues/462
   if (value().IsNull())
     return;
-  // TODO(tkent): Add a member function for AutoReset + setValue.
-  AutoReset<bool> updating(&is_in_update_step_, true);
-  setValue(RemoveTokens(tokens));
+  RemoveTokens(tokens);
 }
 
 bool DOMTokenList::toggle(const AtomicString& token,
@@ -160,55 +156,38 @@
 void DOMTokenList::AddInternal(const AtomicString& token) {
   if (ContainsInternal(token))
     return;
-  // TODO(tkent): Add a member function for AutoReset + setValue.
-  AutoReset<bool> updating(&is_in_update_step_, true);
-  setValue(AddToken(token));
+  Vector<String> tokens;
+  tokens.push_back(token.GetString());
+  AddTokens(tokens);
 }
 
 void DOMTokenList::RemoveInternal(const AtomicString& token) {
-  // Check using contains first since it uses AtomicString comparisons instead
-  // of character by character testing.
+  // Check using contains first to skip unnecessary reserialization.
   if (!ContainsInternal(token))
     return;
-  // TODO(tkent): Add a member function for AutoReset + setValue.
-  AutoReset<bool> updating(&is_in_update_step_, true);
-  setValue(RemoveToken(token));
-}
-
-AtomicString DOMTokenList::AddToken(const AtomicString& token) {
   Vector<String> tokens;
   tokens.push_back(token.GetString());
-  return AddTokens(tokens);
+  RemoveTokens(tokens);
 }
 
 // https://dom.spec.whatwg.org/#dom-domtokenlist-add
-// This returns an AtomicString because it is always passed as argument to
-// setValue() and setValue() takes an AtomicString in argument.
-AtomicString DOMTokenList::AddTokens(const Vector<String>& tokens) {
+void DOMTokenList::AddTokens(const Vector<String>& tokens) {
   SpaceSplitString& token_set = MutableSet();
   // 2. For each token in tokens, append token to context object’s token set.
   for (const auto& token : tokens)
     token_set.Add(AtomicString(token));
   // 3. Run the update steps.
-  return SerializeSet(token_set);
-}
-
-AtomicString DOMTokenList::RemoveToken(const AtomicString& token) {
-  Vector<String> tokens;
-  tokens.push_back(token.GetString());
-  return RemoveTokens(tokens);
+  UpdateWithTokenSet(token_set);
 }
 
 // https://dom.spec.whatwg.org/#dom-domtokenlist-remove
-// This returns an AtomicString because it is always passed as argument to
-// setValue() and setValue() takes an AtomicString in argument.
-AtomicString DOMTokenList::RemoveTokens(const Vector<String>& tokens) {
+void DOMTokenList::RemoveTokens(const Vector<String>& tokens) {
   SpaceSplitString& token_set = MutableSet();
   // 2. For each token in tokens, remove token from context object’s token set.
   for (const auto& token : tokens)
     token_set.Remove(AtomicString(token));
   // 3. Run the update steps.
-  return SerializeSet(token_set);
+  UpdateWithTokenSet(token_set);
 }
 
 // https://dom.spec.whatwg.org/#concept-ordered-set-serializer
@@ -230,6 +209,12 @@
   return builder.ToAtomicString();
 }
 
+// https://dom.spec.whatwg.org/#concept-dtl-update
+void DOMTokenList::UpdateWithTokenSet(const SpaceSplitString& token_set) {
+  AutoReset<bool> updating(&is_in_update_step_, true);
+  setValue(SerializeSet(token_set));
+}
+
 void DOMTokenList::setValue(const AtomicString& value) {
   if (observer_)
     observer_->ValueWasSet(value);
diff --git a/third_party/WebKit/Source/core/dom/DOMTokenList.h b/third_party/WebKit/Source/core/dom/DOMTokenList.h
index 3f351f5..d5e5377 100644
--- a/third_party/WebKit/Source/core/dom/DOMTokenList.h
+++ b/third_party/WebKit/Source/core/dom/DOMTokenList.h
@@ -96,14 +96,14 @@
   bool ValidateToken(const String&, ExceptionState&) const;
   bool ValidateTokens(const Vector<String>&, ExceptionState&) const;
   virtual bool ValidateTokenValue(const AtomicString&, ExceptionState&) const;
-  AtomicString AddToken(const AtomicString&);
-  AtomicString AddTokens(const Vector<String>&);
-  AtomicString RemoveToken(const AtomicString&);
-  AtomicString RemoveTokens(const Vector<String>&);
+  void AddTokens(const Vector<String>&);
+  void RemoveTokens(const Vector<String>&);
   virtual SpaceSplitString& MutableSet() { return tokens_; }
-  static AtomicString SerializeSet(const SpaceSplitString&);
 
  private:
+  void UpdateWithTokenSet(const SpaceSplitString&);
+  static AtomicString SerializeSet(const SpaceSplitString&);
+
   SpaceSplitString tokens_;
   AtomicString value_;
   WeakMember<DOMTokenListObserver> observer_;
diff --git a/third_party/WebKit/Source/core/editing/spellcheck/SpellCheckRequester.h b/third_party/WebKit/Source/core/editing/spellcheck/SpellCheckRequester.h
index 0b90abc..fbe2926 100644
--- a/third_party/WebKit/Source/core/editing/spellcheck/SpellCheckRequester.h
+++ b/third_party/WebKit/Source/core/editing/spellcheck/SpellCheckRequester.h
@@ -44,7 +44,7 @@
 class TextCheckerClient;
 
 // TODO(xiaochengh): Move this class to dedicated files.
-class CORE_EXPORT SpellCheckRequest final : public TextCheckingRequest {
+class SpellCheckRequest final : public TextCheckingRequest {
  public:
   static SpellCheckRequest* Create(const EphemeralRange& checking_range,
                                    int request_number);
diff --git a/third_party/WebKit/Source/core/editing/spellcheck/SpellChecker.cpp b/third_party/WebKit/Source/core/editing/spellcheck/SpellChecker.cpp
index d08c0004..fadc5589 100644
--- a/third_party/WebKit/Source/core/editing/spellcheck/SpellChecker.cpp
+++ b/third_party/WebKit/Source/core/editing/spellcheck/SpellChecker.cpp
@@ -537,7 +537,7 @@
                       DocumentMarker::MarkerType type,
                       int location,
                       int length,
-                      const Vector<String>& descriptions) {
+                      const String& description) {
   DCHECK(type == DocumentMarker::kSpelling || type == DocumentMarker::kGrammar)
       << type;
   DCHECK_GT(length, 0);
@@ -549,13 +549,6 @@
   if (!SpellChecker::IsSpellCheckingEnabledAt(range_to_mark.EndPosition()))
     return;
 
-  String description;
-  for (size_t i = 0; i < descriptions.size(); ++i) {
-    if (i != 0)
-      description.append('\n');
-    description.append(descriptions[i]);
-  }
-
   if (type == DocumentMarker::kSpelling) {
     document->Markers().AddSpellingMarker(range_to_mark.StartPosition(),
                                           range_to_mark.EndPosition(),
@@ -651,7 +644,7 @@
           continue;
         AddMarker(GetFrame().GetDocument(), paragraph.CheckingRange(),
                   DocumentMarker::kSpelling, result_location, result_length,
-                  result.replacements);
+                  result.replacement);
         continue;
 
       case kTextDecorationTypeGrammar:
@@ -667,7 +660,7 @@
             continue;
           AddMarker(GetFrame().GetDocument(), paragraph.CheckingRange(),
                     DocumentMarker::kGrammar, result_location + detail.location,
-                    detail.length, result.replacements);
+                    detail.length, result.replacement);
         }
         continue;
     }
diff --git a/third_party/WebKit/Source/core/editing/spellcheck/SpellCheckerTest.cpp b/third_party/WebKit/Source/core/editing/spellcheck/SpellCheckerTest.cpp
index e4539ef6..3e393d9 100644
--- a/third_party/WebKit/Source/core/editing/spellcheck/SpellCheckerTest.cpp
+++ b/third_party/WebKit/Source/core/editing/spellcheck/SpellCheckerTest.cpp
@@ -5,8 +5,6 @@
 #include "core/editing/spellcheck/SpellChecker.h"
 
 #include "core/editing/Editor.h"
-#include "core/editing/markers/DocumentMarkerController.h"
-#include "core/editing/spellcheck/SpellCheckRequester.h"
 #include "core/editing/spellcheck/SpellCheckTestBase.h"
 #include "core/frame/LocalFrame.h"
 #include "core/frame/LocalFrameView.h"
@@ -84,33 +82,4 @@
   EXPECT_EQ(start_count, LayoutCount());
 }
 
-TEST_F(SpellCheckerTest, MarkAndReplaceForHandlesMultipleReplacements) {
-  SetBodyContent(
-      "<div contenteditable>"
-      "spllchck"
-      "</div>");
-  Element* div = GetDocument().QuerySelector("div");
-  Node* text = div->firstChild();
-  EphemeralRange range_to_check =
-      EphemeralRange(Position(text, 0), Position(text, 8));
-
-  SpellCheckRequest* request = SpellCheckRequest::Create(range_to_check, 0);
-
-  TextCheckingResult result;
-  result.decoration = TextDecorationType::kTextDecorationTypeSpelling;
-  result.location = 0;
-  result.length = 8;
-  result.replacements = Vector<String>({"spellcheck", "spillchuck"});
-
-  GetDocument().GetFrame()->GetSpellChecker().MarkAndReplaceFor(
-      request, Vector<TextCheckingResult>({result}));
-
-  ASSERT_EQ(1u, GetDocument().Markers().Markers().size());
-
-  // The Spelling marker's description should be a newline-separated list of the
-  // suggested replacements
-  EXPECT_EQ("spellcheck\nspillchuck",
-            GetDocument().Markers().Markers()[0]->Description());
-}
-
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/exported/BUILD.gn b/third_party/WebKit/Source/core/exported/BUILD.gn
index 9231089..088f61d 100644
--- a/third_party/WebKit/Source/core/exported/BUILD.gn
+++ b/third_party/WebKit/Source/core/exported/BUILD.gn
@@ -27,6 +27,8 @@
     "WebHistoryItem.cpp",
     "WebImageCache.cpp",
     "WebImageDecoder.cpp",
+    "WebInputMethodControllerImpl.cpp",
+    "WebInputMethodControllerImpl.h",
     "WebMemoryStatistics.cpp",
     "WebPerformance.cpp",
     "WebPluginContainerBase.cpp",
diff --git a/third_party/WebKit/Source/web/WebInputMethodControllerImpl.cpp b/third_party/WebKit/Source/core/exported/WebInputMethodControllerImpl.cpp
similarity index 93%
rename from third_party/WebKit/Source/web/WebInputMethodControllerImpl.cpp
rename to third_party/WebKit/Source/core/exported/WebInputMethodControllerImpl.cpp
index 773c854..d071271 100644
--- a/third_party/WebKit/Source/web/WebInputMethodControllerImpl.cpp
+++ b/third_party/WebKit/Source/core/exported/WebInputMethodControllerImpl.cpp
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "web/WebInputMethodControllerImpl.h"
+#include "core/exported/WebInputMethodControllerImpl.h"
 
 #include "core/InputTypeNames.h"
 #include "core/dom/DocumentUserGestureToken.h"
@@ -15,29 +15,22 @@
 #include "core/editing/PlainTextRange.h"
 #include "core/exported/WebPluginContainerBase.h"
 #include "core/frame/LocalFrame.h"
+#include "core/frame/WebLocalFrameBase.h"
 #include "core/page/FocusController.h"
 #include "core/page/Page.h"
 #include "platform/UserGestureIndicator.h"
 #include "public/platform/WebString.h"
 #include "public/web/WebPlugin.h"
 #include "public/web/WebRange.h"
-#include "web/WebLocalFrameImpl.h"
 
 namespace blink {
 
 WebInputMethodControllerImpl::WebInputMethodControllerImpl(
-    WebLocalFrameImpl& web_frame)
+    WebLocalFrameBase& web_frame)
     : web_frame_(&web_frame) {}
 
 WebInputMethodControllerImpl::~WebInputMethodControllerImpl() {}
 
-// static
-WebInputMethodControllerImpl* WebInputMethodControllerImpl::FromFrame(
-    LocalFrame* frame) {
-  WebLocalFrameImpl* web_frame_impl = WebLocalFrameImpl::FromFrame(frame);
-  return web_frame_impl ? web_frame_impl->GetInputMethodController() : nullptr;
-}
-
 DEFINE_TRACE(WebInputMethodControllerImpl) {
   visitor->Trace(web_frame_);
 }
diff --git a/third_party/WebKit/Source/web/WebInputMethodControllerImpl.h b/third_party/WebKit/Source/core/exported/WebInputMethodControllerImpl.h
similarity index 85%
rename from third_party/WebKit/Source/web/WebInputMethodControllerImpl.h
rename to third_party/WebKit/Source/core/exported/WebInputMethodControllerImpl.h
index 4ecd0c8..944e919e 100644
--- a/third_party/WebKit/Source/web/WebInputMethodControllerImpl.h
+++ b/third_party/WebKit/Source/core/exported/WebInputMethodControllerImpl.h
@@ -5,6 +5,7 @@
 #ifndef WebInputMethodControllerImpl_h
 #define WebInputMethodControllerImpl_h
 
+#include "core/CoreExport.h"
 #include "platform/heap/Handle.h"
 #include "platform/wtf/Allocator.h"
 #include "public/web/WebCompositionUnderline.h"
@@ -14,21 +15,20 @@
 
 class InputMethodController;
 class LocalFrame;
-class WebLocalFrameImpl;
+class WebLocalFrameBase;
 class WebPlugin;
 class WebRange;
 class WebString;
 
-class WebInputMethodControllerImpl : public WebInputMethodController {
+class CORE_EXPORT WebInputMethodControllerImpl
+    : public NON_EXPORTED_BASE(WebInputMethodController) {
   WTF_MAKE_NONCOPYABLE(WebInputMethodControllerImpl);
   DISALLOW_NEW();
 
  public:
-  explicit WebInputMethodControllerImpl(WebLocalFrameImpl& web_frame);
+  explicit WebInputMethodControllerImpl(WebLocalFrameBase& web_frame);
   ~WebInputMethodControllerImpl() override;
 
-  static WebInputMethodControllerImpl* FromFrame(LocalFrame*);
-
   // WebInputMethodController overrides.
   bool SetComposition(const WebString& text,
                       const WebVector<WebCompositionUnderline>& underlines,
@@ -51,7 +51,7 @@
   InputMethodController& GetInputMethodController() const;
   WebPlugin* FocusedPluginIfInputMethodSupported() const;
 
-  const Member<WebLocalFrameImpl> web_frame_;
+  const Member<WebLocalFrameBase> web_frame_;
 };
 }  // namespace blink
 
diff --git a/third_party/WebKit/Source/core/exported/WebViewBase.h b/third_party/WebKit/Source/core/exported/WebViewBase.h
index ed45829..2f9129c1 100644
--- a/third_party/WebKit/Source/core/exported/WebViewBase.h
+++ b/third_party/WebKit/Source/core/exported/WebViewBase.h
@@ -33,7 +33,7 @@
 class PagePopupClient;
 class PageScaleConstraintsSet;
 class WebInputEvent;
-class WebInputMethodControllerImpl;
+class WebInputMethodController;
 class WebKeyboardEvent;
 class WebLayer;
 class WebLocalFrameBase;
@@ -171,7 +171,7 @@
   // corresponding to the focused frame. It will return nullptr if there is no
   // focused frame, or if there is one but it belongs to a different local
   // root.
-  virtual WebInputMethodControllerImpl* GetActiveWebInputMethodController()
+  virtual WebInputMethodController* GetActiveWebInputMethodController()
       const = 0;
   virtual void ScheduleAnimationForWidget() = 0;
   virtual CompositorWorkerProxyClient* CreateCompositorWorkerProxyClient() = 0;
diff --git a/third_party/WebKit/Source/core/style/ComputedStyle.cpp b/third_party/WebKit/Source/core/style/ComputedStyle.cpp
index 6d34f7a..b23deae 100644
--- a/third_party/WebKit/Source/core/style/ComputedStyle.cpp
+++ b/third_party/WebKit/Source/core/style/ComputedStyle.cpp
@@ -340,10 +340,6 @@
   // bunch of stuff other than real style data.
   // See comments for each skipped flag below.
 
-  // These are not generated in ComputedStyleBase
-  SetHasViewportUnits(other.HasViewportUnits());
-  SetHasRemUnitsInternal(other.HasRemUnits());
-
   // Correctly set during selector matching:
   // m_styleType
   // m_pseudoBits
diff --git a/third_party/WebKit/Source/core/svg/SVGElement.cpp b/third_party/WebKit/Source/core/svg/SVGElement.cpp
index 7b4e56a..d3bc529 100644
--- a/third_party/WebKit/Source/core/svg/SVGElement.cpp
+++ b/third_party/WebKit/Source/core/svg/SVGElement.cpp
@@ -31,8 +31,8 @@
 #include "core/animation/DocumentAnimations.h"
 #include "core/animation/EffectStack.h"
 #include "core/animation/ElementAnimations.h"
-#include "core/animation/InterpolationEnvironment.h"
 #include "core/animation/InvalidatableInterpolation.h"
+#include "core/animation/SVGInterpolationEnvironment.h"
 #include "core/animation/SVGInterpolationTypesMap.h"
 #include "core/css/resolver/StyleResolver.h"
 #include "core/dom/Document.h"
@@ -214,7 +214,7 @@
   for (auto& entry : active_interpolations_map) {
     const QualifiedName& attribute = entry.key.SvgAttribute();
     SVGInterpolationTypesMap map;
-    InterpolationEnvironment environment(
+    SVGInterpolationEnvironment environment(
         map, *this, PropertyFromAttribute(attribute)->BaseValueBase());
     InvalidatableInterpolation::ApplyStack(entry.value, environment);
   }
diff --git a/third_party/WebKit/Source/modules/accessibility/AXObjectImpl.cpp b/third_party/WebKit/Source/modules/accessibility/AXObjectImpl.cpp
index cdc1ec6..bef8d97b 100644
--- a/third_party/WebKit/Source/modules/accessibility/AXObjectImpl.cpp
+++ b/third_party/WebKit/Source/modules/accessibility/AXObjectImpl.cpp
@@ -58,7 +58,6 @@
 
 namespace {
 typedef HashMap<String, AccessibilityRole, CaseFoldingHash> ARIARoleMap;
-typedef HashSet<String, CaseFoldingHash> ARIAWidgetSet;
 
 struct RoleEntry {
   const char* aria_role;
@@ -315,32 +314,6 @@
   return internal_role_name_vector;
 }
 
-const char* g_aria_widgets[] = {
-    // From http://www.w3.org/TR/wai-aria/roles#widget_roles
-    "alert", "alertdialog", "button", "checkbox", "dialog", "gridcell", "link",
-    "log", "marquee", "menuitem", "menuitemcheckbox", "menuitemradio", "option",
-    "progressbar", "radio", "scrollbar", "slider", "spinbutton", "status",
-    "tab", "tabpanel", "textbox", "timer", "tooltip", "treeitem",
-    // Composite user interface widgets.
-    // This list is also from the w3.org site referenced above.
-    "combobox", "grid", "listbox", "menu", "menubar", "radiogroup", "tablist",
-    "tree", "treegrid"};
-
-static ARIAWidgetSet* CreateARIARoleWidgetSet() {
-  ARIAWidgetSet* widget_set = new HashSet<String, CaseFoldingHash>();
-  for (size_t i = 0; i < WTF_ARRAY_LENGTH(g_aria_widgets); ++i)
-    widget_set->insert(String(g_aria_widgets[i]));
-  return widget_set;
-}
-
-const char* g_aria_interactive_widget_attributes[] = {
-    // These attributes implicitly indicate the given widget is interactive.
-    // From http://www.w3.org/TR/wai-aria/states_and_properties#attrs_widgets
-    "aria-activedescendant", "aria-checked",  "aria-controls",
-    "aria-disabled",  // If it's disabled, it can be made interactive.
-    "aria-expanded",         "aria-haspopup", "aria-multiselectable",
-    "aria-pressed",          "aria-required", "aria-selected"};
-
 HTMLDialogElement* GetActiveDialogElement(Node* node) {
   return node->GetDocument().ActiveModalDialog();
 }
@@ -1907,48 +1880,6 @@
   return role;
 }
 
-bool AXObjectImpl::IsInsideFocusableElementOrARIAWidget(const Node& node) {
-  const Node* cur_node = &node;
-  do {
-    if (cur_node->IsElementNode()) {
-      const Element* element = ToElement(cur_node);
-      if (element->IsFocusable())
-        return true;
-      String role = element->getAttribute("role");
-      if (!role.IsEmpty() && AXObjectImpl::IncludesARIAWidgetRole(role))
-        return true;
-      if (HasInteractiveARIAAttribute(*element))
-        return true;
-    }
-    cur_node = cur_node->parentNode();
-  } while (cur_node && !isHTMLBodyElement(node));
-  return false;
-}
-
-bool AXObjectImpl::HasInteractiveARIAAttribute(const Element& element) {
-  for (size_t i = 0; i < WTF_ARRAY_LENGTH(g_aria_interactive_widget_attributes);
-       ++i) {
-    const char* attribute = g_aria_interactive_widget_attributes[i];
-    if (element.hasAttribute(attribute)) {
-      return true;
-    }
-  }
-  return false;
-}
-
-bool AXObjectImpl::IncludesARIAWidgetRole(const String& role) {
-  static const HashSet<String, CaseFoldingHash>* role_set =
-      CreateARIARoleWidgetSet();
-
-  Vector<String> role_vector;
-  role.Split(' ', role_vector);
-  for (const auto& child : role_vector) {
-    if (role_set->Contains(child))
-      return true;
-  }
-  return false;
-}
-
 bool AXObjectImpl::NameFromContents(bool recursive) const {
   // ARIA 1.1, section 5.2.7.5.
   bool result = false;
diff --git a/third_party/WebKit/Source/modules/accessibility/AXObjectImpl.h b/third_party/WebKit/Source/modules/accessibility/AXObjectImpl.h
index 0e818af..9eb5526 100644
--- a/third_party/WebKit/Source/modules/accessibility/AXObjectImpl.h
+++ b/third_party/WebKit/Source/modules/accessibility/AXObjectImpl.h
@@ -810,7 +810,6 @@
   static AccessibilityRole AriaRoleToWebCoreRole(const String&);
   static const AtomicString& RoleName(AccessibilityRole);
   static const AtomicString& InternalRoleName(AccessibilityRole);
-  static bool IsInsideFocusableElementOrARIAWidget(const Node&);
 
  protected:
   AXID id_;
@@ -886,8 +885,6 @@
 
  private:
   static bool IsNativeInputInMixedState(const Node*);
-  static bool IncludesARIAWidgetRole(const String&);
-  static bool HasInteractiveARIAAttribute(const Element&);
 
   static unsigned number_of_live_ax_objects_;
 };
diff --git a/third_party/WebKit/Source/modules/accessibility/AXObjectTest.cpp b/third_party/WebKit/Source/modules/accessibility/AXObjectTest.cpp
index 416a8d3..7a71cb0 100644
--- a/third_party/WebKit/Source/modules/accessibility/AXObjectTest.cpp
+++ b/third_party/WebKit/Source/modules/accessibility/AXObjectTest.cpp
@@ -48,25 +48,25 @@
   GetDocument().documentElement()->setInnerHTML(test_content);
   GetDocument().UpdateStyleAndLayout();
   Element* root(GetDocument().documentElement());
-  EXPECT_FALSE(AXObjectImpl::IsInsideFocusableElementOrARIAWidget(
+  EXPECT_FALSE(AXObject::IsInsideFocusableElementOrARIAWidget(
       *root->getElementById("plain")));
-  EXPECT_TRUE(AXObjectImpl::IsInsideFocusableElementOrARIAWidget(
+  EXPECT_TRUE(AXObject::IsInsideFocusableElementOrARIAWidget(
       *root->getElementById("button")));
-  EXPECT_TRUE(AXObjectImpl::IsInsideFocusableElementOrARIAWidget(
+  EXPECT_TRUE(AXObject::IsInsideFocusableElementOrARIAWidget(
       *root->getElementById("button-parent")));
-  EXPECT_TRUE(AXObjectImpl::IsInsideFocusableElementOrARIAWidget(
+  EXPECT_TRUE(AXObject::IsInsideFocusableElementOrARIAWidget(
       *root->getElementById("button-caps")));
-  EXPECT_TRUE(AXObjectImpl::IsInsideFocusableElementOrARIAWidget(
+  EXPECT_TRUE(AXObject::IsInsideFocusableElementOrARIAWidget(
       *root->getElementById("button-second")));
-  EXPECT_FALSE(AXObjectImpl::IsInsideFocusableElementOrARIAWidget(
+  EXPECT_FALSE(AXObject::IsInsideFocusableElementOrARIAWidget(
       *root->getElementById("aria-bogus")));
-  EXPECT_TRUE(AXObjectImpl::IsInsideFocusableElementOrARIAWidget(
+  EXPECT_TRUE(AXObject::IsInsideFocusableElementOrARIAWidget(
       *root->getElementById("aria-selected")));
-  EXPECT_TRUE(AXObjectImpl::IsInsideFocusableElementOrARIAWidget(
+  EXPECT_TRUE(AXObject::IsInsideFocusableElementOrARIAWidget(
       *root->getElementById("haspopup")));
-  EXPECT_TRUE(AXObjectImpl::IsInsideFocusableElementOrARIAWidget(
+  EXPECT_TRUE(AXObject::IsInsideFocusableElementOrARIAWidget(
       *root->getElementById("focusable")));
-  EXPECT_TRUE(AXObjectImpl::IsInsideFocusableElementOrARIAWidget(
+  EXPECT_TRUE(AXObject::IsInsideFocusableElementOrARIAWidget(
       *root->getElementById("focusable-parent")));
 }
 
diff --git a/third_party/WebKit/Source/platform/text/TextChecking.h b/third_party/WebKit/Source/platform/text/TextChecking.h
index 949f076..042dd22 100644
--- a/third_party/WebKit/Source/platform/text/TextChecking.h
+++ b/third_party/WebKit/Source/platform/text/TextChecking.h
@@ -53,7 +53,7 @@
   int location;
   int length;
   Vector<GrammarDetail> details;
-  Vector<String> replacements;
+  String replacement;
 };
 
 const int kUnrequestedTextCheckingSequence = -1;
@@ -73,7 +73,7 @@
   String text_;
 };
 
-class PLATFORM_EXPORT TextCheckingRequest
+class TextCheckingRequest
     : public GarbageCollectedFinalized<TextCheckingRequest> {
  public:
   virtual ~TextCheckingRequest() {}
diff --git a/third_party/WebKit/Source/web/BUILD.gn b/third_party/WebKit/Source/web/BUILD.gn
index 82e4383..aee8f6fb 100644
--- a/third_party/WebKit/Source/web/BUILD.gn
+++ b/third_party/WebKit/Source/web/BUILD.gn
@@ -147,8 +147,6 @@
     "WebIDBKeyRange.cpp",
     "WebInputElement.cpp",
     "WebInputEvent.cpp",
-    "WebInputMethodControllerImpl.cpp",
-    "WebInputMethodControllerImpl.h",
     "WebKit.cpp",
     "WebLabelElement.cpp",
     "WebLanguageDetectionDetails.cpp",
diff --git a/third_party/WebKit/Source/web/TextFinder.cpp b/third_party/WebKit/Source/web/TextFinder.cpp
index cfbcf88..3dbb69231 100644
--- a/third_party/WebKit/Source/web/TextFinder.cpp
+++ b/third_party/WebKit/Source/web/TextFinder.cpp
@@ -125,10 +125,10 @@
   // If the user has selected something since the last Find operation we want
   // to start from there. Otherwise, we start searching from where the last Find
   // operation left off (either a Find or a FindNext operation).
-  VisibleSelection selection(OwnerFrame()
-                                 .GetFrame()
-                                 ->Selection()
-                                 .ComputeVisibleSelectionInDOMTreeDeprecated());
+  // TODO(editing-dev): The use of VisibleSelection should be audited. See
+  // crbug.com/657237 for details.
+  VisibleSelection selection(
+      OwnerFrame().GetFrame()->Selection().ComputeVisibleSelectionInDOMTree());
   bool active_selection = !selection.IsNone();
   if (active_selection) {
     active_match_ = CreateRange(FirstEphemeralRangeOf(selection));
diff --git a/third_party/WebKit/Source/web/WebFrameWidgetImpl.cpp b/third_party/WebKit/Source/web/WebFrameWidgetImpl.cpp
index ce41639..e3806f2d 100644
--- a/third_party/WebKit/Source/web/WebFrameWidgetImpl.cpp
+++ b/third_party/WebKit/Source/web/WebFrameWidgetImpl.cpp
@@ -72,7 +72,6 @@
 #include "web/CompositorMutatorImpl.h"
 #include "web/CompositorWorkerProxyClientImpl.h"
 #include "web/WebDevToolsAgentImpl.h"
-#include "web/WebInputMethodControllerImpl.h"
 #include "web/WebPagePopupImpl.h"
 #include "web/WebRemoteFrameImpl.h"
 #include "web/WebViewFrameWidget.h"
@@ -459,9 +458,11 @@
   local_root_->GetFrameView()->SetBaseBackgroundColor(BaseBackgroundColor());
 }
 
-WebInputMethodControllerImpl*
+WebInputMethodController*
 WebFrameWidgetImpl::GetActiveWebInputMethodController() const {
-  return WebInputMethodControllerImpl::FromFrame(FocusedLocalFrameInWidget());
+  WebLocalFrameBase* local_frame =
+      WebLocalFrameBase::FromFrame(FocusedLocalFrameInWidget());
+  return local_frame ? local_frame->GetInputMethodController() : nullptr;
 }
 
 void WebFrameWidgetImpl::ScheduleAnimation() {
diff --git a/third_party/WebKit/Source/web/WebFrameWidgetImpl.h b/third_party/WebKit/Source/web/WebFrameWidgetImpl.h
index 83184da..0c64adb3 100644
--- a/third_party/WebKit/Source/web/WebFrameWidgetImpl.h
+++ b/third_party/WebKit/Source/web/WebFrameWidgetImpl.h
@@ -44,7 +44,6 @@
 #include "public/web/WebInputMethodController.h"
 #include "web/CompositorMutatorImpl.h"
 #include "web/PageWidgetDelegate.h"
-#include "web/WebInputMethodControllerImpl.h"
 
 namespace blink {
 
@@ -119,8 +118,7 @@
   void SetBaseBackgroundColorOverride(WebColor) override;
   void ClearBaseBackgroundColorOverride() override;
   void SetBaseBackgroundColor(WebColor) override;
-  WebInputMethodControllerImpl* GetActiveWebInputMethodController()
-      const override;
+  WebInputMethodController* GetActiveWebInputMethodController() const override;
 
   Frame* FocusedCoreFrame() const;
 
diff --git a/third_party/WebKit/Source/web/WebLocalFrameImpl.cpp b/third_party/WebKit/Source/web/WebLocalFrameImpl.cpp
index edb2409..47f9078 100644
--- a/third_party/WebKit/Source/web/WebLocalFrameImpl.cpp
+++ b/third_party/WebKit/Source/web/WebLocalFrameImpl.cpp
@@ -2543,7 +2543,7 @@
       ->ToSingleThreadTaskRunner();
 }
 
-WebInputMethodControllerImpl* WebLocalFrameImpl::GetInputMethodController() {
+WebInputMethodController* WebLocalFrameImpl::GetInputMethodController() {
   return &input_method_controller_;
 }
 
diff --git a/third_party/WebKit/Source/web/WebLocalFrameImpl.h b/third_party/WebKit/Source/web/WebLocalFrameImpl.h
index 708bd0b..05374d85 100644
--- a/third_party/WebKit/Source/web/WebLocalFrameImpl.h
+++ b/third_party/WebKit/Source/web/WebLocalFrameImpl.h
@@ -32,6 +32,7 @@
 #define WebLocalFrameImpl_h
 
 #include "core/editing/VisiblePosition.h"
+#include "core/exported/WebInputMethodControllerImpl.h"
 #include "core/frame/ContentSettingsClient.h"
 #include "core/frame/LocalFrame.h"
 #include "core/frame/WebFrameWidgetBase.h"
@@ -45,7 +46,6 @@
 #include "web/LocalFrameClientImpl.h"
 #include "web/UserMediaClientImpl.h"
 #include "web/WebExport.h"
-#include "web/WebInputMethodControllerImpl.h"
 
 #include <memory>
 
@@ -312,7 +312,7 @@
   base::SingleThreadTaskRunner* TimerTaskRunner() override;
   base::SingleThreadTaskRunner* LoadingTaskRunner() override;
   base::SingleThreadTaskRunner* UnthrottledTaskRunner() override;
-  WebInputMethodControllerImpl* GetInputMethodController() override;
+  WebInputMethodController* GetInputMethodController() override;
 
   void ExtractSmartClipData(WebRect rect_in_viewport,
                             WebString& clip_text,
diff --git a/third_party/WebKit/Source/web/WebNode.cpp b/third_party/WebKit/Source/web/WebNode.cpp
index 198f0ce..ba78476 100644
--- a/third_party/WebKit/Source/web/WebNode.cpp
+++ b/third_party/WebKit/Source/web/WebNode.cpp
@@ -31,6 +31,7 @@
 #include "public/web/WebNode.h"
 
 #include "bindings/core/v8/ExceptionState.h"
+#include "core/dom/AXObject.h"
 #include "core/dom/AXObjectCacheBase.h"
 #include "core/dom/Document.h"
 #include "core/dom/Element.h"
@@ -47,7 +48,6 @@
 #include "core/html/HTMLElement.h"
 #include "core/layout/LayoutObject.h"
 #include "core/layout/LayoutPart.h"
-#include "modules/accessibility/AXObjectImpl.h"
 #include "platform/wtf/PtrUtil.h"
 #include "public/platform/WebString.h"
 #include "public/web/WebAXObject.h"
@@ -127,7 +127,7 @@
 }
 
 bool WebNode::IsInsideFocusableElementOrARIAWidget() const {
-  return AXObjectImpl::IsInsideFocusableElementOrARIAWidget(
+  return AXObject::IsInsideFocusableElementOrARIAWidget(
       *this->ConstUnwrap<Node>());
 }
 
diff --git a/third_party/WebKit/Source/web/WebTextCheckingResult.cpp b/third_party/WebKit/Source/web/WebTextCheckingResult.cpp
index bf18da76..fa6ff49a 100644
--- a/third_party/WebKit/Source/web/WebTextCheckingResult.cpp
+++ b/third_party/WebKit/Source/web/WebTextCheckingResult.cpp
@@ -39,18 +39,12 @@
   result.decoration = static_cast<TextDecorationType>(decoration);
   result.location = location;
   result.length = length;
-
-  Vector<String> replacements_vec;
-  for (const WebString& replacement : replacements) {
-    replacements_vec.push_back(replacement);
-  }
-  result.replacements = replacements_vec;
-
+  result.replacement = replacement;
   if (result.decoration == kTextDecorationTypeGrammar) {
     GrammarDetail detail;
     detail.location = 0;
     detail.length = length;
-    detail.user_description = replacements.empty() ? "" : replacements[0];
+    detail.user_description = replacement;
     result.details.push_back(detail);
   }
 
diff --git a/third_party/WebKit/Source/web/WebViewFrameWidget.cpp b/third_party/WebKit/Source/web/WebViewFrameWidget.cpp
index 29cf1d8..1cf162e 100644
--- a/third_party/WebKit/Source/web/WebViewFrameWidget.cpp
+++ b/third_party/WebKit/Source/web/WebViewFrameWidget.cpp
@@ -7,7 +7,6 @@
 #include "core/exported/WebViewBase.h"
 #include "core/frame/WebLocalFrameBase.h"
 #include "core/layout/HitTestResult.h"
-#include "web/WebInputMethodControllerImpl.h"
 
 namespace blink {
 
@@ -211,7 +210,7 @@
   return web_view_->MainFrameImpl();
 }
 
-WebInputMethodControllerImpl*
+WebInputMethodController*
 WebViewFrameWidget::GetActiveWebInputMethodController() const {
   return web_view_->GetActiveWebInputMethodController();
 }
diff --git a/third_party/WebKit/Source/web/WebViewFrameWidget.h b/third_party/WebKit/Source/web/WebViewFrameWidget.h
index 93410d7..3df2833ba 100644
--- a/third_party/WebKit/Source/web/WebViewFrameWidget.h
+++ b/third_party/WebKit/Source/web/WebViewFrameWidget.h
@@ -10,7 +10,6 @@
 #include "platform/heap/Handle.h"
 #include "platform/wtf/Noncopyable.h"
 #include "platform/wtf/RefPtr.h"
-#include "web/WebInputMethodControllerImpl.h"
 
 namespace blink {
 
@@ -92,8 +91,7 @@
   void ClearBaseBackgroundColorOverride() override;
   void SetBaseBackgroundColor(WebColor) override;
   WebLocalFrameBase* LocalRoot() const override;
-  WebInputMethodControllerImpl* GetActiveWebInputMethodController()
-      const override;
+  WebInputMethodController* GetActiveWebInputMethodController() const override;
 
   // WebFrameWidgetBase overrides:
   bool ForSubframe() const override { return false; }
diff --git a/third_party/WebKit/Source/web/WebViewImpl.cpp b/third_party/WebKit/Source/web/WebViewImpl.cpp
index 2ab59d9..633c2cb 100644
--- a/third_party/WebKit/Source/web/WebViewImpl.cpp
+++ b/third_party/WebKit/Source/web/WebViewImpl.cpp
@@ -172,7 +172,6 @@
 #include "web/PrerendererClientImpl.h"
 #include "web/StorageQuotaClientImpl.h"
 #include "web/WebDevToolsAgentImpl.h"
-#include "web/WebInputMethodControllerImpl.h"
 #include "web/WebRemoteFrameImpl.h"
 #include "web/WebSettingsImpl.h"
 
@@ -3514,9 +3513,11 @@
   CancelPagePopup();
 }
 
-WebInputMethodControllerImpl* WebViewImpl::GetActiveWebInputMethodController()
+WebInputMethodController* WebViewImpl::GetActiveWebInputMethodController()
     const {
-  return WebInputMethodControllerImpl::FromFrame(FocusedLocalFrameInWidget());
+  WebLocalFrameBase* local_frame =
+      WebLocalFrameBase::FromFrame(FocusedLocalFrameInWidget());
+  return local_frame ? local_frame->GetInputMethodController() : nullptr;
 }
 
 Color WebViewImpl::BaseBackgroundColor() const {
diff --git a/third_party/WebKit/Source/web/WebViewImpl.h b/third_party/WebKit/Source/web/WebViewImpl.h
index 116282d..b3cfc49 100644
--- a/third_party/WebKit/Source/web/WebViewImpl.h
+++ b/third_party/WebKit/Source/web/WebViewImpl.h
@@ -85,7 +85,7 @@
 class WebActiveGestureAnimation;
 class WebDevToolsAgentImpl;
 class WebElement;
-class WebInputMethodControllerImpl;
+class WebInputMethodController;
 class WebLayerTreeView;
 class WebLocalFrame;
 class WebLocalFrameBase;
@@ -504,7 +504,7 @@
   // corresponding to the focused frame. It will return nullptr if there is no
   // focused frame, or if the there is one but it belongs to a different local
   // root.
-  WebInputMethodControllerImpl* GetActiveWebInputMethodController() const;
+  WebInputMethodController* GetActiveWebInputMethodController() const;
 
   void SetLastHiddenPagePopup(WebPagePopupImpl* page_popup) override {
     last_hidden_page_popup_ = page_popup;
diff --git a/third_party/WebKit/Source/web/tests/WebFrameTest.cpp b/third_party/WebKit/Source/web/tests/WebFrameTest.cpp
index 87e8e69..9aa8819c 100644
--- a/third_party/WebKit/Source/web/tests/WebFrameTest.cpp
+++ b/third_party/WebKit/Source/web/tests/WebFrameTest.cpp
@@ -6600,9 +6600,9 @@
     Vector<WebTextCheckingResult> results;
     const int kMisspellingStartOffset = 1;
     const int kMisspellingLength = 8;
-    results.push_back(WebTextCheckingResult(
-        kWebTextDecorationTypeSpelling, kMisspellingStartOffset,
-        kMisspellingLength, WebVector<WebString>()));
+    results.push_back(WebTextCheckingResult(kWebTextDecorationTypeSpelling,
+                                            kMisspellingStartOffset,
+                                            kMisspellingLength, WebString()));
     completion->DidFinishCheckingText(results);
   }
   int NumberOfTimesChecked() const { return number_of_times_checked_; }
diff --git a/third_party/WebKit/Source/web/tests/WebViewTest.cpp b/third_party/WebKit/Source/web/tests/WebViewTest.cpp
index dad995b..b602ec0 100644
--- a/third_party/WebKit/Source/web/tests/WebViewTest.cpp
+++ b/third_party/WebKit/Source/web/tests/WebViewTest.cpp
@@ -108,7 +108,6 @@
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "third_party/skia/include/core/SkCanvas.h"
 #include "web/DevToolsEmulator.h"
-#include "web/WebInputMethodControllerImpl.h"
 #include "web/WebSettingsImpl.h"
 #include "web/tests/FrameTestHelpers.h"
 
diff --git a/third_party/WebKit/public/web/WebTextCheckingResult.h b/third_party/WebKit/public/web/WebTextCheckingResult.h
index b63cae74..4eb36d8 100644
--- a/third_party/WebKit/public/web/WebTextCheckingResult.h
+++ b/third_party/WebKit/public/web/WebTextCheckingResult.h
@@ -31,10 +31,9 @@
 #ifndef WebTextCheckingResult_h
 #define WebTextCheckingResult_h
 
-#include "WebTextDecorationType.h"
 #include "public/platform/WebCommon.h"
 #include "public/platform/WebString.h"
-#include "public/platform/WebVector.h"
+#include "WebTextDecorationType.h"
 
 namespace blink {
 
@@ -45,15 +44,14 @@
   WebTextCheckingResult()
       : decoration(kWebTextDecorationTypeSpelling), location(0), length(0) {}
 
-  WebTextCheckingResult(
-      WebTextDecorationType decoration,
-      int location,
-      int length,
-      const WebVector<WebString>& replacements = WebVector<WebString>())
+  WebTextCheckingResult(WebTextDecorationType decoration,
+                        int location,
+                        int length,
+                        const WebString& replacement = WebString())
       : decoration(decoration),
         location(location),
         length(length),
-        replacements(replacements) {}
+        replacement(replacement) {}
 
 #if BLINK_IMPLEMENTATION
   operator TextCheckingResult() const;
@@ -62,7 +60,7 @@
   WebTextDecorationType decoration;
   int location;
   int length;
-  WebVector<WebString> replacements;
+  WebString replacement;
 };
 
 }  // namespace blink
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index bf752704..8ebcddc 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -14812,6 +14812,7 @@
 <action name="ShowBookmarks">
   <owner>Please list the metric's owners. Add more owner tags as needed.</owner>
   <description>Please enter the description of this user action.</description>
+  <obsolete>Duplicate of ShowBookmarkManager.</obsolete>
 </action>
 
 <action name="ShowBookmarksBar">
diff --git a/ui/message_center/views/notification_view.cc b/ui/message_center/views/notification_view.cc
index 54cb37b3..5fdf772 100644
--- a/ui/message_center/views/notification_view.cc
+++ b/ui/message_center/views/notification_view.cc
@@ -346,6 +346,11 @@
   return views::GetNativeHandCursor();
 }
 
+void NotificationView::OnMouseMoved(const ui::MouseEvent& event) {
+  MessageView::OnMouseMoved(event);
+  UpdateControlButtonsVisibility();
+}
+
 void NotificationView::OnMouseEntered(const ui::MouseEvent& event) {
   MessageView::OnMouseEntered(event);
   UpdateControlButtonsVisibility();
diff --git a/ui/message_center/views/notification_view.h b/ui/message_center/views/notification_view.h
index 57398c5..7fa691a 100644
--- a/ui/message_center/views/notification_view.h
+++ b/ui/message_center/views/notification_view.h
@@ -46,6 +46,7 @@
   void OnFocus() override;
   void ScrollRectToVisible(const gfx::Rect& rect) override;
   gfx::NativeCursor GetCursor(const ui::MouseEvent& event) override;
+  void OnMouseMoved(const ui::MouseEvent& event) override;
   void OnMouseEntered(const ui::MouseEvent& event) override;
   void OnMouseExited(const ui::MouseEvent& event) override;