diff --git a/DEPS b/DEPS
index 1c50f2c..a8c9bffa 100644
--- a/DEPS
+++ b/DEPS
@@ -44,7 +44,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'cb99d142e27d0570b871503bbae9a5f1b19a594a',
+  'v8_revision': 'b481bccc84783445e123b8b4e3ff9ecfdac2bd90',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
diff --git a/android_webview/BUILD.gn b/android_webview/BUILD.gn
index cc114f8f..023bc64c 100644
--- a/android_webview/BUILD.gn
+++ b/android_webview/BUILD.gn
@@ -583,6 +583,7 @@
     "java/src/org/chromium/android_webview/AwPrintDocumentAdapter.java",
     "java/src/org/chromium/android_webview/AwSafeBrowsingConfigHelper.java",
     "java/src/org/chromium/android_webview/AwQuotaManagerBridge.java",
+    "java/src/org/chromium/android_webview/AwRendererPriorityManager.java",
     "java/src/org/chromium/android_webview/AwRenderProcessGoneDetail.java",
     "java/src/org/chromium/android_webview/AwResource.java",
     "java/src/org/chromium/android_webview/AwScrollOffsetManager.java",
@@ -646,7 +647,10 @@
     deps += [ "//components/spellcheck/browser/android:java" ]
   }
 
-  srcjar_deps = [ "//android_webview/native:aw_permission_request_resource" ]
+  srcjar_deps = [
+    "//android_webview/native:aw_permission_request_resource",
+    "//android_webview/native:aw_renderer_priority_manager_renderer_priority",
+  ]
 }
 
 java_strings_grd("strings_grd") {
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromium.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromium.java
index 21f159b..0ea0849 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromium.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromium.java
@@ -93,7 +93,7 @@
     // Variables for functionality provided by this adapter ---------------------------------------
     private ContentSettingsAdapter mWebSettings;
     // The WebView wrapper for ContentViewCore and required browser compontents.
-    private AwContents mAwContents;
+    AwContents mAwContents;
     // Non-null if this webview is using the GL accelerated draw path.
     private DrawGLFunctor mGLfunctor;
 
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java
index 3e1ed55..5e9fd1b 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java
@@ -179,7 +179,7 @@
     private SharedPreferences mWebViewPrefs;
     private WebViewDelegate mWebViewDelegate;
 
-    private boolean mShouldDisableThreadChecking;
+    boolean mShouldDisableThreadChecking;
 
     /**
      * Entry point for newer versions of Android.
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContents.java b/android_webview/java/src/org/chromium/android_webview/AwContents.java
index 69393ce..6ccba0af 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwContents.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java
@@ -45,6 +45,7 @@
 
 import org.chromium.android_webview.permission.AwGeolocationCallback;
 import org.chromium.android_webview.permission.AwPermissionRequest;
+import org.chromium.android_webview.renderer_priority.RendererPriority.RendererPriorityEnum;
 import org.chromium.base.LocaleUtils;
 import org.chromium.base.Log;
 import org.chromium.base.ObserverList;
@@ -1175,7 +1176,8 @@
     @CalledByNative
     private boolean onRenderProcessGoneDetail(int childProcessID, boolean crashed) {
         if (isDestroyed(NO_WARN)) return false;
-        return mContentsClient.onRenderProcessGone(new AwRenderProcessGoneDetail(crashed));
+        return mContentsClient.onRenderProcessGone(new AwRenderProcessGoneDetail(
+                crashed, nativeGetRendererCurrentPriority(mNativeAwContents)));
     }
 
     private boolean isNoOperation() {
@@ -2699,6 +2701,20 @@
         return mIsPopupWindow;
     }
 
+    @RendererPriorityEnum
+    public int getRendererRequestedPriority() {
+        return nativeGetRendererRequestedPriority(mNativeAwContents);
+    }
+
+    public boolean getRendererPriorityWaivedWhenNotVisible() {
+        return nativeGetRendererPriorityWaivedWhenNotVisible(mNativeAwContents);
+    }
+
+    public void setRendererPriorityPolicy(
+            @RendererPriorityEnum int rendererRequestedPriority, boolean waivedWhenNotVisible) {
+        nativeSetRendererPriorityPolicy(
+                mNativeAwContents, rendererRequestedPriority, waivedWhenNotVisible);
+    }
     //--------------------------------------------------------------------------------------------
     //  Methods called from native via JNI
     //--------------------------------------------------------------------------------------------
@@ -3436,6 +3452,12 @@
     private native void nativeInvokeGeolocationCallback(
             long nativeAwContents, boolean value, String requestingFrame);
 
+    private native int nativeGetRendererRequestedPriority(long nativeAwContents);
+    private native boolean nativeGetRendererPriorityWaivedWhenNotVisible(long nativeAwContents);
+    private native int nativeGetRendererCurrentPriority(long nativeAwContents);
+    private native void nativeSetRendererPriorityPolicy(
+            long nativeAwContents, int rendererRequestedPriority, boolean waivedWhenNotVisible);
+
     private native void nativeSetJsOnlineProperty(long nativeAwContents, boolean networkUp);
 
     private native void nativeTrimMemory(long nativeAwContents, int level, boolean visible);
diff --git a/android_webview/java/src/org/chromium/android_webview/AwRenderProcessGoneDetail.java b/android_webview/java/src/org/chromium/android_webview/AwRenderProcessGoneDetail.java
index 5b551db0..8bd4d65 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwRenderProcessGoneDetail.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwRenderProcessGoneDetail.java
@@ -4,18 +4,27 @@
 
 package org.chromium.android_webview;
 
+import org.chromium.android_webview.renderer_priority.RendererPriority.RendererPriorityEnum;
 /**
  * This class provides more specific information about why the render process
  * exited. It is peer of android.webkit.RenderProcessGoneDetail.
  */
 public class AwRenderProcessGoneDetail {
     private final boolean mDidCrash;
+    @RendererPriorityEnum
+    private final int mRendererPriority;
 
-    public AwRenderProcessGoneDetail(boolean didCrash) {
+    public AwRenderProcessGoneDetail(boolean didCrash, @RendererPriorityEnum int rendererPriority) {
         mDidCrash = didCrash;
+        mRendererPriority = rendererPriority;
     }
 
     public boolean didCrash() {
         return mDidCrash;
     }
+
+    @RendererPriorityEnum
+    public int rendererPriority() {
+        return mRendererPriority;
+    }
 }
diff --git a/android_webview/java/src/org/chromium/android_webview/AwRendererPriorityManager.java b/android_webview/java/src/org/chromium/android_webview/AwRendererPriorityManager.java
new file mode 100644
index 0000000..96db506c
--- /dev/null
+++ b/android_webview/java/src/org/chromium/android_webview/AwRendererPriorityManager.java
@@ -0,0 +1,24 @@
+// 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.android_webview;
+
+import org.chromium.android_webview.renderer_priority.RendererPriority;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.content.browser.ChildProcessLauncher;
+
+/**
+ * Exposes an interface via which native code can manage the priority
+ * of a renderer process.
+ */
+@JNINamespace("android_webview")
+public class AwRendererPriorityManager {
+    @CalledByNative
+    private static void setRendererPriority(
+            int pid, @RendererPriority.RendererPriorityEnum int rendererPriority) {
+        // TODO(tobiasjs): handle RendererPriority.LOW separately from WAIVED.
+        ChildProcessLauncher.setInForeground(pid, rendererPriority == RendererPriority.HIGH);
+    }
+}
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsTest.java
index fac36c4..a475c49 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsTest.java
@@ -22,6 +22,7 @@
 import org.chromium.android_webview.AwContents;
 import org.chromium.android_webview.AwSettings;
 import org.chromium.android_webview.AwSwitches;
+import org.chromium.android_webview.renderer_priority.RendererPriority;
 import org.chromium.android_webview.test.TestAwContentsClient.OnDownloadStartHelper;
 import org.chromium.android_webview.test.util.CommonResources;
 import org.chromium.android_webview.test.util.JSUtils;
@@ -553,17 +554,46 @@
     private static class MockBindingManager implements BindingManager {
         private boolean mIsChildProcessCreated;
 
+        private Object mForegroundStateLock;
+        private ArrayList<Boolean> mForegroundState;
+
+        public MockBindingManager() {
+            super();
+            mForegroundStateLock = new Object();
+            mForegroundState = new ArrayList<Boolean>();
+        }
+
         boolean isChildProcessCreated() {
             return mIsChildProcessCreated;
         }
 
+        void assertSetInForegroundCall(boolean inForeground) {
+            synchronized (mForegroundStateLock) {
+                if (mForegroundState.size() == 0) {
+                    try {
+                        mForegroundStateLock.wait(WAIT_TIMEOUT_MS);
+                    } catch (InterruptedException e) {
+                    }
+                }
+                assertTrue(mForegroundState.size() != 0);
+                assertEquals(inForeground, mForegroundState.get(0).booleanValue());
+                mForegroundState.remove(0);
+                return;
+            }
+        }
+
         @Override
         public void addNewConnection(int pid, ChildProcessConnection connection) {
             mIsChildProcessCreated = true;
         }
 
         @Override
-        public void setInForeground(int pid, boolean inForeground) {}
+        public void setInForeground(int pid, boolean inForeground) {
+            synchronized (mForegroundStateLock) {
+                mForegroundState.add(inForeground);
+                mForegroundStateLock.notifyAll();
+            }
+        }
 
         @Override
         public void determinedVisibility(int pid) {}
@@ -618,4 +648,94 @@
                         mContentsClient.getOnEvaluateJavaScriptResultHelper(), "21 + 21"));
     }
 
+    /**
+     * By default the renderer should be considererd to be in the
+     * foreground.
+     */
+    @Feature({"AndroidWebView"})
+    @SmallTest
+    @CommandLineFlags
+            .Add(AwSwitches.WEBVIEW_SANDBOXED_RENDERER)
+            @ParameterizedTest.Set
+            public void testRendererPriorityStartsHigh() throws Throwable {
+        MockBindingManager bindingManager = new MockBindingManager();
+        ChildProcessLauncher.setBindingManagerForTesting(bindingManager);
+        assertFalse(bindingManager.isChildProcessCreated());
+
+        AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
+        AwContents awContents = testView.getAwContents();
+        loadDataSync(awContents, mContentsClient.getOnPageFinishedHelper(), "<html></html>",
+                "text/html", false);
+
+        assertTrue(bindingManager.isChildProcessCreated());
+        bindingManager.assertSetInForegroundCall(true);
+    }
+
+    /**
+     * If we specify that the priority is WAIVED, then the renderer
+     * should not be in the foreground.
+     */
+    @Feature({"AndroidWebView"})
+    @SmallTest
+    @CommandLineFlags
+            .Add(AwSwitches.WEBVIEW_SANDBOXED_RENDERER)
+            @ParameterizedTest.Set
+            public void testRendererPriorityLow() throws Throwable {
+        MockBindingManager bindingManager = new MockBindingManager();
+        ChildProcessLauncher.setBindingManagerForTesting(bindingManager);
+        assertFalse(bindingManager.isChildProcessCreated());
+
+        final AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
+        final AwContents awContents = testView.getAwContents();
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                awContents.setRendererPriorityPolicy(RendererPriority.WAIVED, false);
+            }
+        });
+        loadDataSync(awContents, mContentsClient.getOnPageFinishedHelper(), "<html></html>",
+                "text/html", false);
+
+        assertTrue(awContents.isPageVisible());
+        assertTrue(bindingManager.isChildProcessCreated());
+        bindingManager.assertSetInForegroundCall(false);
+    }
+
+    /**
+     * If we specify that the priority is HIGH, but WAIVED when in the
+     * background, then pausing the view should send the renderer to
+     * the background.
+     */
+    @Feature({"AndroidWebView"})
+    @SmallTest
+    @CommandLineFlags
+            .Add(AwSwitches.WEBVIEW_SANDBOXED_RENDERER)
+            @ParameterizedTest.Set
+            public void testRendererPriorityManaged() throws Throwable {
+        MockBindingManager bindingManager = new MockBindingManager();
+        ChildProcessLauncher.setBindingManagerForTesting(bindingManager);
+        assertFalse(bindingManager.isChildProcessCreated());
+
+        final AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
+        final AwContents awContents = testView.getAwContents();
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                awContents.setRendererPriorityPolicy(RendererPriority.HIGH, true);
+            }
+        });
+        loadDataSync(awContents, mContentsClient.getOnPageFinishedHelper(), "<html></html>",
+                "text/html", false);
+
+        assertTrue(awContents.isPageVisible());
+        assertTrue(bindingManager.isChildProcessCreated());
+        bindingManager.assertSetInForegroundCall(true);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                awContents.onPause();
+            }
+        });
+        bindingManager.assertSetInForegroundCall(false);
+    }
 }
diff --git a/android_webview/lib/main/aw_main_delegate.cc b/android_webview/lib/main/aw_main_delegate.cc
index 84c98fd..9b9848ed 100644
--- a/android_webview/lib/main/aw_main_delegate.cc
+++ b/android_webview/lib/main/aw_main_delegate.cc
@@ -147,7 +147,7 @@
   if (cl->HasSwitch(switches::kWebViewSandboxedRenderer)) {
     cl->AppendSwitch(switches::kInProcessGPU);
     cl->AppendSwitchASCII(switches::kRendererProcessLimit, "1");
-    cl->AppendSwitch(switches::kDisableRendererBackgrounding);
+    cl->AppendSwitch(switches::kDisableRendererPriorityManagement);
   }
 
   CommandLineHelper::AddEnabledFeature(
diff --git a/android_webview/native/BUILD.gn b/android_webview/native/BUILD.gn
index 2b2d04e6..4efadee5 100644
--- a/android_webview/native/BUILD.gn
+++ b/android_webview/native/BUILD.gn
@@ -76,6 +76,8 @@
     "aw_picture.h",
     "aw_quota_manager_bridge_impl.cc",
     "aw_quota_manager_bridge_impl.h",
+    "aw_renderer_priority_manager.cc",
+    "aw_renderer_priority_manager.h",
     "aw_resource.cc",
     "aw_settings.cc",
     "aw_settings.h",
@@ -141,6 +143,7 @@
     "../java/src/org/chromium/android_webview/AwPdfExporter.java",
     "../java/src/org/chromium/android_webview/AwPicture.java",
     "../java/src/org/chromium/android_webview/AwQuotaManagerBridge.java",
+    "../java/src/org/chromium/android_webview/AwRendererPriorityManager.java",
     "../java/src/org/chromium/android_webview/AwResource.java",
     "../java/src/org/chromium/android_webview/AwSettings.java",
     "../java/src/org/chromium/android_webview/AwTokenBindingManager.java",
@@ -168,3 +171,9 @@
     "permission/aw_permission_request.h",
   ]
 }
+
+java_cpp_enum("aw_renderer_priority_manager_renderer_priority") {
+  sources = [
+    "aw_renderer_priority_manager.h",
+  ]
+}
diff --git a/android_webview/native/aw_contents.cc b/android_webview/native/aw_contents.cc
index dfd0326..050d715 100644
--- a/android_webview/native/aw_contents.cc
+++ b/android_webview/native/aw_contents.cc
@@ -27,6 +27,7 @@
 #include "android_webview/native/aw_gl_functor.h"
 #include "android_webview/native/aw_pdf_exporter.h"
 #include "android_webview/native/aw_picture.h"
+#include "android_webview/native/aw_renderer_priority_manager.h"
 #include "android_webview/native/aw_web_contents_delegate.h"
 #include "android_webview/native/java_browser_view_renderer_helper.h"
 #include "android_webview/native/permission/aw_permission_request.h"
@@ -67,6 +68,8 @@
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_view_host.h"
+#include "content/public/browser/render_widget_host.h"
+#include "content/public/browser/render_widget_host_iterator.h"
 #include "content/public/browser/ssl_status.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/mhtml_generation_params.h"
@@ -79,7 +82,6 @@
 #include "ui/gfx/geometry/rect_f.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/image/image.h"
-
 struct AwDrawSWFunctionTable;
 
 using autofill::ContentAutofillDriverFactory;
@@ -112,6 +114,8 @@
 std::string g_locale_list;
 
 const void* kAwContentsUserDataKey = &kAwContentsUserDataKey;
+const void* kComputedRendererPriorityUserDataKey =
+    &kComputedRendererPriorityUserDataKey;
 
 class AwContentsUserData : public base::SupportsUserData::Data {
  public:
@@ -199,7 +203,10 @@
           this,
           BrowserThread::GetTaskRunnerForThread(BrowserThread::UI)),
       web_contents_(std::move(web_contents)),
-      renderer_manager_key_(GLViewRendererManager::GetInstance()->NullKey()) {
+      renderer_manager_key_(GLViewRendererManager::GetInstance()->NullKey()),
+      renderer_requested_priority_(
+          AwRendererPriorityManager::RENDERER_PRIORITY_HIGH),
+      renderer_priority_waived_when_not_visible_(false) {
   base::subtle::NoBarrier_AtomicIncrement(&g_instance_count, 1);
   icon_helper_.reset(new IconHelper(web_contents_.get()));
   icon_helper_->SetListener(this);
@@ -228,6 +235,8 @@
     InitAutofillIfNecessary(autofill_manager_delegate->GetSaveFormData());
   content::SynchronousCompositor::SetClientForWebContents(
       web_contents_.get(), &browser_view_renderer_);
+  UpdateRendererPriority();
+  web_contents_->GetRenderProcessHost()->AddObserver(this);
   AwContentsLifecycleNotifier::OnWebViewCreated();
 }
 
@@ -307,6 +316,8 @@
 
 AwContents::~AwContents() {
   DCHECK_EQ(this, AwContents::FromWebContents(web_contents_.get()));
+  web_contents_->GetRenderProcessHost()->RemoveObserver(this);
+  UpdateRendererPriority(AwRendererPriorityManager::RENDERER_PRIORITY_WAIVED);
   web_contents_->RemoveUserData(kAwContentsUserDataKey);
   if (find_helper_.get())
     find_helper_->SetListener(NULL);
@@ -859,6 +870,7 @@
                                    bool visible) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   browser_view_renderer_.SetViewVisibility(visible);
+  UpdateRendererPriority();
 }
 
 void AwContents::SetWindowVisibility(JNIEnv* env,
@@ -866,6 +878,7 @@
                                      bool visible) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   browser_view_renderer_.SetWindowVisibility(visible);
+  UpdateRendererPriority();
 }
 
 void AwContents::SetIsPaused(JNIEnv* env,
@@ -873,6 +886,7 @@
                              bool paused) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   browser_view_renderer_.SetIsPaused(paused);
+  UpdateRendererPriority();
 }
 
 void AwContents::OnAttachedToWindow(JNIEnv* env,
@@ -881,12 +895,14 @@
                                     int h) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   browser_view_renderer_.OnAttachedToWindow(w, h);
+  UpdateRendererPriority();
 }
 
 void AwContents::OnDetachedFromWindow(JNIEnv* env,
                                       const JavaParamRef<jobject>& obj) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   browser_view_renderer_.OnDetachedFromWindow();
+  UpdateRendererPriority();
 }
 
 bool AwContents::IsVisible(JNIEnv* env, const JavaParamRef<jobject>& obj) {
@@ -1173,6 +1189,91 @@
                  ScopedJavaGlobalRef<jobject>(env, callback)));
 }
 
+void AwContents::UpdateRendererPriority(
+    AwRendererPriorityManager::RendererPriority base_priority) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  content::RenderProcessHost* rph = web_contents_->GetRenderProcessHost();
+  AwRendererPriorityManager::RendererPriority computed_priority = base_priority;
+
+  std::unique_ptr<content::RenderWidgetHostIterator> widgets(
+      content::RenderWidgetHost::GetRenderWidgetHosts());
+  content::RenderWidgetHost* widget;
+  while ((widget = widgets->GetNextHost()) != nullptr &&
+         computed_priority <
+             AwRendererPriorityManager::RENDERER_PRIORITY_HIGH) {
+    content::RenderViewHost* view = content::RenderViewHost::From(widget);
+    if (view && rph == view->GetProcess()) {
+      content::WebContents* wc = content::WebContents::FromRenderViewHost(view);
+      if (wc && wc != web_contents_.get()) {
+        computed_priority =
+            std::max(FromWebContents(wc)->GetComputedRendererPriority(),
+                     computed_priority);
+      }
+    }
+  }
+  GetAwRendererPriorityManager()->SetRendererPriority(computed_priority);
+}
+
+AwRendererPriorityManager::RendererPriority
+AwContents::GetComputedRendererPriority() {
+  if (renderer_priority_waived_when_not_visible_ &&
+      !browser_view_renderer_.IsClientVisible()) {
+    return AwRendererPriorityManager::RENDERER_PRIORITY_WAIVED;
+  }
+  return renderer_requested_priority_;
+}
+
+void AwContents::UpdateRendererPriority() {
+  UpdateRendererPriority(GetComputedRendererPriority());
+}
+
+AwRendererPriorityManager* AwContents::GetAwRendererPriorityManager() {
+  content::RenderProcessHost* rph = web_contents_->GetRenderProcessHost();
+  AwRendererPriorityManager* manager = static_cast<AwRendererPriorityManager*>(
+      rph->GetUserData(kComputedRendererPriorityUserDataKey));
+  if (manager == nullptr) {
+    rph->SetUserData(kComputedRendererPriorityUserDataKey,
+                     manager = new AwRendererPriorityManager(rph));
+  }
+  return manager;
+}
+
+AwRendererPriorityManager::RendererPriority
+AwContents::GetCurrentRendererPriority() {
+  return GetAwRendererPriorityManager()->GetRendererPriority();
+}
+
+jint AwContents::GetRendererCurrentPriority(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& obj) {
+  return GetCurrentRendererPriority();
+}
+
+jint AwContents::GetRendererRequestedPriority(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& obj) {
+  return renderer_requested_priority_;
+}
+
+jboolean AwContents::GetRendererPriorityWaivedWhenNotVisible(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& obj) {
+  return renderer_priority_waived_when_not_visible_;
+}
+
+void AwContents::SetRendererPriorityPolicy(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& obj,
+    jint renderer_requested_priority,
+    jboolean renderer_priority_waived_when_not_visible) {
+  renderer_requested_priority_ =
+      static_cast<AwRendererPriorityManager::RendererPriority>(
+          renderer_requested_priority);
+  renderer_priority_waived_when_not_visible_ =
+      renderer_priority_waived_when_not_visible;
+  UpdateRendererPriority(renderer_requested_priority_);
+}
+
 void AwContents::ClearView(JNIEnv* env, const JavaParamRef<jobject>& obj) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   browser_view_renderer_.ClearView();
@@ -1343,4 +1444,8 @@
       child_process_id, crashed);
 }
 
+void AwContents::RenderProcessReady(content::RenderProcessHost* host) {
+  UpdateRendererPriority();
+}
+
 }  // namespace android_webview
diff --git a/android_webview/native/aw_contents.h b/android_webview/native/aw_contents.h
index 761a723..fefaefb4 100644
--- a/android_webview/native/aw_contents.h
+++ b/android_webview/native/aw_contents.h
@@ -23,11 +23,13 @@
 #include "android_webview/browser/render_thread_manager.h"
 #include "android_webview/browser/render_thread_manager_client.h"
 #include "android_webview/browser/renderer_host/aw_render_view_host_ext.h"
+#include "android_webview/native/aw_renderer_priority_manager.h"
 #include "android_webview/native/permission/permission_request_handler_client.h"
 #include "base/android/jni_weak_ref.h"
 #include "base/android/scoped_java_ref.h"
 #include "base/callback_forward.h"
 #include "base/macros.h"
+#include "content/public/browser/render_process_host_observer.h"
 #include "content/public/browser/web_contents_observer.h"
 
 class SkBitmap;
@@ -67,6 +69,7 @@
                    public AwBrowserPermissionRequestDelegate,
                    public AwRenderProcessGoneDelegate,
                    public content::WebContentsObserver,
+                   public content::RenderProcessHostObserver,
                    public AwSafeBrowsingUIManager::UIManagerClient {
  public:
   // Returns the AwContents instance associated with |web_contents|, or NULL.
@@ -213,6 +216,22 @@
       jboolean value,
       const base::android::JavaParamRef<jstring>& origin);
 
+  AwRendererPriorityManager::RendererPriority GetCurrentRendererPriority();
+  jint GetRendererCurrentPriority(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj);
+  jint GetRendererRequestedPriority(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj);
+  jboolean GetRendererPriorityWaivedWhenNotVisible(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj);
+  void SetRendererPriorityPolicy(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj,
+      jint rendererRequestedPriority,
+      jboolean waivedhenNotVisible);
+
   // PermissionRequestHandlerClient implementation.
   void OnPermissionRequest(base::android::ScopedJavaLocalRef<jobject> j_request,
                            AwPermissionRequest* request) override;
@@ -343,6 +362,9 @@
   void DidAttachInterstitialPage() override;
   void DidDetachInterstitialPage() override;
 
+  // content::RenderProcessHostObserver overrides
+  void RenderProcessReady(content::RenderProcessHost* host) override;
+
   // AwSafeBrowsingUIManager::UIManagerClient implementation
   bool CanShowInterstitial() override;
 
@@ -361,6 +383,12 @@
 
   void SetAwGLFunctor(AwGLFunctor* functor);
 
+  AwRendererPriorityManager* GetAwRendererPriorityManager();
+  AwRendererPriorityManager::RendererPriority GetComputedRendererPriority();
+  void UpdateRendererPriority(
+      AwRendererPriorityManager::RendererPriority base_priority);
+  void UpdateRendererPriority();
+
   JavaObjectWeakGlobalRef java_ref_;
   AwGLFunctor* functor_;
   BrowserViewRenderer browser_view_renderer_;  // Must outlive |web_contents_|.
@@ -377,12 +405,15 @@
   // GURL is supplied by the content layer as requesting frame.
   // Callback is supplied by the content layer, and is invoked with the result
   // from the permission prompt.
-  typedef std::pair<const GURL, base::Callback<void(bool)> > OriginCallback;
+  typedef std::pair<const GURL, base::Callback<void(bool)>> OriginCallback;
   // The first element in the list is always the currently pending request.
   std::list<OriginCallback> pending_geolocation_prompts_;
 
   GLViewRendererManager::Key renderer_manager_key_;
 
+  AwRendererPriorityManager::RendererPriority renderer_requested_priority_;
+  bool renderer_priority_waived_when_not_visible_;
+
   DISALLOW_COPY_AND_ASSIGN(AwContents);
 };
 
diff --git a/android_webview/native/aw_renderer_priority_manager.cc b/android_webview/native/aw_renderer_priority_manager.cc
new file mode 100644
index 0000000..c19f323
--- /dev/null
+++ b/android_webview/native/aw_renderer_priority_manager.cc
@@ -0,0 +1,42 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "android_webview/native/aw_renderer_priority_manager.h"
+
+#include "base/android/jni_android.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "jni/AwRendererPriorityManager_jni.h"
+
+namespace android_webview {
+
+AwRendererPriorityManager::AwRendererPriorityManager(
+    content::RenderProcessHost* host)
+    : host_(host), renderer_priority_(RENDERER_PRIORITY_INITIAL) {}
+
+void AwRendererPriorityManager::SetRendererPriority(
+    RendererPriority renderer_priority) {
+  if (host_->GetHandle() == 0) {
+    return;
+  }
+  if (renderer_priority_ != renderer_priority) {
+    renderer_priority_ = renderer_priority;
+    content::BrowserThread::PostTask(
+        content::BrowserThread::PROCESS_LAUNCHER, FROM_HERE,
+        base::Bind(&SetRendererPriorityOnLauncherThread, host_->GetHandle(),
+                   renderer_priority_));
+  }
+}
+
+// static
+void AwRendererPriorityManager::SetRendererPriorityOnLauncherThread(
+    int pid,
+    RendererPriority renderer_priority) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  DCHECK(env);
+  Java_AwRendererPriorityManager_setRendererPriority(
+      env, static_cast<jint>(pid), static_cast<jint>(renderer_priority));
+}
+
+}  // namespace android_webview
diff --git a/android_webview/native/aw_renderer_priority_manager.h b/android_webview/native/aw_renderer_priority_manager.h
new file mode 100644
index 0000000..dec22415
--- /dev/null
+++ b/android_webview/native/aw_renderer_priority_manager.h
@@ -0,0 +1,42 @@
+// 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 ANDROID_WEBVIEW_NATIVE_AW_RENDERER_PRIORITY_USER_DATA_H_
+#define ANDROID_WEBVIEW_NATIVE_AW_RENDERER_PRIORITY_USER_DATA_H_
+
+#include "base/supports_user_data.h"
+
+namespace content {
+class RenderProcessHost;
+}
+
+namespace android_webview {
+
+class AwRendererPriorityManager : public base::SupportsUserData::Data {
+ public:
+  // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.android_webview.renderer_priority
+  enum RendererPriority {
+    RENDERER_PRIORITY_INITIAL = -1,
+    RENDERER_PRIORITY_WAIVED = 0,
+    RENDERER_PRIORITY_LOW = 1,
+    RENDERER_PRIORITY_HIGH = 2
+  };
+
+  explicit AwRendererPriorityManager(content::RenderProcessHost* host);
+
+  RendererPriority GetRendererPriority() const { return renderer_priority_; }
+  void SetRendererPriority(RendererPriority renderer_priority);
+
+ private:
+  static void SetRendererPriorityOnLauncherThread(
+      int pid,
+      RendererPriority renderer_priority);
+
+  content::RenderProcessHost* host_;
+  RendererPriority renderer_priority_;
+};
+
+}  // namespace android_webview
+
+#endif  //  ANDROID_WEBVIEW_NATIVE_AW_RENDERER_PRIORITY_USER_DATA_H_
diff --git a/build/whitespace_file.txt b/build/whitespace_file.txt
index 5327d72..e9b2b0f 100644
--- a/build/whitespace_file.txt
+++ b/build/whitespace_file.txt
@@ -162,4 +162,4 @@
 No, really, I couldn't eat another bit.
 When I hunger I think of you, and a pastrami sandwich.
 Do make a terrible mistake every once in a while.
-I just made one.
+I just made two.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ChildNode.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ChildNode.java
index 9823c1e..79344e1f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ChildNode.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ChildNode.java
@@ -22,6 +22,13 @@
         mParent = parent;
     }
 
+    @Override
+    @CallSuper
+    public void detach() {
+        assert mParent != null;
+        mParent = null;
+    }
+
     protected void notifyItemRangeChanged(int index, int count, Object payload) {
         if (mParent != null) mParent.onItemRangeChanged(this, index, count, payload);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/InnerNode.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/InnerNode.java
index fcb153e..2b9d1f3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/InnerNode.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/InnerNode.java
@@ -158,6 +158,7 @@
         int count = child.getItemCount();
         int childStartingOffset = getStartingOffsetForChildIndex(removedIndex);
 
+        child.detach();
         mChildren.remove(removedIndex);
         if (count > 0) notifyItemRangeRemoved(childStartingOffset, count);
     }
@@ -169,6 +170,7 @@
         int itemCount = getItemCount();
         if (itemCount == 0) return;
 
+        for (TreeNode child : mChildren) child.detach();
         mChildren.clear();
         notifyItemRangeRemoved(0, itemCount);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SectionList.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SectionList.java
index da91464..4d090a9b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SectionList.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SectionList.java
@@ -6,6 +6,7 @@
 
 import org.chromium.base.Log;
 import org.chromium.base.VisibleForTesting;
+import org.chromium.chrome.browser.ntp.NewTabPage.DestructionObserver;
 import org.chromium.chrome.browser.ntp.snippets.CategoryInt;
 import org.chromium.chrome.browser.ntp.snippets.CategoryStatus;
 import org.chromium.chrome.browser.ntp.snippets.CategoryStatus.CategoryStatusEnum;
@@ -42,6 +43,13 @@
         mUiDelegate.getMetricsReporter().setRanker(mSuggestionsRanker);
         mOfflinePageBridge = offlinePageBridge;
         resetSections(/* alwaysAllowEmptySections = */ false);
+
+        mUiDelegate.addDestructionObserver(new DestructionObserver() {
+            @Override
+            public void onDestroy() {
+                removeAllSections();
+            }
+        });
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSection.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSection.java
index 850fec8..c9a636a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSection.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSection.java
@@ -4,9 +4,10 @@
 
 package org.chromium.chrome.browser.ntp.cards;
 
+import android.support.annotation.CallSuper;
+
 import org.chromium.base.Callback;
 import org.chromium.base.Log;
-import org.chromium.chrome.browser.ntp.NewTabPage.DestructionObserver;
 import org.chromium.chrome.browser.ntp.NewTabPageUma;
 import org.chromium.chrome.browser.ntp.snippets.CategoryInt;
 import org.chromium.chrome.browser.ntp.snippets.CategoryStatus.CategoryStatusEnum;
@@ -46,6 +47,7 @@
     private final Delegate mDelegate;
     private final SuggestionsCategoryInfo mCategoryInfo;
     private final OfflinePageBridge mOfflinePageBridge;
+    private final OfflinePageBridge.OfflinePageModelObserver mOfflinePageObserver;
 
     // Children
     private final SectionHeader mHeader;
@@ -54,8 +56,6 @@
     private final ActionItem mMoreButton;
     private final ProgressItem mProgressIndicator;
 
-    private boolean mIsNtpDestroyed;
-
     /**
      * Keeps track of how many suggestions have been seen by the user so that we replace only
      * suggestions that have not been seen, yet.
@@ -91,7 +91,34 @@
         mProgressIndicator = new ProgressItem();
         addChildren(mHeader, mSuggestionsList, mStatus, mMoreButton, mProgressIndicator);
 
-        setupOfflinePageBridgeObserver(uiDelegate);
+        mOfflinePageObserver =
+                new OfflinePageBridge.OfflinePageModelObserver() {
+                    @Override
+                    public void offlinePageModelLoaded() {
+                        updateAllSnippetOfflineAvailability();
+                    }
+
+                    @Override
+                    public void offlinePageAdded(OfflinePageItem addedPage) {
+                        updateAllSnippetOfflineAvailability();
+                    }
+
+                    @Override
+                    public void offlinePageDeleted(long offlineId, ClientId clientId) {
+                        for (SnippetArticle article : mSuggestionsList) {
+                            if (article.requiresExactOfflinePage()) continue;
+                            Long articleOfflineId = article.getOfflinePageOfflineId();
+                            if (articleOfflineId == null) continue;
+                            if (articleOfflineId.longValue() != offlineId) continue;
+                            // The old value cannot be simply removed without a request to the
+                            // model, because there may be an older offline page for the same
+                            // URL.
+                            updateSnippetOfflineAvailability(article);
+                        }
+                    }
+                };
+        mOfflinePageBridge.addObserver(mOfflinePageObserver);
+
         refreshChildrenVisibility();
     }
 
@@ -210,43 +237,11 @@
         }
     }
 
-    private void setupOfflinePageBridgeObserver(SuggestionsUiDelegate uiDelegate) {
-        final OfflinePageBridge.OfflinePageModelObserver observer =
-                new OfflinePageBridge.OfflinePageModelObserver() {
-                    @Override
-                    public void offlinePageModelLoaded() {
-                        updateAllSnippetOfflineAvailability();
-                    }
-
-                    @Override
-                    public void offlinePageAdded(OfflinePageItem addedPage) {
-                        updateAllSnippetOfflineAvailability();
-                    }
-
-                    @Override
-                    public void offlinePageDeleted(long offlineId, ClientId clientId) {
-                        for (SnippetArticle article : mSuggestionsList) {
-                            if (article.requiresExactOfflinePage()) continue;
-                            Long articleOfflineId = article.getOfflinePageOfflineId();
-                            if (articleOfflineId == null) continue;
-                            if (articleOfflineId.longValue() != offlineId) continue;
-                            // The old value cannot be simply removed without a request to the
-                            // model, because there may be an older offline page for the same
-                            // URL.
-                            updateSnippetOfflineAvailability(article);
-                        }
-                    }
-                };
-
-        mOfflinePageBridge.addObserver(observer);
-
-        uiDelegate.addDestructionObserver(new DestructionObserver() {
-            @Override
-            public void onDestroy() {
-                mIsNtpDestroyed = true;
-                mOfflinePageBridge.removeObserver(observer);
-            }
-        });
+    @Override
+    @CallSuper
+    public void detach() {
+        mOfflinePageBridge.removeObserver(mOfflinePageObserver);
+        super.detach();
     }
 
     private void refreshChildrenVisibility() {
@@ -406,7 +401,6 @@
                 article.mUrl, /*tabId=*/0, new Callback<OfflinePageItem>() {
                     @Override
                     public void onResult(OfflinePageItem item) {
-                        if (mIsNtpDestroyed) return;
                         mSuggestionsList.updateSuggestionOfflineId(
                                 article, item == null ? null : item.getOfflineId());
                     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/TreeNode.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/TreeNode.java
index 7a13f6f..ff7aef1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/TreeNode.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/TreeNode.java
@@ -21,6 +21,12 @@
     void setParent(NodeParent parent);
 
     /**
+     * Detaches the node from the parent so that changes in the node are no longer notified to the
+     * parent. This is needed when the parent removes this node from its children.
+     */
+    void detach();
+
+    /**
      * Returns the number of items under this subtree. This method may be called
      * before initialization.
      *
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/InnerNodeTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/InnerNodeTest.java
index 4d1596e..54ffe63 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/InnerNodeTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/InnerNodeTest.java
@@ -135,16 +135,27 @@
 
         // The parent should have been notified about the removed items.
         verify(mParent).onItemRangeRemoved(mInnerNode, 6, 3);
+        verify(child).detach();
 
         reset(mParent); // Prepare for the #verifyNoMoreInteractions() call below.
         TreeNode child2 = mChildren.get(3);
         mInnerNode.removeChild(child2);
+        verify(child2).detach();
 
         // There should be no change notifications about the empty child.
         verifyNoMoreInteractions(mParent);
     }
 
     @Test
+    public void testRemoveChildren() {
+        mInnerNode.removeChildren();
+
+        // The parent should have been notified about the removed items.
+        verify(mParent).onItemRangeRemoved(mInnerNode, 0, 12);
+        for (TreeNode child : mChildren) verify(child).detach();
+    }
+
+    @Test
     public void testNotifications() {
         mInnerNode.onItemRangeInserted(mChildren.get(0), 0, 23);
         mInnerNode.onItemRangeChanged(mChildren.get(2), 2, 9000, null);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SectionListTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SectionListTest.java
index e8d0cad..4b0155cb 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SectionListTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SectionListTest.java
@@ -5,8 +5,12 @@
 package org.chromium.chrome.browser.ntp.cards;
 
 import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import static org.chromium.chrome.browser.ntp.cards.ContentSuggestionsTestUtils.bindViewHolders;
@@ -17,6 +21,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.annotation.Config;
@@ -24,6 +29,7 @@
 import org.chromium.base.Callback;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.DisableHistogramsRule;
+import org.chromium.chrome.browser.ntp.NewTabPage.DestructionObserver;
 import org.chromium.chrome.browser.ntp.cards.ContentSuggestionsTestUtils.CategoryInfoBuilder;
 import org.chromium.chrome.browser.ntp.snippets.FakeSuggestionsSource;
 import org.chromium.chrome.browser.ntp.snippets.KnownCategories;
@@ -233,4 +239,30 @@
                            .getPerSectionRank(),
                 equalTo(3));
     }
+
+    @Test
+    @Feature({"Ntp"})
+    public void testRemovesSectionsWhenUiDelegateDestroyed() {
+        registerCategory(mSuggestionSource, KnownCategories.ARTICLES, 1);
+        registerCategory(mSuggestionSource,
+                new CategoryInfoBuilder(KnownCategories.DOWNLOADS).withViewAllAction().build(), 3);
+        SectionList sectionList = new SectionList(mUiDelegate, mOfflinePageBridge);
+        bindViewHolders(sectionList);
+
+        ArgumentCaptor<DestructionObserver> argument =
+                ArgumentCaptor.forClass(DestructionObserver.class);
+        verify(mUiDelegate).addDestructionObserver(argument.capture());
+
+        assertFalse(sectionList.isEmpty());
+        SuggestionsSection section = sectionList.getSectionForTesting(KnownCategories.ARTICLES);
+        assertNotNull(section);
+
+        // Now destroy the UI and thus notify the SectionList.
+        argument.getValue().onDestroy();
+        // The section should be removed.
+        assertTrue(sectionList.isEmpty());
+        // Verify that the section has been detached by notifying its parent about changes. If not
+        // detached, it should crash.
+        section.notifyItemRangeChanged(0, 1);
+    }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
index 4450d7f..7b13c4a 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
@@ -302,6 +302,36 @@
 
     @Test
     @Feature({"Ntp"})
+    public void testOfflineStatusIgnoredIfDetached() {
+        final int suggestionCount = 2;
+        final List<SnippetArticle> suggestions = createDummySuggestions(suggestionCount);
+        assertNull(suggestions.get(0).getOfflinePageOfflineId());
+        assertNull(suggestions.get(1).getOfflinePageOfflineId());
+
+        final OfflinePageItem item0 = createOfflinePageItem(suggestions.get(0).mUrl, 0L);
+        mBridge.setIsOfflinePageModelLoaded(true);
+        mBridge.setItems(Arrays.asList(item0));
+
+        SuggestionsSection section = createSectionWithSuggestions(suggestions);
+
+        // The offline status should propagate before detaching.
+        assertEquals(Long.valueOf(0L), suggestions.get(0).getOfflinePageOfflineId());
+        assertNull(suggestions.get(1).getOfflinePageOfflineId());
+
+        section.detach();
+
+        final OfflinePageItem item1 = createOfflinePageItem(suggestions.get(1).mUrl, 1L);
+        mBridge.setItems(Arrays.asList(item0, item1));
+        // Check that a change in OfflinePageBridge state forces an update.
+        mBridge.fireOfflinePageModelLoaded();
+
+        // The offline status should not change any more.
+        assertEquals(Long.valueOf(0L), suggestions.get(0).getOfflinePageOfflineId());
+        assertNull(suggestions.get(1).getOfflinePageOfflineId());
+    }
+
+    @Test
+    @Feature({"Ntp"})
     public void testViewAllActionPriority() {
         // When all the actions are enabled, ViewAll always has the priority and is shown.
 
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 33250439..443d291 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3673,6 +3673,8 @@
       "supervised_user/child_accounts/child_account_service_factory.h",
       "supervised_user/child_accounts/family_info_fetcher.cc",
       "supervised_user/child_accounts/family_info_fetcher.h",
+      "supervised_user/child_accounts/kids_management_api.cc",
+      "supervised_user/child_accounts/kids_management_api.h",
       "supervised_user/child_accounts/permission_request_creator_apiary.cc",
       "supervised_user/child_accounts/permission_request_creator_apiary.h",
       "supervised_user/experimental/safe_search_url_reporter.cc",
diff --git a/chrome/browser/chromeos/arc/fileapi/arc_documents_provider_root.cc b/chrome/browser/chromeos/arc/fileapi/arc_documents_provider_root.cc
index 1eb533f..8264bcad 100644
--- a/chrome/browser/chromeos/arc/fileapi/arc_documents_provider_root.cc
+++ b/chrome/browser/chromeos/arc/fileapi/arc_documents_provider_root.cc
@@ -18,7 +18,6 @@
 #include "chrome/browser/chromeos/arc/fileapi/arc_documents_provider_util.h"
 #include "chrome/browser/chromeos/arc/fileapi/arc_file_system_operation_runner_util.h"
 #include "content/public/browser/browser_thread.h"
-#include "net/base/mime_util.h"
 #include "url/gurl.h"
 
 using content::BrowserThread;
@@ -53,17 +52,13 @@
       base::ToLowerASCII(base::FilePath(filename).Extension());
   if (!extension.empty())
     extension = extension.substr(1);  // Strip the leading dot.
-  std::vector<base::FilePath::StringType> possible_extensions;
-  net::GetExtensionsForMimeType(document->mime_type, &possible_extensions);
+  std::vector<base::FilePath::StringType> possible_extensions =
+      GetExtensionsForArcMimeType(document->mime_type);
   if (!possible_extensions.empty() &&
       std::find(possible_extensions.begin(), possible_extensions.end(),
                 extension) == possible_extensions.end()) {
-    base::FilePath::StringType new_extension;
-    if (!net::GetPreferredExtensionForMimeType(document->mime_type,
-                                               &new_extension)) {
-      new_extension = possible_extensions[0];
-    }
-    filename = base::FilePath(filename).AddExtension(new_extension).value();
+    filename =
+        base::FilePath(filename).AddExtension(possible_extensions[0]).value();
   }
 
   return filename;
diff --git a/chrome/browser/chromeos/arc/fileapi/arc_documents_provider_util.cc b/chrome/browser/chromeos/arc/fileapi/arc_documents_provider_util.cc
index 26c3db5..488265b 100644
--- a/chrome/browser/chromeos/arc/fileapi/arc_documents_provider_util.cc
+++ b/chrome/browser/chromeos/arc/fileapi/arc_documents_provider_util.cc
@@ -4,16 +4,92 @@
 
 #include "chrome/browser/chromeos/arc/fileapi/arc_documents_provider_util.h"
 
-#include <vector>
+#include <algorithm>
+#include <utility>
 
+#include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "net/base/escape.h"
+#include "net/base/mime_util.h"
 #include "storage/browser/fileapi/file_system_url.h"
 #include "url/gurl.h"
 
 namespace arc {
 
+namespace {
+
+struct MimeTypeToExtensions {
+  const char* mime_type;
+  const char* extensions;
+};
+
+// The mapping from MIME types to file name extensions, taken from Android N.
+// See: frameworks/base/media/java/android/media/MediaFile.java
+constexpr MimeTypeToExtensions kAndroidMimeTypeMappings[] = {
+    {"application/mspowerpoint", "ppt"},
+    {"application/msword", "doc"},
+    {"application/ogg", "ogg,oga"},
+    {"application/pdf", "pdf"},
+    {"application/vnd.apple.mpegurl", "m3u8"},
+    {"application/vnd.ms-excel", "xls"},
+    {"application/vnd.ms-wpl", "wpl"},
+    {"application/x-android-drm-fl", "fl"},
+    {"application/x-mpegurl", "m3u"},
+    {"application/zip", "zip"},
+    {"audio/aac", "aac"},
+    {"audio/aac-adts", "aac"},
+    {"audio/amr", "amr"},
+    {"audio/amr-wb", "awb"},
+    {"audio/flac", "flac"},
+    {"audio/imelody", "imy"},
+    {"audio/midi", "mid,midi,xmf,rtttl,rtx,ota,mxmf"},
+    {"audio/mp4", "m4a"},
+    {"audio/mpeg", "mp3,mpga"},
+    {"audio/mpegurl", "m3u8"},
+    {"audio/ogg", "ogg"},
+    {"audio/sp-midi", "smf"},
+    {"audio/x-matroska", "mka"},
+    {"audio/x-mpegurl", "m3u,m3u8"},
+    {"audio/x-ms-wma", "wma"},
+    {"audio/x-scpls", "pls"},
+    {"audio/x-wav", "wav"},
+    {"image/gif", "gif"},
+    {"image/jpeg", "jpg,jpeg"},
+    {"image/png", "png"},
+    {"image/vnd.wap.wbmp", "wbmp"},
+    {"image/webp", "webp"},
+    {"image/x-adobe-dng", "dng"},
+    {"image/x-canon-cr2", "cr2"},
+    {"image/x-fuji-raf", "raf"},
+    {"image/x-ms-bmp", "bmp"},
+    {"image/x-nikon-nef", "nef"},
+    {"image/x-nikon-nrw", "nrw"},
+    {"image/x-olympus-orf", "orf"},
+    {"image/x-panasonic-rw2", "rw2"},
+    {"image/x-pentax-pef", "pef"},
+    {"image/x-samsung-srw", "srw"},
+    {"image/x-sony-arw", "arw"},
+    {"text/html", "html,htm"},
+    {"text/plain", "txt"},
+    {"video/3gpp", "3gp,3gpp"},
+    {"video/3gpp2", "3g2,3gpp2"},
+    {"video/avi", "avi"},
+    {"video/mp2p", "mpg,mpeg"},
+    {"video/mp2ts", "ts"},
+    {"video/mp4", "mp4,m4v"},
+    {"video/mpeg", "mpg,mpeg"},
+    {"video/quicktime", "mov"},
+    {"video/webm", "webm"},
+    {"video/x-matroska", "mkv"},
+    {"video/x-ms-asf", "asf"},
+    {"video/x-ms-wmv", "wmv"},
+};
+
+constexpr char kApplicationOctetStreamMimeType[] = "application/octet-stream";
+
+}  // namespace
+
 // This is based on net/base/escape.cc: net::(anonymous namespace)::Escape.
 // TODO(nya): Consider consolidating this function with EscapeFileSystemId() in
 // chrome/browser/chromeos/file_system_provider/mount_path_util.cc.
@@ -99,4 +175,45 @@
       net::EscapeQueryParamValue(document_id, false /* use_plus */).c_str()));
 }
 
+std::vector<base::FilePath::StringType> GetExtensionsForArcMimeType(
+    const std::string& mime_type) {
+  // net::GetExtensionsForMimeType() returns unwanted extensions like
+  // "exe,com,bin" for application/octet-stream.
+  if (net::MatchesMimeType(kApplicationOctetStreamMimeType, mime_type))
+    return std::vector<base::FilePath::StringType>();
+
+  // Attempt net::GetExtensionsForMimeType().
+  {
+    std::vector<base::FilePath::StringType> extensions;
+    net::GetExtensionsForMimeType(mime_type, &extensions);
+    if (!extensions.empty()) {
+      base::FilePath::StringType preferred_extension;
+      if (net::GetPreferredExtensionForMimeType(mime_type,
+                                                &preferred_extension)) {
+        auto iter = std::find(extensions.begin(), extensions.end(),
+                              preferred_extension);
+        if (iter == extensions.end()) {
+          // This is unlikely to happen, but there is no guarantee.
+          extensions.insert(extensions.begin(), preferred_extension);
+        } else {
+          std::swap(extensions.front(), *iter);
+        }
+      }
+      return extensions;
+    }
+  }
+
+  // Fallback to our hard-coded list.
+  for (const auto& entry : kAndroidMimeTypeMappings) {
+    if (net::MatchesMimeType(entry.mime_type, mime_type)) {
+      // We assume base::FilePath::StringType == std::string as this code is
+      // built only on Chrome OS.
+      return base::SplitString(entry.extensions, ",", base::TRIM_WHITESPACE,
+                               base::SPLIT_WANT_ALL);
+    }
+  }
+
+  return std::vector<base::FilePath::StringType>();
+}
+
 }  // namespace arc
diff --git a/chrome/browser/chromeos/arc/fileapi/arc_documents_provider_util.h b/chrome/browser/chromeos/arc/fileapi/arc_documents_provider_util.h
index 1ba3583..4f130dde 100644
--- a/chrome/browser/chromeos/arc/fileapi/arc_documents_provider_util.h
+++ b/chrome/browser/chromeos/arc/fileapi/arc_documents_provider_util.h
@@ -8,6 +8,7 @@
 #define CHROME_BROWSER_CHROMEOS_ARC_FILEAPI_ARC_DOCUMENTS_PROVIDER_UTIL_H_
 
 #include <string>
+#include <vector>
 
 #include "base/files/file_path.h"
 
@@ -61,6 +62,14 @@
 GURL BuildDocumentUrl(const std::string& authority,
                       const std::string& document_id);
 
+// Similar to net::GetExtensionsForMimeType(), but this covers more MIME types
+// used in Android.
+// Returns an empty vector if the MIME type is not known.
+// If the returned vector is not empty, the first extension is the preferred
+// extension.
+std::vector<base::FilePath::StringType> GetExtensionsForArcMimeType(
+    const std::string& mime_type);
+
 }  // namespace arc
 
 #endif  // CHROME_BROWSER_CHROMEOS_ARC_FILEAPI_ARC_DOCUMENTS_PROVIDER_UTIL_H_
diff --git a/chrome/browser/chromeos/arc/fileapi/arc_documents_provider_util_unittest.cc b/chrome/browser/chromeos/arc/fileapi/arc_documents_provider_util_unittest.cc
index 496bdd4e..82145ab3 100644
--- a/chrome/browser/chromeos/arc/fileapi/arc_documents_provider_util_unittest.cc
+++ b/chrome/browser/chromeos/arc/fileapi/arc_documents_provider_util_unittest.cc
@@ -199,6 +199,28 @@
   EXPECT_EQ("content://../document/..", BuildDocumentUrl("..", "..").spec());
 }
 
+TEST(ArcDocumentsProviderUtilTest, GetExtensionsForArcMimeType) {
+  // MIME types already known to Chromium.
+  EXPECT_NE(0u, GetExtensionsForArcMimeType("audio/mp3").size());
+  EXPECT_NE(0u, GetExtensionsForArcMimeType("image/jpeg").size());
+  EXPECT_NE(0u, GetExtensionsForArcMimeType("text/html").size());
+  EXPECT_NE(
+      0u, GetExtensionsForArcMimeType("application/x-chrome-extension").size());
+
+  // MIME types known to Android only.
+  EXPECT_NE(0u,
+            GetExtensionsForArcMimeType("application/x-android-drm-fl").size());
+  EXPECT_NE(0u, GetExtensionsForArcMimeType("audio/x-wav").size());
+
+  // Unknown types.
+  EXPECT_EQ(0u, GetExtensionsForArcMimeType("abc/xyz").size());
+  EXPECT_EQ(
+      0u, GetExtensionsForArcMimeType("vnd.android.document/directory").size());
+
+  // Specially handled types.
+  EXPECT_EQ(0u, GetExtensionsForArcMimeType("application/octet-stream").size());
+}
+
 }  // namespace
 
 }  // namespace arc
diff --git a/chrome/browser/password_manager/native_backend_gnome_x.cc b/chrome/browser/password_manager/native_backend_gnome_x.cc
index 9dcf475..a51f1d3 100644
--- a/chrome/browser/password_manager/native_backend_gnome_x.cc
+++ b/chrome/browser/password_manager/native_backend_gnome_x.cc
@@ -31,6 +31,7 @@
 using base::UTF16ToUTF8;
 using content::BrowserThread;
 using namespace password_manager::metrics_util;
+using password_manager::MatchResult;
 using password_manager::PasswordStore;
 
 namespace {
@@ -121,45 +122,48 @@
   std::vector<std::unique_ptr<PasswordForm>> forms;
   password_manager::PSLDomainMatchMetric psl_domain_match_metric =
       password_manager::PSL_DOMAIN_MATCH_NONE;
-  const bool allow_psl_match =
-      lookup_form && password_manager::ShouldPSLDomainMatchingApply(
-                         password_manager::GetRegistryControlledDomain(
-                             GURL(lookup_form->signon_realm)));
   for (GList* element = g_list_first(found); element;
        element = g_list_next(element)) {
     GnomeKeyringFound* data = static_cast<GnomeKeyringFound*>(element->data);
     GnomeKeyringAttributeList* attrs = data->attributes;
 
     std::unique_ptr<PasswordForm> form(FormFromAttributes(attrs));
-    if (form) {
-      if (lookup_form && form->signon_realm != lookup_form->signon_realm) {
-        if (lookup_form->scheme != PasswordForm::SCHEME_HTML ||
-            form->scheme != PasswordForm::SCHEME_HTML)
-          continue;  // Ignore non-HTML matches.
-        // This is not an exact match, we try PSL matching and federated match.
-        if (allow_psl_match &&
-            password_manager::IsPublicSuffixDomainMatch(
-                form->signon_realm, lookup_form->signon_realm)) {
+    if (!form) {
+      LOG(WARNING) << "Could not initialize PasswordForm from attributes!";
+      continue;
+    }
+
+    if (lookup_form) {
+      switch (GetMatchResult(*form, *lookup_form)) {
+        case MatchResult::NO_MATCH:
+          continue;
+        case MatchResult::EXACT_MATCH:
+          break;
+        case MatchResult::PSL_MATCH:
           psl_domain_match_metric = password_manager::PSL_DOMAIN_MATCH_FOUND;
           form->is_public_suffix_match = true;
-        } else if (!form->federation_origin.unique() &&
-                   password_manager::IsFederatedMatch(form->signon_realm,
-                                                      lookup_form->origin)) {
-        } else {
-          continue;
-        }
+          break;
+        case MatchResult::FEDERATED_MATCH:
+          break;
+        case MatchResult::FEDERATED_PSL_MATCH:
+          psl_domain_match_metric =
+              password_manager::PSL_DOMAIN_MATCH_FOUND_FEDERATED;
+          form->is_public_suffix_match = true;
+          break;
       }
-      if (data->secret) {
-        form->password_value = UTF8ToUTF16(data->secret);
-      } else {
-        LOG(WARNING) << "Unable to access password from list element!";
-      }
-      forms.push_back(std::move(form));
-    } else {
-      LOG(WARNING) << "Could not initialize PasswordForm from attributes!";
     }
+
+    if (data->secret) {
+      form->password_value = UTF8ToUTF16(data->secret);
+    } else {
+      LOG(WARNING) << "Unable to access password from list element!";
+    }
+    forms.push_back(std::move(form));
   }
   if (lookup_form) {
+    const bool allow_psl_match = password_manager::ShouldPSLDomainMatchingApply(
+        password_manager::GetRegistryControlledDomain(
+            GURL(lookup_form->signon_realm)));
     UMA_HISTOGRAM_ENUMERATION("PasswordManager.PslDomainMatchTriggering",
                               allow_psl_match
                                   ? psl_domain_match_metric
diff --git a/chrome/browser/password_manager/native_backend_gnome_x_unittest.cc b/chrome/browser/password_manager/native_backend_gnome_x_unittest.cc
index e62dc9b..7080a52 100644
--- a/chrome/browser/password_manager/native_backend_gnome_x_unittest.cc
+++ b/chrome/browser/password_manager/native_backend_gnome_x_unittest.cc
@@ -894,6 +894,22 @@
                                           PasswordForm::SCHEME_HTML, nullptr));
 }
 
+TEST_F(NativeBackendGnomeTest, FetchPSLMatchedFederatedCredentialOnHTTPS) {
+  other_auth_.signon_realm = "federation://www.sub.example.com/google.com";
+  other_auth_.federation_origin = url::Origin(GURL("https://google.com/"));
+  EXPECT_TRUE(CheckCredentialAvailability(other_auth_,
+                                          GURL("https://www.example.com/"),
+                                          PasswordForm::SCHEME_HTML, nullptr));
+}
+
+TEST_F(NativeBackendGnomeTest, DontFetchPSLMatchedFederatedCredentialOnHTTP) {
+  other_auth_.signon_realm = "federation://www.sub.example.com/google.com";
+  other_auth_.federation_origin = url::Origin(GURL("https://google.com/"));
+  EXPECT_FALSE(CheckCredentialAvailability(other_auth_,
+                                           GURL("http://www.example.com/"),
+                                           PasswordForm::SCHEME_HTML, nullptr));
+}
+
 TEST_F(NativeBackendGnomeTest, BasicUpdateLogin) {
   NativeBackendGnome backend(42);
   backend.Init();
diff --git a/chrome/browser/password_manager/native_backend_libsecret.cc b/chrome/browser/password_manager/native_backend_libsecret.cc
index dd21a65..1a6a99ab 100644
--- a/chrome/browser/password_manager/native_backend_libsecret.cc
+++ b/chrome/browser/password_manager/native_backend_libsecret.cc
@@ -27,6 +27,7 @@
 using autofill::PasswordForm;
 using base::UTF8ToUTF16;
 using base::UTF16ToUTF8;
+using password_manager::MatchResult;
 using password_manager::PasswordStore;
 
 namespace {
@@ -516,10 +517,6 @@
   password_manager::PSLDomainMatchMetric psl_domain_match_metric =
       password_manager::PSL_DOMAIN_MATCH_NONE;
   GError* error = nullptr;
-  const bool allow_psl_match =
-      lookup_form && password_manager::ShouldPSLDomainMatchingApply(
-                         password_manager::GetRegistryControlledDomain(
-                             GURL(lookup_form->signon_realm)));
   for (GList* element = g_list_first(found); element != nullptr;
        element = g_list_next(element)) {
     SecretItem* secretItem = static_cast<SecretItem*>(element->data);
@@ -533,40 +530,47 @@
     GHashTable* attrs = LibsecretLoader::secret_item_get_attributes(secretItem);
     std::unique_ptr<PasswordForm> form(FormOutOfAttributes(attrs));
     g_hash_table_unref(attrs);
-    if (form) {
-      if (lookup_form && form->signon_realm != lookup_form->signon_realm) {
-        if (lookup_form->scheme != PasswordForm::SCHEME_HTML ||
-            form->scheme != PasswordForm::SCHEME_HTML)
+    if (!form) {
+      VLOG(1) << "Could not initialize PasswordForm from attributes!";
+      continue;
+    }
+
+    if (lookup_form) {
+      switch (GetMatchResult(*form, *lookup_form)) {
+        case MatchResult::NO_MATCH:
           continue;
-        // This is not an exact match, we try PSL matching and federated match.
-        if (allow_psl_match &&
-            password_manager::IsPublicSuffixDomainMatch(
-                form->signon_realm, lookup_form->signon_realm)) {
+        case MatchResult::EXACT_MATCH:
+          break;
+        case MatchResult::PSL_MATCH:
           psl_domain_match_metric = password_manager::PSL_DOMAIN_MATCH_FOUND;
           form->is_public_suffix_match = true;
-        } else if (!form->federation_origin.unique() &&
-                   password_manager::IsFederatedMatch(form->signon_realm,
-                                                      lookup_form->origin)) {
-        } else {
-          continue;
-        }
+          break;
+        case MatchResult::FEDERATED_MATCH:
+          break;
+        case MatchResult::FEDERATED_PSL_MATCH:
+          psl_domain_match_metric =
+              password_manager::PSL_DOMAIN_MATCH_FOUND_FEDERATED;
+          form->is_public_suffix_match = true;
+          break;
       }
-      SecretValue* secretValue =
-          LibsecretLoader::secret_item_get_secret(secretItem);
-      if (secretValue) {
-        form->password_value =
-            UTF8ToUTF16(LibsecretLoader::secret_value_get_text(secretValue));
-        LibsecretLoader::secret_value_unref(secretValue);
-      } else {
-        LOG(WARNING) << "Unable to access password from list element!";
-      }
-      forms.push_back(std::move(form));
-    } else {
-      VLOG(1) << "Could not initialize PasswordForm from attributes!";
     }
+
+    SecretValue* secretValue =
+        LibsecretLoader::secret_item_get_secret(secretItem);
+    if (secretValue) {
+      form->password_value =
+          UTF8ToUTF16(LibsecretLoader::secret_value_get_text(secretValue));
+      LibsecretLoader::secret_value_unref(secretValue);
+    } else {
+      LOG(WARNING) << "Unable to access password from list element!";
+    }
+    forms.push_back(std::move(form));
   }
 
   if (lookup_form) {
+    const bool allow_psl_match = password_manager::ShouldPSLDomainMatchingApply(
+        password_manager::GetRegistryControlledDomain(
+            GURL(lookup_form->signon_realm)));
     UMA_HISTOGRAM_ENUMERATION("PasswordManager.PslDomainMatchTriggering",
                               allow_psl_match
                                   ? psl_domain_match_metric
diff --git a/chrome/browser/password_manager/native_backend_libsecret_unittest.cc b/chrome/browser/password_manager/native_backend_libsecret_unittest.cc
index 6572194..5c3515e 100644
--- a/chrome/browser/password_manager/native_backend_libsecret_unittest.cc
+++ b/chrome/browser/password_manager/native_backend_libsecret_unittest.cc
@@ -701,6 +701,23 @@
                                           PasswordForm::SCHEME_HTML, nullptr));
 }
 
+TEST_F(NativeBackendLibsecretTest, FetchPSLMatchedFederatedCredentialOnHTTPS) {
+  other_auth_.signon_realm = "federation://www.example.com/google.com";
+  other_auth_.federation_origin = url::Origin(GURL("https://google.com/"));
+  EXPECT_TRUE(CheckCredentialAvailability(other_auth_,
+                                          GURL("https://www.example.com/"),
+                                          PasswordForm::SCHEME_HTML, nullptr));
+}
+
+TEST_F(NativeBackendLibsecretTest,
+       DontFetchPSLMatchedFederatedCredentialOnHTTP) {
+  other_auth_.signon_realm = "federation://www.example.com/google.com";
+  other_auth_.federation_origin = url::Origin(GURL("https://google.com/"));
+  EXPECT_TRUE(CheckCredentialAvailability(other_auth_,
+                                          GURL("http://www.example.com/"),
+                                          PasswordForm::SCHEME_HTML, nullptr));
+}
+
 TEST_F(NativeBackendLibsecretTest, BasicUpdateLogin) {
   NativeBackendLibsecret backend(42);
 
diff --git a/chrome/browser/predictors/resource_prefetch_predictor.cc b/chrome/browser/predictors/resource_prefetch_predictor.cc
index 2da784f0..67dd25a 100644
--- a/chrome/browser/predictors/resource_prefetch_predictor.cc
+++ b/chrome/browser/predictors/resource_prefetch_predictor.cc
@@ -736,11 +736,6 @@
   url_redirect_table_cache_ = std::move(url_redirect_data_map);
   host_redirect_table_cache_ = std::move(host_redirect_data_map);
 
-  UMA_HISTOGRAM_COUNTS("ResourcePrefetchPredictor.UrlTableMainFrameUrlCount",
-                       url_table_cache_->size());
-  UMA_HISTOGRAM_COUNTS("ResourcePrefetchPredictor.HostTableHostCount",
-                       host_table_cache_->size());
-
   ConnectToHistoryService();
 }
 
diff --git a/chrome/browser/predictors/resource_prefetch_predictor_tables.cc b/chrome/browser/predictors/resource_prefetch_predictor_tables.cc
index f5be8ec..7d5515e 100644
--- a/chrome/browser/predictors/resource_prefetch_predictor_tables.cc
+++ b/chrome/browser/predictors/resource_prefetch_predictor_tables.cc
@@ -451,14 +451,14 @@
       base::StringPrintf("SELECT count(*) FROM %s", kUrlResourceTableName)
           .c_str()));
   if (statement.Step())
-    UMA_HISTOGRAM_COUNTS("ResourcePrefetchPredictor.UrlTableRowCount",
+    UMA_HISTOGRAM_COUNTS("ResourcePrefetchPredictor.UrlTableRowCount2",
                          statement.ColumnInt(0));
 
   statement.Assign(DB()->GetUniqueStatement(
       base::StringPrintf("SELECT count(*) FROM %s", kHostResourceTableName)
           .c_str()));
   if (statement.Step())
-    UMA_HISTOGRAM_COUNTS("ResourcePrefetchPredictor.HostTableRowCount",
+    UMA_HISTOGRAM_COUNTS("ResourcePrefetchPredictor.HostTableRowCount2",
                          statement.ColumnInt(0));
 }
 
diff --git a/chrome/browser/resources/chromeos/login/gaia_input.css b/chrome/browser/resources/chromeos/login/gaia_input.css
index de31266..af1bfc4 100644
--- a/chrome/browser/resources/chromeos/login/gaia_input.css
+++ b/chrome/browser/resources/chromeos/login/gaia_input.css
@@ -14,7 +14,6 @@
   };
   --paper-input-container-input-color: rgba(0, 0, 0, 0.87);
   --paper-input-container-label: {
-    color: rgba(0, 0, 0, 0.54);
     font-size: 15px;
   };
 }
@@ -23,6 +22,10 @@
   padding: 0;
 }
 
+paper-input-container > label > span {
+  color: rgba(0, 0, 0, 0.54);
+}
+
 :host-context(html[dir=rtl]) input {
   flex-direction: row-reverse;
 }
diff --git a/chrome/browser/resources/chromeos/login/offline_ad_login.js b/chrome/browser/resources/chromeos/login/offline_ad_login.js
index f722ecf..6c202bc 100644
--- a/chrome/browser/resources/chromeos/login/offline_ad_login.js
+++ b/chrome/browser/resources/chromeos/login/offline_ad_login.js
@@ -124,6 +124,8 @@
   onSubmit_: function() {
     if (this.showMachineInput && !this.$.machineNameInput.checkValidity())
       return;
+    if (!this.$.userInput.checkValidity())
+      return;
     if (!this.$.passwordInput.checkValidity())
       return;
     var user = /** @type {string} */ (this.$.userInput.value);
diff --git a/chrome/browser/supervised_user/child_accounts/family_info_fetcher.cc b/chrome/browser/supervised_user/child_accounts/family_info_fetcher.cc
index 3f20b35ec..28fbabb7 100644
--- a/chrome/browser/supervised_user/child_accounts/family_info_fetcher.cc
+++ b/chrome/browser/supervised_user/child_accounts/family_info_fetcher.cc
@@ -10,15 +10,15 @@
 #include "base/macros.h"
 #include "base/strings/stringprintf.h"
 #include "base/values.h"
+#include "chrome/browser/supervised_user/child_accounts/kids_management_api.h"
 #include "components/data_use_measurement/core/data_use_user_data.h"
 #include "net/base/load_flags.h"
 #include "net/http/http_status_code.h"
 #include "net/url_request/url_request_status.h"
 #include "url/gurl.h"
 
-const char kFamilyApiUrl[] = "https://www.googleapis.com/kidsmanagement/v1/";
-const char kGetFamilyProfileApiSuffix[] = "families/mine?alt=json";
-const char kGetFamilyMembersApiSuffix[] = "families/mine/members?alt=json";
+const char kGetFamilyProfileApiPath[] = "families/mine?alt=json";
+const char kGetFamilyMembersApiPath[] = "families/mine/members?alt=json";
 const char kScope[] = "https://www.googleapis.com/auth/kid.family.readonly";
 const char kAuthorizationHeaderFormat[] = "Authorization: Bearer %s";
 const int kNumRetries = 1;
@@ -89,7 +89,6 @@
       account_id_(account_id),
       token_service_(token_service),
       request_context_(request_context),
-      request_type_(net::URLFetcher::GET),
       access_token_expired_(false) {
 }
 
@@ -118,14 +117,12 @@
 }
 
 void FamilyInfoFetcher::StartGetFamilyProfile() {
-  request_suffix_ = kGetFamilyProfileApiSuffix;
-  request_type_ = net::URLFetcher::GET;
+  request_path_ = kGetFamilyProfileApiPath;
   StartFetching();
 }
 
 void FamilyInfoFetcher::StartGetFamilyMembers() {
-  request_suffix_ = kGetFamilyMembersApiSuffix;
-  request_type_ = net::URLFetcher::GET;
+  request_path_ = kGetFamilyMembersApiPath;
   StartFetching();
 }
 
@@ -141,8 +138,8 @@
 void FamilyInfoFetcher::StartFetchingAccessToken() {
   OAuth2TokenService::ScopeSet scopes;
   scopes.insert(kScope);
-  access_token_request_ = token_service_->StartRequest(
-    account_id_, scopes, this);
+  access_token_request_ =
+      token_service_->StartRequest(account_id_, scopes, this);
 }
 
 void FamilyInfoFetcher::OnRefreshTokenAvailable(
@@ -172,9 +169,9 @@
   DCHECK_EQ(access_token_request_.get(), request);
   access_token_ = access_token;
 
-  GURL url(kFamilyApiUrl + request_suffix_);
+  GURL url = kids_management_api::GetURL(request_path_);
   const int id = 0;
-  url_fetcher_ = net::URLFetcher::Create(id, url, request_type_, this);
+  url_fetcher_ = net::URLFetcher::Create(id, url, net::URLFetcher::GET, this);
 
   data_use_measurement::DataUseUserData::AttachToFetcher(
       url_fetcher_.get(),
@@ -226,9 +223,9 @@
   std::string response_body;
   source->GetResponseAsString(&response_body);
 
-  if (request_suffix_ == kGetFamilyProfileApiSuffix) {
+  if (request_path_ == kGetFamilyProfileApiPath) {
     FamilyProfileFetched(response_body);
-  } else if (request_suffix_ == kGetFamilyMembersApiSuffix) {
+  } else if (request_path_ == kGetFamilyMembersApiPath) {
     FamilyMembersFetched(response_body);
   } else {
     NOTREACHED();
diff --git a/chrome/browser/supervised_user/child_accounts/family_info_fetcher.h b/chrome/browser/supervised_user/child_accounts/family_info_fetcher.h
index a2e61c0..2adb0b06 100644
--- a/chrome/browser/supervised_user/child_accounts/family_info_fetcher.h
+++ b/chrome/browser/supervised_user/child_accounts/family_info_fetcher.h
@@ -25,6 +25,9 @@
 class URLRequestContextGetter;
 }
 
+// Fetches information about the family of the signed-in user. It can get
+// information about the family itself (e.g. a name), as well as a list of
+// family members and their properties.
 class FamilyInfoFetcher : public OAuth2TokenService::Observer,
                           public OAuth2TokenService::Consumer,
                           public net::URLFetcherDelegate {
@@ -34,6 +37,8 @@
     NETWORK_ERROR,  // Network failure.
     SERVICE_ERROR,  // Service returned an error or malformed reply.
   };
+  // Note: If you add or update an entry, also update |kFamilyMemberRoleStrings|
+  // in the .cc file.
   enum FamilyMemberRole {
     HEAD_OF_HOUSEHOLD = 0,
     PARENT,
@@ -74,6 +79,8 @@
     virtual void OnFailure(ErrorCode error) {}
   };
 
+  // Instantiates a fetcher, but doesn't start a fetch - use the StartGet*
+  // methods below. |consumer| must outlive us.
   FamilyInfoFetcher(Consumer* consumer,
                     const std::string& account_id,
                     OAuth2TokenService* token_service,
@@ -84,6 +91,8 @@
   static std::string RoleToString(FamilyMemberRole role);
   static bool StringToRole(const std::string& str, FamilyMemberRole* role);
 
+  // Start a fetch for the family profile or members.
+  // Note: Only one fetch is supported at a time.
   void StartGetFamilyProfile();
   void StartGetFamilyMembers();
 
@@ -119,8 +128,7 @@
   OAuth2TokenService* token_service_;
   net::URLRequestContextGetter* request_context_;
 
-  std::string request_suffix_;
-  net::URLFetcher::RequestType request_type_;
+  std::string request_path_;
   std::unique_ptr<OAuth2TokenService::Request> access_token_request_;
   std::string access_token_;
   bool access_token_expired_;
@@ -130,4 +138,3 @@
 };
 
 #endif  // CHROME_BROWSER_SUPERVISED_USER_CHILD_ACCOUNTS_FAMILY_INFO_FETCHER_H_
-
diff --git a/chrome/browser/supervised_user/child_accounts/kids_management_api.cc b/chrome/browser/supervised_user/child_accounts/kids_management_api.cc
new file mode 100644
index 0000000..f22f716
--- /dev/null
+++ b/chrome/browser/supervised_user/child_accounts/kids_management_api.cc
@@ -0,0 +1,41 @@
+// 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 "chrome/browser/supervised_user/child_accounts/kids_management_api.h"
+
+#include "base/feature_list.h"
+#include "components/variations/variations_associated_data.h"
+#include "url/gurl.h"
+
+namespace kids_management_api {
+
+namespace {
+
+const char kDefaultBaseURL[] =
+    "https://kidsmanagement-pa.googleapis.com/kidsmanagement/v1/";
+
+// A dummy feature that can be used to specify a variation param that overrides
+// the default API URL.
+const base::Feature kKidsManagementAPIFeature{
+    "KidsManagementAPI", base::FEATURE_DISABLED_BY_DEFAULT};
+
+const char kURLParamName[] = "kids_management_api_url";
+
+}  // namespace
+
+GURL GetBaseURL() {
+  // If the parameter isn't set or the feature is disabled, this will return
+  // the empty string, resulting in an invalid URL.
+  GURL url(variations::GetVariationParamValueByFeature(
+      kKidsManagementAPIFeature, kURLParamName));
+  if (url.is_valid())
+    return url;
+  return GURL(kDefaultBaseURL);
+}
+
+GURL GetURL(const std::string& path) {
+  return GetBaseURL().Resolve(path);
+}
+
+}  // namespace kids_management_api
diff --git a/chrome/browser/supervised_user/child_accounts/kids_management_api.h b/chrome/browser/supervised_user/child_accounts/kids_management_api.h
new file mode 100644
index 0000000..927077b8
--- /dev/null
+++ b/chrome/browser/supervised_user/child_accounts/kids_management_api.h
@@ -0,0 +1,20 @@
+// 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 CHROME_BROWSER_SUPERVISED_USER_CHILD_ACCOUNTS_KIDS_MANAGEMENT_API_H_
+#define CHROME_BROWSER_SUPERVISED_USER_CHILD_ACCOUNTS_KIDS_MANAGEMENT_API_H_
+
+#include <string>
+
+class GURL;
+
+namespace kids_management_api {
+
+GURL GetBaseURL();
+
+GURL GetURL(const std::string& path);
+
+}  // namespace kids_management_api
+
+#endif  // CHROME_BROWSER_SUPERVISED_USER_CHILD_ACCOUNTS_KIDS_MANAGEMENT_API_H_
diff --git a/chrome/browser/supervised_user/child_accounts/permission_request_creator_apiary.cc b/chrome/browser/supervised_user/child_accounts/permission_request_creator_apiary.cc
index d28092d..f887bb5 100644
--- a/chrome/browser/supervised_user/child_accounts/permission_request_creator_apiary.cc
+++ b/chrome/browser/supervised_user/child_accounts/permission_request_creator_apiary.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
 #include "chrome/browser/signin/signin_manager_factory.h"
+#include "chrome/browser/supervised_user/child_accounts/kids_management_api.h"
 #include "chrome/common/chrome_switches.h"
 #include "components/data_use_measurement/core/data_use_user_data.h"
 #include "components/signin/core/browser/profile_oauth2_token_service.h"
@@ -30,8 +31,7 @@
 
 using net::URLFetcher;
 
-const char kApiUrl[] =
-    "https://www.googleapis.com/kidsmanagement/v1/people/me/permissionRequests";
+const char kApiPath[] = "people/me/permissionRequests";
 const char kApiScope[] = "https://www.googleapis.com/auth/kid.permission";
 
 const int kNumRetries = 1;
@@ -138,9 +138,9 @@
     LOG_IF(WARNING, !url.is_valid())
         << "Got invalid URL for " << switches::kPermissionRequestApiUrl;
     return url;
-  } else {
-    return GURL(kApiUrl);
   }
+
+  return kids_management_api::GetURL(kApiPath);
 }
 
 std::string PermissionRequestCreatorApiary::GetApiScope() const {
diff --git a/chromeos/chromeos.gyp b/chromeos/chromeos.gyp
index a02a49b..8e98eb2a5 100644
--- a/chromeos/chromeos.gyp
+++ b/chromeos/chromeos.gyp
@@ -441,6 +441,7 @@
       'dbus/cras_audio_client_unittest.cc',
       'dbus/cros_disks_client_unittest.cc',
       'dbus/dbus_thread_manager_unittest.cc',
+      'dbus/fake_auth_policy_client_unittest.cc',
       'dbus/fake_cryptohome_client_unittest.cc',
       'dbus/fake_easy_unlock_client_unittest.cc',
       'dbus/fake_power_manager_client_unittest.cc',
diff --git a/chromeos/dbus/auth_policy_client.cc b/chromeos/dbus/auth_policy_client.cc
index 52d7cc9a..ade93a7 100644
--- a/chromeos/dbus/auth_policy_client.cc
+++ b/chromeos/dbus/auth_policy_client.cc
@@ -14,9 +14,12 @@
 
 namespace {
 
-// The first device policy fetch after joining Active Directory can be very slow
-// because machine credentials need to propagate through the AD deployment.
-const int kRefreshDevicePolicyTimeoutMilliseconds = 90000;
+// Policy fetch may take up to 300 seconds.  To ensure that a second policy
+// fetch queuing after the first one can succeed (e.g. user policy following
+// device policy), the D-Bus timeout needs to be at least twice that value.
+// JoinADDomain() is an exception since it's always guaranteed to be the first
+// call.
+constexpr int kSlowDbusTimeoutMilliseconds = 630 * 1000;
 
 authpolicy::ErrorType GetErrorFromReader(dbus::MessageReader* reader) {
   int32_t int_error;
@@ -59,7 +62,7 @@
     dbus::MessageWriter writer(&method_call);
     writer.AppendString(user_principal_name);
     writer.AppendFileDescriptor(password_fd);
-    proxy_->CallMethod(&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
+    proxy_->CallMethod(&method_call, kSlowDbusTimeoutMilliseconds,
                        base::Bind(&AuthPolicyClientImpl::HandleAuthCallback,
                                   weak_ptr_factory_.GetWeakPtr(), callback));
   }
@@ -68,7 +71,7 @@
     dbus::MethodCall method_call(authpolicy::kAuthPolicyInterface,
                                  authpolicy::kAuthPolicyRefreshDevicePolicy);
     proxy_->CallMethod(
-        &method_call, kRefreshDevicePolicyTimeoutMilliseconds,
+        &method_call, kSlowDbusTimeoutMilliseconds,
         base::Bind(&AuthPolicyClientImpl::HandleRefreshPolicyCallback,
                    weak_ptr_factory_.GetWeakPtr(), callback));
   }
@@ -81,7 +84,7 @@
     dbus::MessageWriter writer(&method_call);
     writer.AppendString(account_id.GetAccountIdKey());
     proxy_->CallMethod(
-        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
+        &method_call, kSlowDbusTimeoutMilliseconds,
         base::Bind(&AuthPolicyClientImpl::HandleRefreshPolicyCallback,
                    weak_ptr_factory_.GetWeakPtr(), callback));
   }
diff --git a/chromeos/dbus/fake_auth_policy_client.cc b/chromeos/dbus/fake_auth_policy_client.cc
index f37f1ea5..4f3a35de 100644
--- a/chromeos/dbus/fake_auth_policy_client.cc
+++ b/chromeos/dbus/fake_auth_policy_client.cc
@@ -10,6 +10,7 @@
 #include "base/location.h"
 #include "base/md5.h"
 #include "base/path_service.h"
+#include "base/strings/string_split.h"
 #include "base/task_scheduler/post_task.h"
 #include "chrome/browser/chromeos/policy/proto/chrome_device_policy.pb.h"
 #include "chromeos/chromeos_paths.h"
@@ -24,6 +25,9 @@
 
 namespace {
 
+const size_t kMaxMachineNameLength = 15;
+const char kInvalidMachineNameCharacters[] = "\\/:*?\"<>|";
+
 // Drop stub policy file of |policy_type| at |policy_path| containing
 // |serialized_payload|.
 bool WritePolicyFile(const base::FilePath& policy_path,
@@ -65,6 +69,25 @@
                                         const std::string& user_principal_name,
                                         int password_fd,
                                         const JoinCallback& callback) {
+  if (machine_name.size() > kMaxMachineNameLength) {
+    callback.Run(authpolicy::ERROR_MACHINE_NAME_TOO_LONG);
+    return;
+  }
+
+  if (machine_name.empty() ||
+      machine_name.find_first_of(kInvalidMachineNameCharacters) !=
+          std::string::npos) {
+    callback.Run(authpolicy::ERROR_BAD_MACHINE_NAME);
+    return;
+  }
+
+  std::vector<std::string> parts = base::SplitString(
+      user_principal_name, "@", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+  if (parts.size() != 2 || parts[0].empty() || parts[1].empty()) {
+    callback.Run(authpolicy::ERROR_PARSE_UPN_FAILED);
+    return;
+  }
+
   callback.Run(authpolicy::ERROR_NONE);
 }
 
diff --git a/chromeos/dbus/fake_auth_policy_client_unittest.cc b/chromeos/dbus/fake_auth_policy_client_unittest.cc
new file mode 100644
index 0000000..a8b6b79
--- /dev/null
+++ b/chromeos/dbus/fake_auth_policy_client_unittest.cc
@@ -0,0 +1,61 @@
+// 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 "chromeos/dbus/fake_auth_policy_client.h"
+
+#include "base/bind.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+namespace {
+
+const char kCorrectMachineName[] = "machine_name";
+const char kCorrectUserName[] = "user@realm.com";
+
+void JoinAdCallback(const std::string& machine_name,
+                    const std::string& user_principal_name,
+                    authpolicy::ErrorType expected,
+                    authpolicy::ErrorType actual) {
+  EXPECT_EQ(expected, actual) << "with machine name: " << machine_name
+                              << ", and user name: " << user_principal_name;
+}
+
+void TestDomainJoin(const std::string& machine_name,
+                    const std::string& user_principal_name,
+                    authpolicy::ErrorType expected) {
+  FakeAuthPolicyClient client;
+  client.JoinAdDomain(
+      machine_name, user_principal_name, /* password_fd */ -1,
+      base::Bind(&JoinAdCallback, machine_name, user_principal_name, expected));
+}
+
+}  // namespace
+
+// Tests parsing machine name.
+TEST(FakeAuthPolicyClientTest, JoinAdDomain_ParseMachineName) {
+  TestDomainJoin("correct_length1", kCorrectUserName, authpolicy::ERROR_NONE);
+  TestDomainJoin("", kCorrectUserName, authpolicy::ERROR_BAD_MACHINE_NAME);
+  TestDomainJoin("too_long_machine_name ", kCorrectUserName,
+                 authpolicy::ERROR_MACHINE_NAME_TOO_LONG);
+  TestDomainJoin("invalid:name", kCorrectUserName,
+                 authpolicy::ERROR_BAD_MACHINE_NAME);
+  TestDomainJoin(">nvalidname", kCorrectUserName,
+                 authpolicy::ERROR_BAD_MACHINE_NAME);
+}
+
+// Tests parsing user name.
+TEST(FakeAuthPolicyClientTest, JoinAdDomain_ParseUPN) {
+  TestDomainJoin(kCorrectMachineName, "user@realm.com", authpolicy::ERROR_NONE);
+  TestDomainJoin(kCorrectMachineName, "user",
+                 authpolicy::ERROR_PARSE_UPN_FAILED);
+  TestDomainJoin(kCorrectMachineName, "", authpolicy::ERROR_PARSE_UPN_FAILED);
+  TestDomainJoin(kCorrectMachineName, "user@",
+                 authpolicy::ERROR_PARSE_UPN_FAILED);
+  TestDomainJoin(kCorrectMachineName, "@realm",
+                 authpolicy::ERROR_PARSE_UPN_FAILED);
+  TestDomainJoin(kCorrectMachineName, "user@realm@com",
+                 authpolicy::ERROR_PARSE_UPN_FAILED);
+}
+
+}  // namespace chromeos
diff --git a/components/password_manager/core/browser/form_fetcher_impl_unittest.cc b/components/password_manager/core/browser/form_fetcher_impl_unittest.cc
index 9ab832f..9e3e072 100644
--- a/components/password_manager/core/browser/form_fetcher_impl_unittest.cc
+++ b/components/password_manager/core/browser/form_fetcher_impl_unittest.cc
@@ -37,6 +37,7 @@
 using testing::Pointee;
 using testing::Return;
 using testing::UnorderedElementsAre;
+using testing::WithArg;
 
 namespace password_manager {
 
@@ -144,7 +145,7 @@
 }
 
 // Small helper that wraps passed in forms in unique ptrs.
-std::vector<std::unique_ptr<PasswordForm>> make_results(
+std::vector<std::unique_ptr<PasswordForm>> MakeResults(
     const std::vector<PasswordForm>& forms) {
   std::vector<std::unique_ptr<PasswordForm>> results;
   results.reserve(forms.size());
@@ -170,9 +171,8 @@
   MOCK_METHOD0(ProcessMigratedFormsDummy, void());
 };
 
-ACTION(InvokeMigratorGetResults) {
-  static_cast<HttpPasswordMigrator*>(arg0)->OnGetPasswordStoreResults(
-      std::vector<std::unique_ptr<PasswordForm>>());
+ACTION_P(GetAndAssignWeakPtr, ptr) {
+  *ptr = arg0->GetWeakPtr();
 }
 
 }  // namespace
@@ -412,30 +412,40 @@
   GURL::Replacements http_rep;
   http_rep.SetSchemeStr(url::kHttpScheme);
   const GURL http_origin = form_digest_.origin.ReplaceComponents(http_rep);
-  const PasswordStore::FormDigest http_form_digest(
+  form_digest_ = PasswordStore::FormDigest(
       PasswordForm::SCHEME_HTML, http_origin.GetOrigin().spec(), http_origin);
 
   // A new form fetcher is created to be able to set the form digest and
   // migration flag.
-  const auto http_form_fetcher = base::MakeUnique<MockFormFetcherImpl>(
-      http_form_digest, &client_, /* should_migrate_http_passwords */ true);
+  form_fetcher_ = base::MakeUnique<MockFormFetcherImpl>(
+      form_digest_, &client_, /* should_migrate_http_passwords */ true);
+  EXPECT_CALL(consumer_, ProcessMatches(IsEmpty(), 0u));
+  form_fetcher_->AddConsumer(&consumer_);
 
   std::vector<PasswordForm> empty_forms;
   const PasswordForm http_form = CreateHTTPNonFederated();
+  const PasswordForm federated_form = CreateFederated();
 
-  // Tests that there is no attempt to migrate credentials on HTTP origins.
-  // FormFetcherImplTest::Fetch() can't be used here due to different
-  // expectations. The repeated calls to MockFormFetcherImpl::Fetch() are
-  // necessary to set the correct state.
-  EXPECT_CALL(*mock_store_, GetLogins(_, _));
-  http_form_fetcher->Fetch();
+  Fetch();
   EXPECT_CALL(*mock_store_, GetLogins(_, _)).Times(0);
-  http_form_fetcher->OnGetPasswordStoreResults(make_results(empty_forms));
+  EXPECT_CALL(*mock_store_, AddLogin(_)).Times(0);
+  EXPECT_CALL(consumer_, ProcessMatches(IsEmpty(), 0u));
+  form_fetcher_->OnGetPasswordStoreResults(MakeResults(empty_forms));
+  EXPECT_THAT(form_fetcher_->GetFederatedMatches(), IsEmpty());
 
-  EXPECT_CALL(*mock_store_, GetLogins(_, _));
-  http_form_fetcher->Fetch();
-  EXPECT_CALL(*mock_store_, GetLogins(_, _)).Times(0);
-  http_form_fetcher->OnGetPasswordStoreResults(make_results({http_form}));
+  Fetch();
+  EXPECT_CALL(consumer_,
+              ProcessMatches(UnorderedElementsAre(Pointee(http_form)), 0u));
+  form_fetcher_->OnGetPasswordStoreResults(MakeResults({http_form}));
+  EXPECT_THAT(form_fetcher_->GetFederatedMatches(), IsEmpty());
+
+  Fetch();
+  EXPECT_CALL(consumer_,
+              ProcessMatches(UnorderedElementsAre(Pointee(http_form)), 0u));
+  form_fetcher_->OnGetPasswordStoreResults(
+      MakeResults({http_form, federated_form}));
+  EXPECT_THAT(form_fetcher_->GetFederatedMatches(),
+              UnorderedElementsAre(Pointee(federated_form)));
 }
 
 // Test that ensures HTTP passwords are only migrated on HTTPS sites when no
@@ -444,28 +454,73 @@
   GURL::Replacements https_rep;
   https_rep.SetSchemeStr(url::kHttpsScheme);
   const GURL https_origin = form_digest_.origin.ReplaceComponents(https_rep);
-  const PasswordStore::FormDigest https_form_digest(
+  form_digest_ = PasswordStore::FormDigest(
       PasswordForm::SCHEME_HTML, https_origin.GetOrigin().spec(), https_origin);
 
   // A new form fetcher is created to be able to set the form digest and
   // migration flag.
-  const auto https_form_fetcher = base::MakeUnique<MockFormFetcherImpl>(
-      https_form_digest, &client_, /* should_migrate_http_passwords */ true);
+  form_fetcher_ = base::MakeUnique<MockFormFetcherImpl>(
+      form_digest_, &client_, /* should_migrate_http_passwords */ true);
+  EXPECT_CALL(consumer_, ProcessMatches(IsEmpty(), 0u));
+  form_fetcher_->AddConsumer(&consumer_);
+
+  PasswordForm https_form = CreateNonFederated();
+
+  // Create HTTP form for the same orgin (except scheme), which will be passed
+  // to the migrator.
+  GURL::Replacements http_rep;
+  http_rep.SetSchemeStr(url::kHttpScheme);
+  PasswordForm http_form = https_form;
+  http_form.origin = https_form.origin.ReplaceComponents(http_rep);
+  http_form.signon_realm = http_form.origin.GetOrigin().spec();
 
   std::vector<PasswordForm> empty_forms;
-  const PasswordForm https_form = CreateNonFederated();
 
   // Tests that there is only an attempt to migrate credentials on HTTPS origins
   // when no other credentials are available.
-  // FormFetcherImplTest::Fetch() can't be used here due to different
-  // expectations. The repeated calls to MockFormFetcherImpl::Fetch() are
-  // necessary to set the correct state.
-  EXPECT_CALL(*mock_store_, GetLogins(_, _));
-  https_form_fetcher->Fetch();
-  EXPECT_CALL(*mock_store_, GetLogins(_, _))
-      .WillOnce(testing::WithArg<1>(InvokeMigratorGetResults()));
-  EXPECT_CALL(*https_form_fetcher, ProcessMigratedFormsDummy());
-  https_form_fetcher->OnGetPasswordStoreResults(make_results(empty_forms));
+  const GURL form_digest_http_origin =
+      form_digest_.origin.ReplaceComponents(http_rep);
+  PasswordStore::FormDigest http_form_digest(
+      PasswordForm::SCHEME_HTML, form_digest_http_origin.GetOrigin().spec(),
+      form_digest_http_origin);
+  Fetch();
+  base::WeakPtr<PasswordStoreConsumer> migrator_ptr;
+  EXPECT_CALL(*mock_store_, GetLogins(http_form_digest, _))
+      .WillOnce(WithArg<1>(GetAndAssignWeakPtr(&migrator_ptr)));
+  form_fetcher_->OnGetPasswordStoreResults(MakeResults(empty_forms));
+  ASSERT_TRUE(migrator_ptr);
+
+  // Setting action equal to origin is necessary in order to mirror the behavior
+  // in HttpPasswordMigrator.
+  https_form.action = https_form.origin;
+
+  // Now perform the actual migration.
+  EXPECT_CALL(*mock_store_, AddLogin(https_form));
+  EXPECT_CALL(*static_cast<MockFormFetcherImpl*>(form_fetcher_.get()),
+              ProcessMigratedFormsDummy());
+  EXPECT_CALL(consumer_,
+              ProcessMatches(UnorderedElementsAre(Pointee(https_form)), 0u));
+  static_cast<HttpPasswordMigrator*>(migrator_ptr.get())
+      ->OnGetPasswordStoreResults(MakeResults({http_form}));
+  EXPECT_THAT(form_fetcher_->GetFederatedMatches(), IsEmpty());
+
+  // No migration should happen when results are present.
+  Fetch();
+  EXPECT_CALL(*mock_store_, GetLogins(_, _)).Times(0);
+  EXPECT_CALL(*mock_store_, AddLogin(_)).Times(0);
+  EXPECT_CALL(consumer_,
+              ProcessMatches(UnorderedElementsAre(Pointee(https_form)), 0u));
+  form_fetcher_->OnGetPasswordStoreResults(MakeResults({https_form}));
+  EXPECT_THAT(form_fetcher_->GetFederatedMatches(), IsEmpty());
+
+  const PasswordForm federated_form = CreateFederated();
+  Fetch();
+  EXPECT_CALL(consumer_,
+              ProcessMatches(UnorderedElementsAre(Pointee(https_form)), 0u));
+  form_fetcher_->OnGetPasswordStoreResults(
+      MakeResults({https_form, federated_form}));
+  EXPECT_THAT(form_fetcher_->GetFederatedMatches(),
+              UnorderedElementsAre(Pointee(federated_form)));
 }
 
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/login_database.cc b/components/password_manager/core/browser/login_database.cc
index 586ba07..ff7d4da 100644
--- a/components/password_manager/core/browser/login_database.cc
+++ b/components/password_manager/core/browser/login_database.cc
@@ -1217,26 +1217,26 @@
     if (result == ENCRYPTION_RESULT_ITEM_FAILURE)
       continue;
     DCHECK_EQ(ENCRYPTION_RESULT_SUCCESS, result);
-    if (matched_form && matched_form->signon_realm != new_form->signon_realm) {
-      if (new_form->scheme != PasswordForm::SCHEME_HTML)
-        continue;  // Ignore non-HTML matches.
 
-      if (IsPublicSuffixDomainMatch(new_form->signon_realm,
-                                    matched_form->signon_realm)) {
-        psl_domain_match_metric = PSL_DOMAIN_MATCH_FOUND;
-        new_form->is_public_suffix_match = true;
-      } else if (!new_form->federation_origin.unique() &&
-                 IsFederatedMatch(new_form->signon_realm,
-                                  matched_form->origin)) {
-      } else if (!new_form->federation_origin.unique() &&
-                 IsFederatedPSLMatch(new_form->signon_realm,
-                                     matched_form->origin)) {
-        psl_domain_match_metric = PSL_DOMAIN_MATCH_FOUND_FEDERATED;
-        new_form->is_public_suffix_match = true;
-      } else {
-        continue;
+    if (matched_form) {
+      switch (GetMatchResult(*new_form, *matched_form)) {
+        case MatchResult::NO_MATCH:
+          continue;
+        case MatchResult::EXACT_MATCH:
+          break;
+        case MatchResult::PSL_MATCH:
+          psl_domain_match_metric = PSL_DOMAIN_MATCH_FOUND;
+          new_form->is_public_suffix_match = true;
+          break;
+        case MatchResult::FEDERATED_MATCH:
+          break;
+        case MatchResult::FEDERATED_PSL_MATCH:
+          psl_domain_match_metric = PSL_DOMAIN_MATCH_FOUND_FEDERATED;
+          new_form->is_public_suffix_match = true;
+          break;
       }
     }
+
     forms->push_back(std::move(new_form));
   }
 
diff --git a/components/password_manager/core/browser/psl_matching_helper.cc b/components/password_manager/core/browser/psl_matching_helper.cc
index 8790e8a..02bccbe4 100644
--- a/components/password_manager/core/browser/psl_matching_helper.cc
+++ b/components/password_manager/core/browser/psl_matching_helper.cc
@@ -5,6 +5,7 @@
 #include "components/password_manager/core/browser/psl_matching_helper.h"
 
 #include <memory>
+#include <ostream>
 
 #include "base/logging.h"
 #include "base/strings/string_util.h"
@@ -17,6 +18,53 @@
 
 namespace password_manager {
 
+std::ostream& operator<<(std::ostream& out, MatchResult result) {
+  switch (result) {
+    case MatchResult::NO_MATCH:
+      return out << "No Match";
+    case MatchResult::EXACT_MATCH:
+      return out << "Exact Match";
+    case MatchResult::PSL_MATCH:
+      return out << "PSL Match";
+    case MatchResult::FEDERATED_MATCH:
+      return out << "Federated Match";
+    case MatchResult::FEDERATED_PSL_MATCH:
+      return out << "Federated PSL Match";
+  }
+  // This should never be reached, it is simply here to suppress compiler
+  // warnings.
+  return out;
+}
+
+MatchResult GetMatchResult(const PasswordForm& form,
+                           const PasswordStore::FormDigest& form_digest) {
+  if (form.signon_realm == form_digest.signon_realm)
+    return MatchResult::EXACT_MATCH;
+
+  // PSL and federated matches only apply to HTML forms.
+  if (form_digest.scheme != PasswordForm::SCHEME_HTML ||
+      form.scheme != PasswordForm::SCHEME_HTML)
+    return MatchResult::NO_MATCH;
+
+  const bool allow_psl_match = ShouldPSLDomainMatchingApply(
+      GetRegistryControlledDomain(GURL(form_digest.signon_realm)));
+  const bool allow_federated_match = !form.federation_origin.unique();
+
+  if (allow_psl_match &&
+      IsPublicSuffixDomainMatch(form.signon_realm, form_digest.signon_realm))
+    return MatchResult::PSL_MATCH;
+
+  if (allow_federated_match &&
+      IsFederatedMatch(form.signon_realm, form_digest.origin))
+    return MatchResult::FEDERATED_MATCH;
+
+  if (allow_psl_match && allow_federated_match &&
+      IsFederatedPSLMatch(form.signon_realm, form_digest.origin))
+    return MatchResult::FEDERATED_PSL_MATCH;
+
+  return MatchResult::NO_MATCH;
+}
+
 bool ShouldPSLDomainMatchingApply(
     const std::string& registry_controlled_domain) {
   return !registry_controlled_domain.empty() &&
diff --git a/components/password_manager/core/browser/psl_matching_helper.h b/components/password_manager/core/browser/psl_matching_helper.h
index 56b2ac4..d5839c5 100644
--- a/components/password_manager/core/browser/psl_matching_helper.h
+++ b/components/password_manager/core/browser/psl_matching_helper.h
@@ -5,8 +5,10 @@
 #ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_PSL_MATCHING_HELPER_H_
 #define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_PSL_MATCHING_HELPER_H_
 
+#include <iosfwd>
 #include <string>
 
+#include "components/password_manager/core/browser/password_store.h"
 
 class GURL;
 
@@ -23,6 +25,21 @@
   PSL_DOMAIN_MATCH_COUNT
 };
 
+enum class MatchResult {
+  NO_MATCH,
+  EXACT_MATCH,
+  PSL_MATCH,
+  FEDERATED_MATCH,
+  FEDERATED_PSL_MATCH,
+};
+
+// For testing.
+std::ostream& operator<<(std::ostream& out, MatchResult result);
+
+// Returns what type of match applies to |form| and |form_digest|.
+MatchResult GetMatchResult(const autofill::PasswordForm& form,
+                           const PasswordStore::FormDigest& form_digest);
+
 // Using the public suffix list for matching the origin is only needed for
 // websites that do not have a single hostname for entering credentials. It
 // would be better for their users if they did, but until then we help them find
diff --git a/components/password_manager/core/browser/psl_matching_helper_unittest.cc b/components/password_manager/core/browser/psl_matching_helper_unittest.cc
index 68381d2c..b98dddc 100644
--- a/components/password_manager/core/browser/psl_matching_helper_unittest.cc
+++ b/components/password_manager/core/browser/psl_matching_helper_unittest.cc
@@ -14,6 +14,154 @@
 
 namespace {
 
+TEST(PSLMatchingUtilsTest, GetMatchResult) {
+  struct TestData {
+    const char* form_signon_realm;
+    const char* form_federation_origin;
+    autofill::PasswordForm::Scheme digest_scheme;
+    const char* digest_signon_realm;
+    const char* digest_origin;
+    MatchResult match_result;
+  };
+
+  const TestData cases[] = {
+      // Test Exact Matches.
+      {"http://facebook.com/", "", autofill::PasswordForm::SCHEME_HTML,
+       "http://facebook.com/", "http://facebook.com", MatchResult::EXACT_MATCH},
+
+      {"http://m.facebook.com/", "", autofill::PasswordForm::SCHEME_HTML,
+       "http://m.facebook.com/", "http://m.facebook.com",
+       MatchResult::EXACT_MATCH},
+
+      // Scheme mismatch.
+      {"http://www.example.com/", "", autofill::PasswordForm::SCHEME_HTML,
+       "https://www.example.com/", "https://www.example.com",
+       MatchResult::NO_MATCH},
+
+      // Host mismatch.
+      {"http://www.example.com/", "", autofill::PasswordForm::SCHEME_HTML,
+       "http://wwwexample.com/", "http://wwwexample.com",
+       MatchResult::NO_MATCH},
+
+      {"http://www.example1.com/", "", autofill::PasswordForm::SCHEME_HTML,
+       "http://www.example2.com/", "http://www.example2.com",
+       MatchResult::NO_MATCH},
+
+      // Port mismatch.
+      {"http://www.example.com:123/", "", autofill::PasswordForm::SCHEME_HTML,
+       "http://www.example.com/", "http://www.example.com",
+       MatchResult::NO_MATCH},
+
+      // TLD mismatch.
+      {"http://www.example.org/", "", autofill::PasswordForm::SCHEME_HTML,
+       "http://www.example.com/", "http://www.example.com",
+       MatchResult::NO_MATCH},
+
+      // URLs without registry controlled domains should not match.
+      {"http://localhost/", "", autofill::PasswordForm::SCHEME_HTML,
+       "http://127.0.0.1/", "http://127.0.0.1", MatchResult::NO_MATCH},
+
+      // Invalid URLs don't match.
+      {"http://www.example.com/", "", autofill::PasswordForm::SCHEME_HTML,
+       "http://", "", MatchResult::NO_MATCH},
+
+      {"", "", autofill::PasswordForm::SCHEME_HTML, "http://www.example.com/",
+       "http://www.example.com", MatchResult::NO_MATCH},
+
+      {"http://www.example.com", "", autofill::PasswordForm::SCHEME_HTML,
+       "bad url", "", MatchResult::NO_MATCH},
+
+      // Test PSL Matches.
+      {"http://facebook.com/", "", autofill::PasswordForm::SCHEME_HTML,
+       "http://m.facebook.com/", "http://m.facebook.com",
+       MatchResult::PSL_MATCH},
+
+      {"https://facebook.com/", "", autofill::PasswordForm::SCHEME_HTML,
+       "https://m.facebook.com/", "https://m.facebook.com",
+       MatchResult::PSL_MATCH},
+
+      {"https://www.facebook.com/", "", autofill::PasswordForm::SCHEME_HTML,
+       "https://m.facebook.com/", "https://m.facebook.com",
+       MatchResult::PSL_MATCH},
+
+      // Don't apply PSL matching to Google domains.
+      {"https://google.com/", "", autofill::PasswordForm::SCHEME_HTML,
+       "https://maps.google.com/", "https://maps.google.com",
+       MatchResult::NO_MATCH},
+
+      {"https://google.com/", "", autofill::PasswordForm::SCHEME_HTML,
+       "https://maps.google.com/", "https://maps.google.com",
+       MatchResult::NO_MATCH},
+
+      // Test Federated Matches.
+      {"federation://example.com/google.com", "https://google.com",
+       autofill::PasswordForm::SCHEME_HTML, "https://example.com/",
+       "https://example.com", MatchResult::FEDERATED_MATCH},
+
+      // Empty federation providers don't match.
+      {"federation://example.com/", "", autofill::PasswordForm::SCHEME_HTML,
+       "https://example.com/", "https://example.com", MatchResult::NO_MATCH},
+
+      // Invalid origins don't match.
+      {"federation://example.com/google.com", "https://google.com",
+       autofill::PasswordForm::SCHEME_HTML, "https://example.com",
+       "example.com", MatchResult::NO_MATCH},
+
+      {"federation://example.com/google.com", "https://google.com",
+       autofill::PasswordForm::SCHEME_HTML, "https://example.com", "example",
+       MatchResult::NO_MATCH},
+
+      // Test Federated PSL Matches.
+      {"federation://sub.example.com/google.com", "https://google.com",
+       autofill::PasswordForm::SCHEME_HTML, "https://sub.example.com/",
+       "https://sub.example.com", MatchResult::FEDERATED_MATCH},
+
+      {"federation://sub1.example.com/google.com", "https://google.com",
+       autofill::PasswordForm::SCHEME_HTML, "https://sub2.example.com/",
+       "https://sub2.example.com", MatchResult::FEDERATED_PSL_MATCH},
+
+      {"federation://example.com/google.com", "https://google.com",
+       autofill::PasswordForm::SCHEME_HTML, "https://sub.example.com/",
+       "https://sub.example.com", MatchResult::FEDERATED_PSL_MATCH},
+
+      // Federated PSL matches do not apply to HTTP.
+      {"federation://sub1.example.com/google.com", "https://google.com",
+       autofill::PasswordForm::SCHEME_HTML, "http://sub2.example.com/",
+       "http://sub2.example.com", MatchResult::NO_MATCH},
+
+      {"federation://example.com/google.com", "https://google.com",
+       autofill::PasswordForm::SCHEME_HTML, "http://sub.example.com/",
+       "http://sub.example.com", MatchResult::NO_MATCH},
+
+      // Federated PSL matches do not apply to Google on HTTP or HTTPS.
+      {"federation://accounts.google.com/facebook.com", "https://facebook.com",
+       autofill::PasswordForm::SCHEME_HTML, "https://maps.google.com/",
+       "https://maps.google.com", MatchResult::NO_MATCH},
+
+      {"federation://accounts.google.com/facebook.com", "https://facebook.com",
+       autofill::PasswordForm::SCHEME_HTML, "http://maps.google.com/",
+       "http://maps.google.com", MatchResult::NO_MATCH},
+
+      // TLD Mismatch.
+      {"federation://sub.example.com/google.com", "https://google.com",
+       autofill::PasswordForm::SCHEME_HTML, "https://sub.example.org/",
+       "https://sub.example.org", MatchResult::NO_MATCH},
+  };
+
+  for (const TestData& data : cases) {
+    autofill::PasswordForm form;
+    form.signon_realm = data.form_signon_realm;
+    form.federation_origin = url::Origin(GURL(data.form_federation_origin));
+    PasswordStore::FormDigest digest(
+        data.digest_scheme, data.digest_signon_realm, GURL(data.digest_origin));
+
+    EXPECT_EQ(data.match_result, GetMatchResult(form, digest))
+        << "signon_realm = " << data.form_signon_realm
+        << ", federation_origin = " << data.form_federation_origin
+        << ", digest = " << digest;
+  }
+}
+
 TEST(PSLMatchingUtilsTest, IsPublicSuffixDomainMatch) {
   struct TestPair {
     const char* url1;
@@ -86,6 +234,43 @@
   }
 }
 
+TEST(PSLMatchingUtilsTest, IsFederatedPSLMatch) {
+  struct TestPair {
+    const char* signon_realm;
+    const char* origin;
+    bool should_match;
+  };
+
+  const TestPair pairs[] = {
+      {"https://facebook.com", "https://facebook.com", false},
+      {"", "", false},
+      {"", "https://facebook.com/", false},
+      {"https://facebook.com/", "", false},
+
+      {"federation://example.com/google.com", "https://example.com/", true},
+      {"federation://example.com/google.com", "http://example.com/", false},
+      {"federation://example.com/google.com", "example.com", false},
+      {"federation://example.com/", "http://example.com/", false},
+      {"federation://example.com/google.com", "example", false},
+
+      {"federation://sub.example.com/google.com", "https://sub.example.com/",
+       true},
+      {"federation://sub1.example.com/google.com", "https://sub2.example.com/",
+       true},
+      {"federation://example.com/google.com", "https://sub.example.com/", true},
+      {"federation://example.com/google.com", "http://sub.example.com/", false},
+      {"federation://sub.example.com/", "https://sub.example.com/", false},
+  };
+
+  for (const TestPair& pair : pairs) {
+    std::string signon_realm = pair.signon_realm;
+    GURL origin(pair.origin);
+    EXPECT_EQ(pair.should_match, IsFederatedPSLMatch(signon_realm, origin))
+        << "signon_realm = " << pair.signon_realm
+        << ", origin = " << pair.origin;
+  }
+}
+
 }  // namespace
 
 }  // namespace password_manager
diff --git a/content/browser/bluetooth/bluetooth_metrics.cc b/content/browser/bluetooth/bluetooth_metrics.cc
index d33f130..84421ee 100644
--- a/content/browser/bluetooth/bluetooth_metrics.cc
+++ b/content/browser/bluetooth/bluetooth_metrics.cc
@@ -281,7 +281,7 @@
       RecordStartNotificationsOutcome(outcome);
       return;
     case UMAGATTOperation::DESCRIPTOR_READ:
-      // TODO(667319) Add reporting to descriptors
+      RecordDescriptorReadValueOutcome(outcome);
       return;
     case UMAGATTOperation::COUNT:
       NOTREACHED();
@@ -313,7 +313,6 @@
 
 // Characteristic.readValue
 
-// static
 void RecordCharacteristicReadValueOutcome(UMAGATTOperationOutcome outcome) {
   UMA_HISTOGRAM_ENUMERATION("Bluetooth.Web.Characteristic.ReadValue.Outcome",
                             static_cast<int>(outcome),
@@ -356,6 +355,19 @@
                               rssi);
 }
 
+// Descriptor.readValue
+
+void RecordDescriptorReadValueOutcome(UMAGATTOperationOutcome outcome) {
+  UMA_HISTOGRAM_ENUMERATION("Bluetooth.Web.Descriptor.ReadValue.Outcome",
+                            static_cast<int>(outcome),
+                            static_cast<int>(UMAGATTOperationOutcome::COUNT));
+}
+
+void RecordDescriptorReadValueOutcome(CacheQueryOutcome outcome) {
+  RecordDescriptorReadValueOutcome(
+      TranslateCacheQueryOutcomeToGATTOperationOutcome(outcome));
+}
+
 void RecordRSSISignalStrengthLevel(UMARSSISignalStrengthLevel level) {
   UMA_HISTOGRAM_ENUMERATION(
       "Bluetooth.Web.RequestDevice.RSSISignalStrengthLevel",
diff --git a/content/browser/bluetooth/bluetooth_metrics.h b/content/browser/bluetooth/bluetooth_metrics.h
index 37b54af..65ad7b4f 100644
--- a/content/browser/bluetooth/bluetooth_metrics.h
+++ b/content/browser/bluetooth/bluetooth_metrics.h
@@ -35,6 +35,7 @@
   REMOTE_GATT_SERVER_DISCONNECT = 8,
   SERVICE_GET_CHARACTERISTICS = 9,
   GET_PRIMARY_SERVICES = 10,
+  DESCRIPTOR_READ_VALUE = 11,
   // NOTE: Add new actions immediately above this line. Make sure to update
   // the enum list in tools/metrics/histograms/histograms.xml accordingly.
   COUNT
@@ -278,6 +279,16 @@
 // called if QueryCacheForCharacteristic fails.
 void RecordStartNotificationsOutcome(CacheQueryOutcome outcome);
 
+// Descriptor.readValue() Metrics
+// There should be a call to this function for every call to
+// Send(BluetoothMsg_ReadDescriptorValueSuccess) and
+// Send(BluetoothMsg_ReadDescriptorValueError).
+void RecordDescriptorReadValueOutcome(UMAGATTOperationOutcome error);
+
+// Records the outcome of a cache query for readValue. Should only be called if
+// QueryCacheForDescriptor fails.
+void RecordDescriptorReadValueOutcome(CacheQueryOutcome outcome);
+
 enum class UMARSSISignalStrengthLevel {
   LESS_THAN_OR_EQUAL_TO_MIN_RSSI,
   LEVEL_0,
diff --git a/content/browser/bluetooth/web_bluetooth_service_impl.cc b/content/browser/bluetooth/web_bluetooth_service_impl.cc
index 15238442..5adce0e 100644
--- a/content/browser/bluetooth/web_bluetooth_service_impl.cc
+++ b/content/browser/bluetooth/web_bluetooth_service_impl.cc
@@ -744,6 +744,8 @@
     const std::string& descriptor_instance_id,
     const RemoteDescriptorReadValueCallback& callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  RecordWebBluetoothFunctionCall(
+      UMAWebBluetoothFunction::DESCRIPTOR_READ_VALUE);
 
   const CacheQueryResult query_result =
       QueryCacheForDescriptor(descriptor_instance_id);
@@ -753,12 +755,14 @@
   }
 
   if (query_result.outcome != CacheQueryOutcome::SUCCESS) {
+    RecordDescriptorReadValueOutcome(query_result.outcome);
     callback.Run(query_result.GetWebResult(), base::nullopt /* value */);
     return;
   }
 
   if (BluetoothBlocklist::Get().IsExcludedFromReads(
           query_result.descriptor->GetUUID())) {
+    RecordDescriptorReadValueOutcome(UMAGATTOperationOutcome::BLOCKLISTED);
     callback.Run(blink::mojom::WebBluetoothResult::BLOCKLISTED_READ,
                  base::nullopt /* value */);
     return;
@@ -976,6 +980,7 @@
     const RemoteDescriptorReadValueCallback& callback,
     const std::vector<uint8_t>& value) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  RecordDescriptorReadValueOutcome(UMAGATTOperationOutcome::SUCCESS);
   callback.Run(blink::mojom::WebBluetoothResult::SUCCESS, value);
 }
 
@@ -983,7 +988,6 @@
     const RemoteDescriptorReadValueCallback& callback,
     device::BluetoothRemoteGattService::GattErrorCode error_code) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  // TODO(667319) We are reporting failures to UMA but not reporting successes.
   callback.Run(TranslateGATTErrorAndRecord(error_code,
                                            UMAGATTOperation::DESCRIPTOR_READ),
                base::nullopt /* value */);
diff --git a/content/browser/browser_main_loop.cc b/content/browser/browser_main_loop.cc
index 6eb0ca7..e12d29d5 100644
--- a/content/browser/browser_main_loop.cc
+++ b/content/browser/browser_main_loop.cc
@@ -1534,8 +1534,8 @@
   {
     TRACE_EVENT0("startup",
       "BrowserMainLoop::BrowserThreadsStarted:InitSpeechRecognition");
-    speech_recognition_manager_.reset(new SpeechRecognitionManagerImpl(
-        audio_manager_.get(), media_stream_manager_.get()));
+    speech_recognition_manager_.reset(
+        new SpeechRecognitionManagerImpl(media_stream_manager_.get()));
   }
 
   {
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index b57a31f..4a3b2c7ce 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -2806,6 +2806,11 @@
     return;
   }
 
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kDisableRendererPriorityManagement)) {
+    return;
+  }
+
   // We background a process as soon as it hosts no active audio streams and no
   // visible widgets -- the callers must call this function whenever we
   // transition in/out of those states.
diff --git a/content/browser/speech/speech_recognition_manager_impl.cc b/content/browser/speech/speech_recognition_manager_impl.cc
index a20356b7..c9e470c2 100644
--- a/content/browser/speech/speech_recognition_manager_impl.cc
+++ b/content/browser/speech/speech_recognition_manager_impl.cc
@@ -26,7 +26,6 @@
 #include "content/public/common/speech_recognition_error.h"
 #include "content/public/common/speech_recognition_result.h"
 #include "media/audio/audio_device_description.h"
-#include "media/audio/audio_manager.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -44,11 +43,6 @@
 
 SpeechRecognitionManagerImpl* g_speech_recognition_manager_impl;
 
-void ShowAudioInputSettingsOnFileThread(media::AudioManager* audio_manager) {
-  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
-  audio_manager->ShowAudioInputSettings();
-}
-
 }  // namespace
 
 SpeechRecognitionManager* SpeechRecognitionManager::GetInstance() {
@@ -67,15 +61,14 @@
 }
 
 SpeechRecognitionManagerImpl::SpeechRecognitionManagerImpl(
-      media::AudioManager* audio_manager,
-      MediaStreamManager* media_stream_manager)
-    : audio_manager_(audio_manager),
-      media_stream_manager_(media_stream_manager),
+    MediaStreamManager* media_stream_manager)
+    : media_stream_manager_(media_stream_manager),
       primary_session_id_(kSessionIDInvalid),
       last_session_id_(kSessionIDInvalid),
       is_dispatching_event_(false),
-      delegate_(GetContentClient()->browser()->
-                    CreateSpeechRecognitionManagerDelegate()),
+      delegate_(GetContentClient()
+                    ->browser()
+                    ->CreateSpeechRecognitionManagerDelegate()),
       weak_factory_(this) {
   DCHECK(!g_speech_recognition_manager_impl);
   g_speech_recognition_manager_impl = this;
@@ -649,18 +642,6 @@
   return GetSession(session_id)->config;
 }
 
-bool SpeechRecognitionManagerImpl::HasAudioInputDevices() {
-  return audio_manager_->HasAudioInputDevices();
-}
-
-void SpeechRecognitionManagerImpl::ShowAudioInputSettings() {
-  // Since AudioManager::ShowAudioInputSettings can potentially launch external
-  // processes, do that in the FILE thread to not block the calling threads.
-  BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
-                          base::Bind(&ShowAudioInputSettingsOnFileThread,
-                                     audio_manager_));
-}
-
 SpeechRecognitionManagerImpl::Session::Session()
   : id(kSessionIDInvalid),
     abort_requested(false),
diff --git a/content/browser/speech/speech_recognition_manager_impl.h b/content/browser/speech/speech_recognition_manager_impl.h
index 4afc19d..9469016af0b 100644
--- a/content/browser/speech/speech_recognition_manager_impl.h
+++ b/content/browser/speech/speech_recognition_manager_impl.h
@@ -19,10 +19,6 @@
 #include "content/public/browser/speech_recognition_session_context.h"
 #include "content/public/common/speech_recognition_error.h"
 
-namespace media {
-class AudioManager;
-}
-
 namespace content {
 class BrowserMainLoop;
 class MediaStreamManager;
@@ -72,8 +68,6 @@
   int GetSession(int render_process_id,
                  int render_view_id,
                  int request_id) const override;
-  bool HasAudioInputDevices() override;
-  void ShowAudioInputSettings() override;
 
   // SpeechRecognitionEventListener methods.
   void OnRecognitionStart(int session_id) override;
@@ -98,8 +92,7 @@
   friend class BrowserMainLoop;
   // Needed for dtor.
   friend std::default_delete<SpeechRecognitionManagerImpl>;
-  SpeechRecognitionManagerImpl(media::AudioManager* audio_manager,
-                               MediaStreamManager* media_stream_manager);
+  SpeechRecognitionManagerImpl(MediaStreamManager* media_stream_manager);
   ~SpeechRecognitionManagerImpl() override;
 
  private:
@@ -173,7 +166,6 @@
   SpeechRecognitionEventListener* GetDelegateListener() const;
   int GetNextSessionID();
 
-  media::AudioManager* audio_manager_;
   MediaStreamManager* media_stream_manager_;
   typedef std::map<int, Session*> SessionsTable;
   SessionsTable sessions_;
diff --git a/content/public/browser/speech_recognition_manager.h b/content/public/browser/speech_recognition_manager.h
index abe1c3a..4e6e9b7 100644
--- a/content/public/browser/speech_recognition_manager.h
+++ b/content/public/browser/speech_recognition_manager.h
@@ -75,13 +75,6 @@
                          int render_view_id,
                          int request_id) const = 0;
 
-  // Returns true if the OS reports existence of audio recording devices.
-  virtual bool HasAudioInputDevices() = 0;
-
-  // Invokes the platform provided microphone settings UI in a non-blocking way,
-  // via the BrowserThread::FILE thread.
-  virtual void ShowAudioInputSettings() = 0;
-
  protected:
   virtual ~SpeechRecognitionManager() {}
 
diff --git a/content/public/common/content_switches.cc b/content/public/common/content_switches.cc
index 1bb64460..69e3577 100644
--- a/content/public/common/content_switches.cc
+++ b/content/public/common/content_switches.cc
@@ -270,6 +270,10 @@
 // Prevent renderer process backgrounding when set.
 const char kDisableRendererBackgrounding[]  = "disable-renderer-backgrounding";
 
+// No not manage renderer process priority at all when set.
+const char kDisableRendererPriorityManagement[] =
+    "disable-renderer-priority-management";
+
 // Whether the resize lock is disabled. Default is false. This is generally only
 // useful for tests that want to force disabling.
 const char kDisableResizeLock[] = "disable-resize-lock";
diff --git a/content/public/common/content_switches.h b/content/public/common/content_switches.h
index e5b8d33..e2d66533 100644
--- a/content/public/common/content_switches.h
+++ b/content/public/common/content_switches.h
@@ -88,6 +88,7 @@
 extern const char kDisableRemoteFonts[];
 CONTENT_EXPORT extern const char kDisableRemotePlaybackAPI[];
 extern const char kDisableRendererAccessibility[];
+CONTENT_EXPORT extern const char kDisableRendererPriorityManagement[];
 CONTENT_EXPORT extern const char kDisableRendererBackgrounding[];
 CONTENT_EXPORT extern const char kDisableResizeLock[];
 CONTENT_EXPORT extern const char kDisableSeccompFilterSandbox[];
diff --git a/content/public/test/fake_speech_recognition_manager.cc b/content/public/test/fake_speech_recognition_manager.cc
index e5e898a..5feb66f 100644
--- a/content/public/test/fake_speech_recognition_manager.cc
+++ b/content/public/test/fake_speech_recognition_manager.cc
@@ -126,8 +126,6 @@
   DCHECK(delegate_);  // We only expect this to be called via |delegate_|.
 }
 
-bool FakeSpeechRecognitionManager::HasAudioInputDevices() { return true; }
-
 int FakeSpeechRecognitionManager::GetSession(int render_process_id,
                                              int render_view_id,
                                              int request_id) const {
diff --git a/content/public/test/fake_speech_recognition_manager.h b/content/public/test/fake_speech_recognition_manager.h
index b0f3fc4..98a34b8 100644
--- a/content/public/test/fake_speech_recognition_manager.h
+++ b/content/public/test/fake_speech_recognition_manager.h
@@ -54,8 +54,6 @@
   void AbortAllSessionsForRenderProcess(int render_process_id) override;
   void AbortAllSessionsForRenderView(int render_process_id,
                                      int render_view_id) override;
-  bool HasAudioInputDevices() override;
-  void ShowAudioInputSettings() override {}
   int GetSession(int render_process_id,
                  int render_view_id,
                  int request_id) const override;
diff --git a/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h b/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h
index ca3aa300..01aed06 100644
--- a/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h
+++ b/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h
@@ -123,9 +123,9 @@
   // WriteRemoteCharacteristic request callbacks.
   std::pair<base::Closure, ErrorCallback> write_characteristic_value_callbacks_;
   // Stores SubscribeToNotifications request callbacks.
-  typedef std::pair<base::Closure, ErrorCallback> PendingNotifyCallback;
-  // Stores SubscribeToNotifications request callback.
-  PendingNotifyCallback subscribe_to_notification_callback_;
+  typedef std::pair<base::Closure, ErrorCallback> PendingNotifyCallbacks;
+  // Stores SubscribeToNotifications request callbacks.
+  PendingNotifyCallbacks subscribe_to_notification_callbacks_;
   // Map of descriptors, keyed by descriptor identifier.
   std::unordered_map<std::string,
                      std::unique_ptr<BluetoothRemoteGattDescriptorMac>>
diff --git a/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm b/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm
index 67c4c46a..92d7847c 100644
--- a/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm
+++ b/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm
@@ -210,9 +210,9 @@
     BluetoothRemoteGattDescriptor* ccc_descriptor,
     const base::Closure& callback,
     const ErrorCallback& error_callback) {
-  DCHECK(subscribe_to_notification_callback_.first.is_null());
-  DCHECK(subscribe_to_notification_callback_.second.is_null());
-  subscribe_to_notification_callback_ =
+  DCHECK(subscribe_to_notification_callbacks_.first.is_null());
+  DCHECK(subscribe_to_notification_callbacks_.second.is_null());
+  subscribe_to_notification_callbacks_ =
       std::make_pair(callback, error_callback);
   [GetCBPeripheral() setNotifyValue:YES
                   forCharacteristic:cb_characteristic_.get()];
@@ -294,8 +294,8 @@
 
 void BluetoothRemoteGattCharacteristicMac::DidUpdateNotificationState(
     NSError* error) {
-  PendingNotifyCallback reentrant_safe_callbacks;
-  reentrant_safe_callbacks.swap(subscribe_to_notification_callback_);
+  PendingNotifyCallbacks reentrant_safe_callbacks;
+  reentrant_safe_callbacks.swap(subscribe_to_notification_callbacks_);
   if (error) {
     VLOG(1) << "Bluetooth error while modifying notification state for "
                "characteristic, domain: "
diff --git a/ios/chrome/browser/native_app_launcher/BUILD.gn b/ios/chrome/browser/native_app_launcher/BUILD.gn
index f85a041b..687ac9db 100644
--- a/ios/chrome/browser/native_app_launcher/BUILD.gn
+++ b/ios/chrome/browser/native_app_launcher/BUILD.gn
@@ -101,6 +101,7 @@
     "//net:test_support",
     "//testing/gmock",
     "//testing/gtest",
+    "//ui/base",
     "//url",
   ]
 }
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index b627fb9c..f6c062b5 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -1593,6 +1593,10 @@
 
 crbug.com/240576 external/wpt/fullscreen/api/element-ready-check-containing-iframe-manual.html [ Timeout Failure Pass ]
 
+# TODO(foolip): Make the timing of fullscreen events reliable.
+crbug.com/402376 external/wpt/fullscreen/api/document-exit-fullscreen-timing-manual.html [ Failure Pass ]
+crbug.com/402376 external/wpt/fullscreen/api/element-request-fullscreen-timing-manual.html [ Failure Pass ]
+
 crbug.com/567230 [ Debug ] virtual/threaded/animations/restart-not-visible.html [ Timeout ]
 
 crbug.com/567419 inspector/elements/styles-2/metrics-box-sizing.html [ Skip ]
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fullscreen/api/document-exit-fullscreen-twice-manual-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/fullscreen/api/document-exit-fullscreen-twice-manual-expected.txt
new file mode 100644
index 0000000..2124e08
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/fullscreen/api/document-exit-fullscreen-twice-manual-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Document#exitFullscreen() called twice assert_equals: fullscreenElement after first exitFullscreen() expected Element node <div id="log"></div> but got null
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fullscreen/api/document-fullscreen-element-manual-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/fullscreen/api/document-fullscreen-element-manual-expected.txt
new file mode 100644
index 0000000..bb1b685
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/fullscreen/api/document-fullscreen-element-manual-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Document#fullscreenElement assert_equals: fullscreenElement after requestFullscreen() expected null but got Element node <div id="log"></div>
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fullscreen/api/element-request-fullscreen-and-move-manual-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/fullscreen/api/element-request-fullscreen-and-move-manual-expected.txt
new file mode 100644
index 0000000..ddb6e01
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/fullscreen/api/element-request-fullscreen-and-move-manual-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Element#requestFullscreen() followed by moving the element within the document assert_equals: expected Element node <div id="target"></div> but got null
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fullscreen/api/element-request-fullscreen-and-move-to-iframe-manual-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/fullscreen/api/element-request-fullscreen-and-move-to-iframe-manual-expected.txt
new file mode 100644
index 0000000..bb136d07
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/fullscreen/api/element-request-fullscreen-and-move-to-iframe-manual-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Element#requestFullscreen() followed by moving the element into an iframe assert_unreached: fullscreenchange event Reached unreachable code
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fullscreen/api/element-request-fullscreen-and-remove-iframe-manual-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/fullscreen/api/element-request-fullscreen-and-remove-iframe-manual-expected.txt
new file mode 100644
index 0000000..a522306c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/fullscreen/api/element-request-fullscreen-and-remove-iframe-manual-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Element#requestFullscreen() in iframe followed by removing the iframe assert_unreached: fullscreenchange event Reached unreachable code
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fullscreen/api/element-request-fullscreen-and-remove-manual-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/fullscreen/api/element-request-fullscreen-and-remove-manual-expected.txt
new file mode 100644
index 0000000..19293294
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/fullscreen/api/element-request-fullscreen-and-remove-manual-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Element#requestFullscreen() followed by removing the element assert_unreached: fullscreenchange event Reached unreachable code
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fullscreen/api/element-request-fullscreen-twice-manual-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/fullscreen/api/element-request-fullscreen-twice-manual-expected.txt
new file mode 100644
index 0000000..a6b832f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/fullscreen/api/element-request-fullscreen-twice-manual-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Element#requestFullscreen() twice assert_equals: fullscreenElement after first requestFullscreen() expected null but got Element node <div id="log"></div>
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/fast/css-grid-layout/grid-auto-repeat-huge-grid.html b/third_party/WebKit/LayoutTests/fast/css-grid-layout/grid-auto-repeat-huge-grid.html
new file mode 100644
index 0000000..6aa75a5
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css-grid-layout/grid-auto-repeat-huge-grid.html
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<title>Test for auto-fit and auto-fill with huge grids (lots of tracks)</title>
+<link href="resources/grid.css" rel="stylesheet">
+<link href="../css-intrinsic-dimensions/resources/width-keyword-classes.css" rel="stylesheet">
+<link href="../css-intrinsic-dimensions/resources/height-keyword-classes.css" rel="stylesheet">
+
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="resources/grid-definitions-parsing-utils.js"></script>
+<style>
+.wideGrid { width: 1000000000px; }
+.tallGrid { height: 1000000000px; }
+
+.minSizeWideGrid { min-width: 1000000000px; }
+.minSizeTallGrid { min-height: 1000000000px; }
+
+.lotsOfFixedRepeatWithAutoFitCols { grid-template-columns: repeat(auto-fit, 10px 2px 8px) repeat(992, 1px); }
+.lotsOfFixedRepeatWithAutoFillCols { grid-template-columns: repeat(auto-fill, 10px 2px 8px 7px 20px) repeat(995, 1px); }
+
+.lotsOfAutoRepeatWithAutoFitCols { grid-template-columns: repeat(auto-fit, 10px 2px 8px) repeat(10, 1px); }
+.lotsOfAutoRepeatWithAutoFillCols { grid-template-columns: repeat(auto-fill, 10px 2px 8px 7px 20px) repeat(10, 1px); }
+
+.lotsOfFixedRepeatWithAutoFitRows { grid-template-rows: repeat(auto-fit, 10px 2px 8px) repeat(992, 1px); }
+.lotsOfFixedRepeatWithAutoFillRows { grid-template-rows: repeat(auto-fill, 10px 2px 8px 7px 20px) repeat(995, 1px); }
+
+.lotsOfAutoRepeatWithAutoFitRows { grid-template-rows: repeat(auto-fit, 10px 2px 8px) repeat(10, 1px); }
+.lotsOfAutoRepeatWithAutoFillRows { grid-template-rows: repeat(auto-fill, 10px 2px 8px 7px 20px) repeat(10, 1px); }
+</style>
+
+<div id="wideAutoFillGrid" class="grid wideGrid lotsOfFixedRepeatWithAutoFillCols"></div>
+<div id="wideAutoFitGrid" class="grid wideGrid lotsOfFixedRepeatWithAutoFitCols">
+    <div>Item1</div>
+    <div>Item2</div>
+    <div>Item3</div>
+</div>
+
+<div id="tallAutoFillGrid" class="grid tallGrid lotsOfFixedRepeatWithAutoFillRows"></div>
+<div id="tallAutoFitGrid" class="grid tallGrid lotsOfFixedRepeatWithAutoFitRows">
+    <div>Item1</div>
+    <div>Item2</div>
+    <div>Item3</div>
+</div>
+
+<div id="wideAutoFillGridFewRepetitions" class="grid wideGrid lotsOfAutoRepeatWithAutoFillCols"></div>
+<div id="wideAutoFitGridFewRepetitions" class="grid wideGrid lotsOfAutoRepeatWithAutoFitCols">
+    <div>Item1</div>
+    <div>Item2</div>
+    <div>Item3</div>
+</div>
+
+<div id="tallAutoFillGridFewRepetitions" class="grid tallGrid lotsOfAutoRepeatWithAutoFillRows"></div>
+<div id="tallAutoFitGridFewRepetitions" class="grid tallGrid lotsOfAutoRepeatWithAutoFitRows">
+    <div>Item1</div>
+    <div>Item2</div>
+    <div>Item3</div>
+</div>
+
+<div id="wideAutoFillGridFewRepetitionsMinSize" class="grid lotsOfAutoRepeatWithAutoFillCols minSizeWideGrid min-content"></div>
+<div id="wideAutoFitGridFewRepetitionsMinSize" class="grid lotsOfAutoRepeatWithAutoFitCols minSizeWideGrid min-content">
+    <div>Item1</div>
+    <div>Item2</div>
+    <div>Item3</div>
+</div>
+
+<div id="tallAutoFillGridFewRepetitionsMinSize" class="grid lotsOfAutoRepeatWithAutoFillRows minSizeTallGrid min-content"></div>
+<div id="tallAutoFitGridFewRepetitionsMinSize" class="grid lotsOfAutoRepeatWithAutoFitRows minSizeTallGrid min-content">
+    <div>Item1</div>
+    <div>Item2</div>
+    <div>Item3</div>
+</div>
+
+<script>
+function testElement(element, property, length) {
+     var tracks = getComputedStyle(document.getElementById(element), '').getPropertyValue(property).split(' ');
+     assert_equals(tracks.length, length);
+     return tracks;
+}
+
+test(function() {
+     testElement("wideAutoFillGrid", "grid-template-columns", 1000);
+     testElement("wideAutoFitGrid", "grid-template-columns", 998);
+}, "Test that we don't get more than kGridMaxTracks repetitions even on very wide grids.");
+
+test(function() {
+     testElement("tallAutoFillGrid", "grid-template-rows", 1000);
+     testElement("tallAutoFitGrid", "grid-template-rows", 998);
+}, "Test that we don't get more than kGridMaxTracks repetitions even on very tall grids.");
+
+test(function() {
+     var wideAutoFillGrid = document.getElementById("wideAutoFillGridFewRepetitions");
+     var wideAutoFitGrid = document.getElementById("wideAutoFitGridFewRepetitions");
+
+     wideAutoFillGrid.style.gridGap = "100px";
+     wideAutoFitGrid.style.gridGap = "100px";
+
+     testElement("wideAutoFillGridFewRepetitions", "grid-template-columns", 1000);
+     testElement("wideAutoFitGridFewRepetitions", "grid-template-columns", 1000);
+
+     wideAutoFillGrid.style.gridGap = "1000000px";
+     wideAutoFitGrid.style.gridGap = "1000000px";
+
+     testElement("wideAutoFillGridFewRepetitions", "grid-template-columns", 130);
+     testElement("wideAutoFitGridFewRepetitions", "grid-template-columns", 82);
+
+     wideAutoFillGrid.style.gridGap = "0px";
+     wideAutoFitGrid.style.gridGap = "0px";
+}, "Test that we don't get more than kGridMaxTracks repetitions even on very wide grids with gaps.");
+
+test(function() {
+     var tallAutoFillGrid = document.getElementById("tallAutoFillGridFewRepetitions");
+     var tallAutoFitGrid = document.getElementById("tallAutoFitGridFewRepetitions");
+
+     tallAutoFillGrid.style.gridGap = "100px";
+     tallAutoFitGrid.style.gridGap = "100px";
+
+     testElement("tallAutoFillGridFewRepetitions", "grid-template-rows", 1000);
+     testElement("tallAutoFitGridFewRepetitions", "grid-template-rows", 1000);
+
+     tallAutoFillGrid.style.gridGap = "1000000px";
+     tallAutoFitGrid.style.gridGap = "1000000px";
+
+     testElement("tallAutoFillGridFewRepetitions", "grid-template-rows", 130);
+     testElement("tallAutoFitGridFewRepetitions", "grid-template-rows", 82);
+
+     tallAutoFillGrid.style.gridGap = "0px";
+     tallAutoFitGrid.style.gridGap = "0px";
+}, "Test that we don't get more than kGridMaxTracks repetitions even on very tall grids with gaps.");
+
+ test(function() {
+     var autoFillCols = testElement("wideAutoFillGridFewRepetitionsMinSize", "grid-template-columns", 1000);
+     var autoFitCols = testElement("wideAutoFitGridFewRepetitionsMinSize", "grid-template-columns", 1000);
+
+     /* Check that clamping auto repetitions does not reduce the amount of the other tracks. */
+     assert_equals(autoFillCols[1000 - 10 - 1], "20px");
+     assert_equals(autoFillCols[1000 - 10], "1px");
+     assert_equals(autoFitCols[1000 - 10 - 1], "0px");
+     assert_equals(autoFitCols[1000 - 10], "1px");
+
+     var autoFillRows = testElement("tallAutoFillGridFewRepetitionsMinSize", "grid-template-rows", 1000);
+     var autoFitRows = testElement("tallAutoFitGridFewRepetitionsMinSize", "grid-template-rows", 1000);
+
+     /* Check that clamping auto repetitions does not reduce the amount of the other tracks. */
+     assert_equals(autoFillRows[1000 - 10 - 1], "20px");
+     assert_equals(autoFillRows[1000 - 10], "1px");
+     assert_equals(autoFitRows[1000 - 10 - 1], "0px");
+     assert_equals(autoFitRows[1000 - 10], "1px");
+ }, "Test that we don't get more than kGridMaxTracks repetitions even on very wide grids with gaps and min-width.");
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/fullscreen/api/document-exit-fullscreen-twice.html b/third_party/WebKit/LayoutTests/fullscreen/api/document-exit-fullscreen-twice.html
new file mode 100644
index 0000000..fbf429eb
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fullscreen/api/document-exit-fullscreen-twice.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Document#exitFullscreen() called twice</title>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="../trusted-click.js"></script>
+<div id="log"></div>
+<script>
+// Adapted from https://github.com/w3c/web-platform-tests/pull/4250
+// TODO(foolip): Remove this test when the above is imported and passing.
+async_test(t => {
+  const div = document.querySelector("div");
+
+  document.onfullscreenchange = t.step_func(() => {
+    // We are now in fullscreen.
+    assert_equals(document.fullscreenElement, div);
+
+    document.onfullscreenchange = t.step_func(() => {
+      assert_equals(document.fullscreenElement, null);
+      // Done, but ensure that there's only one fullscreenchange event.
+      document.onfullscreenchange = t.unreached_func("second fullscreenchange event");
+      setTimeout(t.step_func_done(), 0);
+    });
+
+    // Exit fullscreen twice.
+    document.exitFullscreen();
+    assert_equals(document.fullscreenElement, null, "fullscreenElement after first exitFullscreen()");
+    document.exitFullscreen();
+    assert_equals(document.fullscreenElement, null, "fullscreenElement after second exitFullscreen()");
+  });
+  document.onfullscreenerror = t.unreached_func("fullscreenerror event");
+
+  trusted_request(div);
+});
+</script>
diff --git a/third_party/WebKit/LayoutTests/fullscreen/api/document-exit-fullscreen-vs-request.html b/third_party/WebKit/LayoutTests/fullscreen/api/document-exit-fullscreen-vs-request.html
index 2c402e5..54f2afc 100644
--- a/third_party/WebKit/LayoutTests/fullscreen/api/document-exit-fullscreen-vs-request.html
+++ b/third_party/WebKit/LayoutTests/fullscreen/api/document-exit-fullscreen-vs-request.html
@@ -4,14 +4,12 @@
 <script src="../../resources/testharnessreport.js"></script>
 <script src="../trusted-click.js"></script>
 <div id="log"></div>
-<div id="parent"><div><div></div></div></div>
+<div id="parent"><div></div></div>
 <script>
 // Adapted from https://github.com/w3c/web-platform-tests/pull/4250
 // TODO(foolip): Remove this test when the above is imported and passing.
 async_test(t => {
   const parent = document.getElementById("parent");
-  const child = parent.firstChild;
-  const grandChild = child.firstChild;
 
   document.onfullscreenchange = t.step_func(() => {
     // We are now in fullscreen, so exiting requires a resize but requesting
@@ -22,26 +20,23 @@
       // Request fullscreen on another element, to avoid any synchronous
       // short-circuiting on document.fullscreenElement.requestFullscreen(),
       // which used to be in the spec. Also request both before and after the
-      // exit. Both requests synchronously enqueue animation frame tasks. They
-      // may run after exiting, but still before the animation frame task for
-      // the exit, and so both will succeed, and there will be 3
-      // fullscreenchange events, but not matching the order of the calls.
+      // exit. Both requests should be silently ignored due to the exit.
 
-      let i = 0;
-      const expected = [child, grandChild, null];
-      document.onfullscreenchange = t.step_func(() => {
-        assert_equals(document.fullscreenElement, expected[i], "fullscreenElement when i=" + i);
-        i++;
-        if (i == 3)
+      let fullscreenchanges = 0;
+      document.onfullscreenchange = t.step_func((event) => {
+        assert_equals(document.fullscreenElement, child);
+        fullscreenchanges++;
+        if (fullscreenchanges == 3)
           t.done();
       });
 
+      const child = parent.firstChild;
       child.requestFullscreen();
-      assert_equals(document.fullscreenElement, parent, "fullscreenElement after first requestFullscreen()");
+      assert_equals(document.fullscreenElement, child, "fullscreenElement after first requestFullscreen()");
       document.exitFullscreen();
       assert_equals(document.fullscreenElement, parent, "fullscreenElement after exitFullscreen()");
-      grandChild.requestFullscreen();
-      assert_equals(document.fullscreenElement, parent, "fullscreenElement after second requestFullscreen()");
+      child.requestFullscreen();
+      assert_equals(document.fullscreenElement, child, "fullscreenElement after second requestFullscreen()");
     }), parent);
   });
   document.onfullscreenerror = t.unreached_func("fullscreenerror event");
diff --git a/third_party/WebKit/LayoutTests/fullscreen/api/document-fullscreen-element.html b/third_party/WebKit/LayoutTests/fullscreen/api/document-fullscreen-element.html
new file mode 100644
index 0000000..f339c0b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fullscreen/api/document-fullscreen-element.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<title>Document.fullscreenElement</title>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="../trusted-click.js"></script>
+<div id="log"></div>
+<script>
+async_test(function(t)
+{
+    var div = document.querySelector("div");
+
+    document.onfullscreenchange = t.step_func(function()
+    {
+        assert_equals(document.fullscreenElement, div, "fullscreenElement before exitFullscreen()");
+        document.exitFullscreen();
+        // TODO(foolip): fullscreenElement should still be div.
+        // https://crbug.com/402421
+        assert_equals(document.fullscreenElement, null, "fullscreenElement after exitFullscreen()");
+
+        document.onfullscreenchange = t.step_func(function()
+        {
+            assert_equals(document.fullscreenElement, null, "fullscreenElement after exiting fullscreen");
+            t.done();
+        });
+    });
+
+    trusted_click(t.step_func(function()
+    {
+        assert_equals(document.fullscreenElement, null, "fullscreenElement before requestFullscreen()");
+        div.requestFullscreen();
+        // TODO(foolip): fullscreenElement should still be null.
+        // https://crbug.com/402421
+        assert_equals(document.fullscreenElement, div, "fullscreenElement after requestFullscreen()");
+    }), document.body);
+});
+</script>
diff --git a/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-and-move-to-iframe-prefixed.html b/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-and-move-to-iframe-prefixed.html
deleted file mode 100644
index ef873a6..0000000
--- a/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-and-move-to-iframe-prefixed.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE html>
-<title>Element#webkitRequestFullscreen() followed by moving the element into an iframe</title>
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script src="../trusted-click.js"></script>
-<div id="log"></div>
-<div id="target"></div>
-<iframe allowfullscreen></iframe>
-<script>
-async_test(t => {
-  const target = document.getElementById("target");
-  const iframeDoc = document.querySelector("iframe").contentDocument;
-
-  iframeDoc.onwebkitfullscreenchange = t.unreached_func("webkitfullscreenchange event in iframe");
-  iframeDoc.onwebkitfullscreenerror = t.unreached_func("webkitfullscreenerror event in iframe");
-  document.onwebkitfullscreenchange = t.unreached_func("webkitfullscreenchange event");
-  document.onwebkitfullscreenerror = t.step_func_done(event => {
-    assert_equals(event.target, document);
-    assert_equals(document.webkitFullscreenElement, null);
-    assert_equals(iframeDoc.webkitFullscreenElement, null);
-  });
-
-  trusted_click(t.step_func(() => {
-    target.webkitRequestFullscreen();
-    iframeDoc.body.appendChild(target);
-  }), document.body);
-});
-</script>
diff --git a/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-and-move-to-iframe.html b/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-and-move-to-iframe.html
new file mode 100644
index 0000000..1a8c72f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-and-move-to-iframe.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>Element#requestFullscreen() followed by moving the element into an iframe</title>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="../trusted-click.js"></script>
+<div id="log"></div>
+<div id="target"></div>
+<iframe allowfullscreen></iframe>
+<script>
+// Adapted from https://github.com/w3c/web-platform-tests/pull/4250
+// TODO(foolip): Remove this test when the above is imported and passing.
+async_test(t => {
+  const target = document.getElementById("target");
+  const iframeDoc = document.querySelector("iframe").contentDocument;
+
+  iframeDoc.onfullscreenchange = t.unreached_func("fullscreenchange event in iframe");
+  iframeDoc.onfullscreenerror = t.unreached_func("fullscreenerror event in iframe");
+  document.onfullscreenchange = t.step_func_done(() => {
+    assert_equals(document.fullscreenElement, null);
+    assert_equals(iframeDoc.fullscreenElement, null);
+  });
+  document.onfullscreenerror = t.unreached_func("fullscreenerror event");
+
+  trusted_click(t.step_func(() => {
+    target.requestFullscreen();
+    iframeDoc.body.appendChild(target);
+  }), document.body);
+});
+</script>
diff --git a/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-and-move.html b/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-and-move.html
new file mode 100644
index 0000000..5189566
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-and-move.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>Element#requestFullscreen() followed by moving the element within the document</title>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="../trusted-click.js"></script>
+<div id="log"></div>
+<div id="target"></div>
+<div id="moveto"></div>
+<script>
+// Adapted from https://github.com/w3c/web-platform-tests/pull/4250
+// TODO(foolip): Remove this test when the above is imported and passing.
+async_test(t => {
+  const target = document.getElementById("target");
+  const moveTo = document.getElementById("moveto");
+
+  document.onfullscreenchange = t.step_func_done(() => {
+    assert_equals(document.fullscreenElement, null);
+    assert_equals(target.parentNode, moveTo);
+  });
+  document.onfullscreenerror = t.unreached_func("fullscreenchange event");
+
+  trusted_click(t.step_func(() => {
+    target.requestFullscreen();
+    moveTo.appendChild(target);
+  }), document.body);
+});
+</script>
diff --git a/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-and-remove-iframe.html b/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-and-remove-iframe.html
new file mode 100644
index 0000000..3edb7c8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-and-remove-iframe.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>Element#requestFullscreen() in iframe followed by removing the iframe</title>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="../trusted-click.js"></script>
+<div id="log"></div>
+<iframe allowfullscreen></iframe>
+<script>
+// Adapted from https://github.com/w3c/web-platform-tests/pull/4250
+// TODO(foolip): Remove this test when the above is imported and passing.
+async_test(t => {
+  const iframe = document.querySelector("iframe");
+  const iframeDocument = iframe.contentDocument;
+
+  document.onfullscreenchange = t.step_func_done(() => {
+    assert_equals(document.fullscreenElement, null);
+    assert_equals(iframeDocument.fullscreenElement, null);
+  });
+  document.onfullscreenerror = t.unreached_func("fullscreenerror event");
+  iframeDocument.onfullscreenchange = t.unreached_func("iframe fullscreenchange event");
+  iframeDocument.onfullscreenerror = t.unreached_func("iframe fullscreenerror event");
+
+  trusted_click(t.step_func(() => {
+    iframeDocument.body.requestFullscreen();
+    iframe.remove();
+  }), document.body);
+});
+</script>
diff --git a/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-and-remove-prefixed.html b/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-and-remove-prefixed.html
deleted file mode 100644
index 62a78e6c..0000000
--- a/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-and-remove-prefixed.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE html>
-<title>Element#webkitRequestFullscreen() followed by removing the element</title>
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script src="../trusted-click.js"></script>
-<div id="log"></div>
-<div id="target"></div>
-<script>
-async_test(t => {
-  const target = document.getElementById("target");
-
-  document.onwebkitfullscreenchange = t.unreached_func("webkitfullscreenchange event");
-  document.onwebkitfullscreenerror = t.step_func_done(event => {
-    assert_equals(event.target, document);
-    assert_equals(document.webkitFullscreenElement, null);
-  });
-
-  trusted_click(t.step_func(() => {
-    target.webkitRequestFullscreen();
-    target.remove();
-  }), document.body);
-});
-</script>
diff --git a/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-and-remove.html b/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-and-remove.html
new file mode 100644
index 0000000..f5bb0e9
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-and-remove.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<title>Element#requestFullscreen() followed by removing the element</title>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="../trusted-click.js"></script>
+<div id="log"></div>
+<div id="target"></div>
+<script>
+// Adapted from https://github.com/w3c/web-platform-tests/pull/4250
+// TODO(foolip): Remove this test when the above is imported and passing.
+async_test(t => {
+  const target = document.getElementById("target");
+
+  document.onfullscreenchange = t.step_func_done(() => {
+    assert_equals(document.fullscreenElement, null);
+  });
+  document.onfullscreenerror = t.unreached_func("fullscreenchange event");
+
+  trusted_click(t.step_func(() => {
+    target.requestFullscreen();
+    target.remove();
+  }), document.body);
+});
+</script>
diff --git a/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-twice.html b/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-twice.html
new file mode 100644
index 0000000..80d1761e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-twice.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>Element#requestFullscreen() twice</title>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="../trusted-click.js"></script>
+<div id="log"></div>
+<script>
+// Adapted from https://github.com/w3c/web-platform-tests/pull/4250
+// TODO(foolip): Remove this test when the above is imported and passing.
+async_test(t => {
+  const div = document.querySelector("div");
+
+  document.onfullscreenchange = t.step_func(() => {
+    assert_equals(document.fullscreenElement, div);
+    // Done, but ensure that there's only one fullscreenchange event.
+    document.onfullscreenchange = t.unreached_func("second fullscreenchange event");
+    setTimeout(t.step_func_done(), 0);
+  });
+  document.onfullscreenerror = t.unreached_func("fullscreenerror event");
+
+  trusted_click(t.step_func(() => {
+    // Request fullscreen twice.
+    div.requestFullscreen();
+    assert_equals(document.fullscreenElement, div, "fullscreenElement after first requestFullscreen()");
+    div.requestFullscreen();
+    assert_equals(document.fullscreenElement, div, "fullscreenElement after second requestFullscreen()");
+  }), document.body);
+});
+</script>
diff --git a/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-two-iframes.html b/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-two-iframes.html
index 688e669..c9b0ed6a 100644
--- a/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-two-iframes.html
+++ b/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-two-iframes.html
@@ -18,15 +18,15 @@
   // Expect first a fullscreenchange event for the second (!) request, then a
   // fullscreenerror event for the first request. TODO(foolip): Remove the
   // Fullscreen hierarchy restrictions. https://crbug.com/627792
-  a.contentDocument.onfullscreenchange = t.step_func_done(() => {
-    assert_equals(document.fullscreenElement, a, 'fullscreenElement');
-    assert_equals(a.contentDocument.fullscreenElement, a.contentDocument.body, 'fullscreenElement in iframe a');
-    b.contentDocument.onfullscreenerror = t.step_func(() => {
-      assert_equals(b.contentDocument.fullscreenElement, null, 'fullscreenElement in iframe b');
+  a.contentDocument.onfullscreenerror = t.step_func(() => {
+    b.contentDocument.onfullscreenchange = t.step_func_done(() => {
+      assert_equals(document.fullscreenElement, b, 'fullscreenElement');
+      assert_equals(a.contentDocument.fullscreenElement, null, 'fullscreenElement in iframe a');
+      assert_equals(b.contentDocument.fullscreenElement, b.contentDocument.body, 'fullscreenElement in iframe b');
     });
   });
-  a.contentDocument.onfullscreenerror = t.unreached_func('fullscreenerror event in iframe a');
-  b.contentDocument.onfullscreenchange = t.unreached_func('fullscreenchange event in iframe b');
+  a.contentDocument.onfullscreenchange = t.unreached_func('fullscreenchange event in iframe a');
+  b.contentDocument.onfullscreenerror = t.unreached_func('fullscreenerror event in iframe b');
 
   trusted_click(t.step_func(() => {
     b.contentDocument.body.requestFullscreen();
diff --git a/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-vs-exit.html b/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-vs-exit.html
index ad5ed1e..a766a6f 100644
--- a/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-vs-exit.html
+++ b/third_party/WebKit/LayoutTests/fullscreen/api/element-request-fullscreen-vs-exit.html
@@ -18,7 +18,7 @@
     document.onfullscreenerror = t.unreached_func("fullscreenerror event");
 
     target.requestFullscreen();
-    assert_equals(document.fullscreenElement, null, "fullscreenElement after requestFullscreen()");
+    assert_equals(document.fullscreenElement, target, "fullscreenElement after requestFullscreen()");
     document.exitFullscreen();
     assert_equals(document.fullscreenElement, null, "fullscreenElement after exitFullscreen()");
   }), document.body);
diff --git a/third_party/WebKit/LayoutTests/fullscreen/enter-exit-full-screen-hover-expected.txt b/third_party/WebKit/LayoutTests/fullscreen/enter-exit-full-screen-hover-expected.txt
index 9388a98..93985ea 100644
--- a/third_party/WebKit/LayoutTests/fullscreen/enter-exit-full-screen-hover-expected.txt
+++ b/third_party/WebKit/LayoutTests/fullscreen/enter-exit-full-screen-hover-expected.txt
@@ -2,10 +2,8 @@
 
 TEST COMPLETE
 PASS document.webkitIsFullScreen is true
-PASS getHoverActiveState(enterButton) is "hovered"
 PASS getHoverActiveState(enterButton) is "default"
 PASS document.webkitIsFullScreen is false
-PASS getHoverActiveState(exitButton) is "hovered"
 PASS getHoverActiveState(exitButton) is "default"
 Go full screen Exit full screen
 EVENT(webkitfullscreenchange)
diff --git a/third_party/WebKit/LayoutTests/fullscreen/enter-exit-full-screen-hover.html b/third_party/WebKit/LayoutTests/fullscreen/enter-exit-full-screen-hover.html
index 85f4a27..1aa60aab 100644
--- a/third_party/WebKit/LayoutTests/fullscreen/enter-exit-full-screen-hover.html
+++ b/third_party/WebKit/LayoutTests/fullscreen/enter-exit-full-screen-hover.html
@@ -23,30 +23,22 @@
     var enterButtonCenter = elementCenter(enterButton);
 
     waitForEventOnce(document, 'webkitfullscreenchange', function() {
-        shouldBeTrue("document.webkitIsFullScreen");
-        // After entering fullscreen + layout, the button should lose hover.
-        // TODO(foolip): Synchronize hover state changes with animation frames.
-        // https://crbug.com/668758
-        shouldBeOnlyHovered("getHoverActiveState(enterButton)");
-        testRunner.layoutAndPaintAsyncThen(function() {
-            shouldBeDefault("getHoverActiveState(enterButton)");
+        shouldBeTrue("document.webkitIsFullScreen")
+        // After entering fullscreen, the button should lose hover
+        shouldBeDefault("getHoverActiveState(enterButton)")
 
-            waitForEventOnce(document, 'webkitfullscreenchange', function() {
-                shouldBeFalse("document.webkitIsFullScreen");
-                // After exiting fullscreen + layout, the button should lose hover.
-                shouldBeOnlyHovered("getHoverActiveState(exitButton)");
-                testRunner.layoutAndPaintAsyncThen(function() {
-                    shouldBeDefault("getHoverActiveState(exitButton)");
-                    endTest();
-                });
-            });
-
-            var exitButtonCenter = elementCenter(exitButton);
-            // Hover on and click the "Exit fullscreen" button
-            eventSender.mouseMoveTo(exitButtonCenter.x, exitButtonCenter.y);
-            eventSender.mouseDown();
-            eventSender.mouseUp();
+        waitForEventOnce(document, 'webkitfullscreenchange', function() {
+            shouldBeFalse("document.webkitIsFullScreen")
+            // After leaving fullscreen, the button should lose hover
+            shouldBeDefault("getHoverActiveState(exitButton)")
+            endTest();
         });
+
+        var exitButtonCenter = elementCenter(exitButton);
+        // Hover on and click the "Exit fullscreen" button
+        eventSender.mouseMoveTo(exitButtonCenter.x, exitButtonCenter.y);
+        eventSender.mouseDown();
+        eventSender.mouseUp();
     });
 
 
diff --git a/third_party/WebKit/LayoutTests/fullscreen/full-screen-remove-ancestor-during-transition-expected.txt b/third_party/WebKit/LayoutTests/fullscreen/full-screen-remove-ancestor-during-transition-expected.txt
new file mode 100644
index 0000000..b5c1b43d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fullscreen/full-screen-remove-ancestor-during-transition-expected.txt
@@ -0,0 +1,5 @@
+PASS successfullyParsed is true
+
+TEST COMPLETE
+PASS document.webkitFullscreenElement is not null
+PASS
diff --git a/third_party/WebKit/LayoutTests/fullscreen/full-screen-remove-ancestor-during-transition.html b/third_party/WebKit/LayoutTests/fullscreen/full-screen-remove-ancestor-during-transition.html
new file mode 100644
index 0000000..735c5204
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fullscreen/full-screen-remove-ancestor-during-transition.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<script src="../resources/js-test.js"></script>
+<script>
+if (window.testRunner) {
+    testRunner.dumpAsText();
+    testRunner.waitUntilDone();
+}
+
+function runWithKeyDown(fn)
+{
+    document.addEventListener('keydown', function() { fn(); }, false);
+    if (window.testRunner) {
+        eventSender.keyDown('a');
+    }
+}
+
+function init() {
+    runWithKeyDown(goFullScreen);
+}
+
+function goFullScreen() {
+    var iframe = document.getElementById('block1');
+    var element = iframe.contentDocument.documentElement;
+    setTimeout(function () {
+        iframe.parentNode.removeChild(iframe);
+        gc();
+        setTimeout(function () {
+            if (window.testRunner) {
+                testRunner.notifyDone();
+            }
+        }, 0);
+    }, 0);
+    element.webkitRequestFullScreen();
+    shouldNotBe("document.webkitFullscreenElement", "null");
+}
+</script>
+<body onload="init()">
+    <iframe allowfullscreen src="resources/inner.html" id="block1"></iframe>
+    PASS
+</body>
diff --git a/third_party/WebKit/LayoutTests/fullscreen/full-screen-stacking-context.html b/third_party/WebKit/LayoutTests/fullscreen/full-screen-stacking-context.html
index 56bd356..34f58dfe 100644
--- a/third_party/WebKit/LayoutTests/fullscreen/full-screen-stacking-context.html
+++ b/third_party/WebKit/LayoutTests/fullscreen/full-screen-stacking-context.html
@@ -5,9 +5,7 @@
             var runPixelTests = true;
             
             function init() {
-                waitForEventOnce(document, 'webkitfullscreenchange', function() {
-                    testRunner.layoutAndPaintAsyncThen(endTest);
-                });
+                waitForEventAndEnd(document, 'webkitfullscreenchange');
                 runWithKeyDown(goFullScreen);
             }
             
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-exit-fullscreen-twice-manual-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-exit-fullscreen-twice-manual-expected.txt
new file mode 100644
index 0000000..2124e08
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-exit-fullscreen-twice-manual-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Document#exitFullscreen() called twice assert_equals: fullscreenElement after first exitFullscreen() expected Element node <div id="log"></div> but got null
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-fullscreen-element-manual-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-fullscreen-element-manual-expected.txt
new file mode 100644
index 0000000..bb1b685
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/document-fullscreen-element-manual-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Document#fullscreenElement assert_equals: fullscreenElement after requestFullscreen() expected null but got Element node <div id="log"></div>
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-move-manual-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-move-manual-expected.txt
new file mode 100644
index 0000000..ddb6e01
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-move-manual-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Element#requestFullscreen() followed by moving the element within the document assert_equals: expected Element node <div id="target"></div> but got null
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-move-to-iframe-manual-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-move-to-iframe-manual-expected.txt
new file mode 100644
index 0000000..bb136d07
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-move-to-iframe-manual-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Element#requestFullscreen() followed by moving the element into an iframe assert_unreached: fullscreenchange event Reached unreachable code
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-remove-iframe-manual-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-remove-iframe-manual-expected.txt
new file mode 100644
index 0000000..a522306c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-remove-iframe-manual-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Element#requestFullscreen() in iframe followed by removing the iframe assert_unreached: fullscreenchange event Reached unreachable code
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-remove-manual-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-remove-manual-expected.txt
new file mode 100644
index 0000000..19293294
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-and-remove-manual-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Element#requestFullscreen() followed by removing the element assert_unreached: fullscreenchange event Reached unreachable code
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-twice-manual-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-twice-manual-expected.txt
new file mode 100644
index 0000000..a6b832f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/imported/wpt/fullscreen/api/element-request-fullscreen-twice-manual-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Element#requestFullscreen() twice assert_equals: fullscreenElement after first requestFullscreen() expected null but got Element node <div id="log"></div>
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/Source/bindings/core/v8/custom/V8WindowCustom.cpp b/third_party/WebKit/Source/bindings/core/v8/custom/V8WindowCustom.cpp
index 5c9bf858..774a91e6f 100644
--- a/third_party/WebKit/Source/bindings/core/v8/custom/V8WindowCustom.cpp
+++ b/third_party/WebKit/Source/bindings/core/v8/custom/V8WindowCustom.cpp
@@ -51,6 +51,7 @@
 #include "core/frame/ImageBitmap.h"
 #include "core/frame/LocalDOMWindow.h"
 #include "core/frame/LocalFrame.h"
+#include "core/frame/Location.h"
 #include "core/frame/Settings.h"
 #include "core/frame/UseCounter.h"
 #include "core/frame/csp/ContentSecurityPolicy.h"
@@ -65,6 +66,58 @@
 
 namespace blink {
 
+void V8Window::locationAttributeGetterCustom(
+    const v8::PropertyCallbackInfo<v8::Value>& info) {
+  v8::Isolate* isolate = info.GetIsolate();
+  v8::Local<v8::Object> holder = info.Holder();
+
+  DOMWindow* window = V8Window::toImpl(holder);
+  Location* location = window->location();
+  DCHECK(location);
+
+  // Keep the wrapper object for the return value alive as long as |this|
+  // object is alive in order to save creation time of the wrapper object.
+  if (DOMDataStore::setReturnValue(info.GetReturnValue(), location))
+    return;
+
+  v8::Local<v8::Value> wrapper;
+
+  // Note that this check is gated on whether or not |window| is remote, not
+  // whether or not |window| is cross-origin. If |window| is local, the
+  // |location| property must always return the same wrapper, even if the
+  // cross-origin status changes by changing properties like |document.domain|.
+  if (window->isRemoteDOMWindow()) {
+    DOMWrapperWorld& world = DOMWrapperWorld::current(isolate);
+    const auto* wrapperTypeInfo = location->wrapperTypeInfo();
+    v8::Local<v8::Object> newWrapper =
+        wrapperTypeInfo->domTemplate(isolate, world)
+            ->NewRemoteInstance()
+            .ToLocalChecked();
+
+    DCHECK(!DOMDataStore::containsWrapper(location, isolate));
+    wrapper = V8DOMWrapper::associateObjectWithWrapper(
+        isolate, location, wrapperTypeInfo, newWrapper);
+  } else {
+    wrapper = ToV8(location, holder, isolate);
+  }
+
+  // Keep the wrapper object for the return value alive as long as |this|
+  // object is alive in order to save creation time of the wrapper object.
+  //
+  // TODO(dcheng): The hidden reference behavior is broken in many ways. We
+  // should be caching for all DOM attributes. Even if it's not critical for
+  // remote Location objects, we should clean this up to improve
+  // maintainability. In the long-term, this will be superseded by wrapper
+  // tracing.
+  const char kKeepAliveKey[] = "KeepAlive#Window#location";
+  V8HiddenValue::setHiddenValue(
+      ScriptState::current(isolate), holder,
+      v8AtomicString(isolate, StringView(kKeepAliveKey, sizeof kKeepAliveKey)),
+      wrapper);
+
+  v8SetReturnValue(info, wrapper);
+}
+
 void V8Window::eventAttributeGetterCustom(
     const v8::FunctionCallbackInfo<v8::Value>& info) {
   LocalDOMWindow* impl = toLocalDOMWindow(V8Window::toImpl(info.Holder()));
diff --git a/third_party/WebKit/Source/bindings/scripts/v8_attributes.py b/third_party/WebKit/Source/bindings/scripts/v8_attributes.py
index 7eed0bd..109b010 100644
--- a/third_party/WebKit/Source/bindings/scripts/v8_attributes.py
+++ b/third_party/WebKit/Source/bindings/scripts/v8_attributes.py
@@ -198,8 +198,6 @@
     # [CrossOrigin] is incompatible with a number of other attributes, so check
     # for them here.
     if is_cross_origin:
-        if context['has_cross_origin_getter'] and context['has_custom_getter']:
-            raise Exception('[CrossOrigin] and [Custom] are incompatible on the same getter: %s.%s', interface.name, attribute.name)
         if context['has_cross_origin_setter'] and context['has_custom_setter']:
             raise Exception('[CrossOrigin] and [Custom] are incompatible on the same setter: %s.%s', interface.name, attribute.name)
         if context['is_per_world_bindings']:
diff --git a/third_party/WebKit/Source/bindings/templates/interface_base.cpp.tmpl b/third_party/WebKit/Source/bindings/templates/interface_base.cpp.tmpl
index 31c8bebb..0a768e6 100644
--- a/third_party/WebKit/Source/bindings/templates/interface_base.cpp.tmpl
+++ b/third_party/WebKit/Source/bindings/templates/interface_base.cpp.tmpl
@@ -162,7 +162,15 @@
   {% for attribute in attributes if attribute.has_cross_origin_getter or attribute.has_cross_origin_setter %}
   {
     "{{attribute.name}}",
-    {%+ if attribute.has_cross_origin_getter %}&{{cpp_class}}V8Internal::{{attribute.name}}AttributeGetter{% else %}nullptr{% endif %},
+    {% if attribute.has_cross_origin_getter %}
+    {% if attribute.has_custom_getter %}
+    {{v8_class}}::{{attribute.name}}AttributeGetterCustom,
+    {% else %}
+    &{{cpp_class}}V8Internal::{{attribute.name}}AttributeGetter,
+    {% endif %}
+    {% else %}
+    nullptr,
+    {% endif %}
     {%+ if attribute.has_cross_origin_setter %}&{{cpp_class}}V8Internal::{{attribute.name}}AttributeSetter{% else %}nullptr{% endif %},
   },
   {% endfor %}
@@ -246,11 +254,12 @@
 
   const DOMWindow* targetWindow = V8Window::toImpl(window);
   return BindingSecurity::shouldAllowAccessTo(toLocalDOMWindow(toDOMWindow(accessingContext)), targetWindow, BindingSecurity::ErrorReportOption::DoNotReport);
-  {% else %}{# if interface_name == 'Window' #}
-  {# Not 'Window' means it\'s Location. #}
+  {% elif interface_name == 'Location' %}
   {{cpp_class}}* impl = {{v8_class}}::toImpl(accessedObject);
   return BindingSecurity::shouldAllowAccessTo(toLocalDOMWindow(toDOMWindow(accessingContext)), impl, BindingSecurity::ErrorReportOption::DoNotReport);
-  {% endif %}{# if interface_name == 'Window' #}
+  {% else %}
+  #error "Unexpected security check for interface {{interface_name}}"
+  {% endif %}
 }
 
 {% if has_cross_origin_named_getter %}
@@ -309,9 +318,11 @@
   for (const auto& attribute : {{cpp_class_or_partial}}V8Internal::kCrossOriginAttributeTable)
     names.push_back(attribute.name);
 
-  v8SetReturnValue(
-      info,
-      ToV8(names, info.Holder(), info.GetIsolate()).As<v8::Array>());
+  // Use the current context as the creation context, as a cross-origin access
+  // may involve an object that does not have a creation context.
+  v8SetReturnValue(info,
+                   ToV8(names, info.GetIsolate()->GetCurrentContext()->Global(),
+                        info.GetIsolate()).As<v8::Array>());
 }
 {% endif %}
 
diff --git a/third_party/WebKit/Source/bindings/tests/results/core/V8TestInterfaceCheckSecurity.cpp b/third_party/WebKit/Source/bindings/tests/results/core/V8TestInterfaceCheckSecurity.cpp
index 6d7138f..5a28da3 100644
--- a/third_party/WebKit/Source/bindings/tests/results/core/V8TestInterfaceCheckSecurity.cpp
+++ b/third_party/WebKit/Source/bindings/tests/results/core/V8TestInterfaceCheckSecurity.cpp
@@ -396,8 +396,7 @@
 }
 
 bool V8TestInterfaceCheckSecurity::securityCheck(v8::Local<v8::Context> accessingContext, v8::Local<v8::Object> accessedObject, v8::Local<v8::Value> data) {
-  TestInterfaceCheckSecurity* impl = V8TestInterfaceCheckSecurity::toImpl(accessedObject);
-  return BindingSecurity::shouldAllowAccessTo(toLocalDOMWindow(toDOMWindow(accessingContext)), impl, BindingSecurity::ErrorReportOption::DoNotReport);
+  #error "Unexpected security check for interface TestInterfaceCheckSecurity"
 }
 
 void V8TestInterfaceCheckSecurity::crossOriginNamedGetter(v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
@@ -439,9 +438,11 @@
   for (const auto& attribute : TestInterfaceCheckSecurityV8Internal::kCrossOriginAttributeTable)
     names.push_back(attribute.name);
 
-  v8SetReturnValue(
-      info,
-      ToV8(names, info.Holder(), info.GetIsolate()).As<v8::Array>());
+  // Use the current context as the creation context, as a cross-origin access
+  // may involve an object that does not have a creation context.
+  v8SetReturnValue(info,
+                   ToV8(names, info.GetIsolate()->GetCurrentContext()->Global(),
+                        info.GetIsolate()).As<v8::Array>());
 }
 
 // Suppress warning: global constructors, because AttributeConfiguration is trivial
diff --git a/third_party/WebKit/Source/core/css/CSSDefaultStyleSheets.cpp b/third_party/WebKit/Source/core/css/CSSDefaultStyleSheets.cpp
index b92e529..992b9a9 100644
--- a/third_party/WebKit/Source/core/css/CSSDefaultStyleSheets.cpp
+++ b/third_party/WebKit/Source/core/css/CSSDefaultStyleSheets.cpp
@@ -34,6 +34,7 @@
 #include "core/css/MediaQueryEvaluator.h"
 #include "core/css/RuleSet.h"
 #include "core/css/StyleSheetContents.h"
+#include "core/dom/Fullscreen.h"
 #include "core/html/HTMLAnchorElement.h"
 #include "core/html/HTMLHtmlElement.h"
 #include "core/layout/LayoutTheme.h"
diff --git a/third_party/WebKit/Source/core/css/SelectorChecker.cpp b/third_party/WebKit/Source/core/css/SelectorChecker.cpp
index b28a486..da7b0ec 100644
--- a/third_party/WebKit/Source/core/css/SelectorChecker.cpp
+++ b/third_party/WebKit/Source/core/css/SelectorChecker.cpp
@@ -1005,7 +1005,7 @@
       if (isHTMLFrameElementBase(element) &&
           element.containsFullScreenElement())
         return true;
-      return Fullscreen::isFullscreenElement(element);
+      return Fullscreen::isCurrentFullScreenElement(element);
     case CSSSelector::PseudoFullScreenAncestor:
       return element.containsFullScreenElement();
     case CSSSelector::PseudoInRange:
diff --git a/third_party/WebKit/Source/core/dom/DocumentFullscreen.cpp b/third_party/WebKit/Source/core/dom/DocumentFullscreen.cpp
index 2f5d0d7c..87d2577f 100644
--- a/third_party/WebKit/Source/core/dom/DocumentFullscreen.cpp
+++ b/third_party/WebKit/Source/core/dom/DocumentFullscreen.cpp
@@ -41,4 +41,8 @@
   Fullscreen::exitFullscreen(document);
 }
 
+Element* DocumentFullscreen::currentFullScreenElement(Document& document) {
+  return Fullscreen::currentFullScreenElementForBindingFrom(document);
+}
+
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/dom/DocumentFullscreen.h b/third_party/WebKit/Source/core/dom/DocumentFullscreen.h
index 5443a42..aa8da741 100644
--- a/third_party/WebKit/Source/core/dom/DocumentFullscreen.h
+++ b/third_party/WebKit/Source/core/dom/DocumentFullscreen.h
@@ -45,6 +45,9 @@
   DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(fullscreenchange);
   DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(fullscreenerror);
 
+  // Mozilla version
+  static Element* currentFullScreenElement(Document&);
+
   DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(webkitfullscreenchange);
   DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(webkitfullscreenerror);
 };
diff --git a/third_party/WebKit/Source/core/dom/DocumentFullscreen.idl b/third_party/WebKit/Source/core/dom/DocumentFullscreen.idl
index 316ebb6..23225e7 100644
--- a/third_party/WebKit/Source/core/dom/DocumentFullscreen.idl
+++ b/third_party/WebKit/Source/core/dom/DocumentFullscreen.idl
@@ -30,8 +30,8 @@
     [RuntimeEnabled=FullscreenUnprefixed] attribute EventHandler onfullscreenerror;
 
     // Mozilla version
-    [MeasureAs=PrefixedDocumentIsFullscreen, ImplementedAs=fullscreenElement] readonly attribute boolean webkitIsFullScreen;
-    [MeasureAs=PrefixedDocumentCurrentFullScreenElement, ImplementedAs=fullscreenElement] readonly attribute Element webkitCurrentFullScreenElement;
+    [MeasureAs=PrefixedDocumentIsFullscreen, ImplementedAs=currentFullScreenElement] readonly attribute boolean webkitIsFullScreen;
+    [MeasureAs=PrefixedDocumentCurrentFullScreenElement, ImplementedAs=currentFullScreenElement] readonly attribute Element webkitCurrentFullScreenElement;
     [MeasureAs=PrefixedDocumentCancelFullScreen, ImplementedAs=exitFullscreen] void webkitCancelFullScreen();
 
     // W3C version
diff --git a/third_party/WebKit/Source/core/dom/Element.cpp b/third_party/WebKit/Source/core/dom/Element.cpp
index dde22c8..81d88538 100644
--- a/third_party/WebKit/Source/core/dom/Element.cpp
+++ b/third_party/WebKit/Source/core/dom/Element.cpp
@@ -1630,7 +1630,7 @@
 
   DCHECK(!hasRareData() || !elementRareData()->hasPseudoElements());
 
-  if (Fullscreen::isFullscreenElement(*this)) {
+  if (Fullscreen::isCurrentFullScreenElement(*this)) {
     setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false);
     if (insertionPoint->isElementNode()) {
       toElement(insertionPoint)->setContainsFullScreenElement(false);
@@ -4047,7 +4047,7 @@
     return false;
   if (hasAnimations())
     return false;
-  if (Fullscreen::isFullscreenElement(*this))
+  if (Fullscreen::isCurrentFullScreenElement(*this))
     return false;
   return true;
 }
diff --git a/third_party/WebKit/Source/core/dom/Fullscreen.cpp b/third_party/WebKit/Source/core/dom/Fullscreen.cpp
index 5fbb037d..4868177 100644
--- a/third_party/WebKit/Source/core/dom/Fullscreen.cpp
+++ b/third_party/WebKit/Source/core/dom/Fullscreen.cpp
@@ -33,6 +33,7 @@
 #include "core/dom/Document.h"
 #include "core/dom/ElementTraversal.h"
 #include "core/dom/StyleEngine.h"
+#include "core/dom/TaskRunnerHelper.h"
 #include "core/events/Event.h"
 #include "core/frame/FrameView.h"
 #include "core/frame/HostsUsingFeatures.h"
@@ -186,26 +187,17 @@
   return true;
 }
 
-// https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen step 4:
-bool requestFullscreenConditionsMet(Element& pending, Document& document) {
-  // |pending|'s namespace is the HTML namespace or |pending| is an SVG svg or
-  // MathML math element. Note: MathML is not supported.
-  if (!pending.isHTMLElement() && !isSVGSVGElement(pending))
-    return false;
+bool isPrefixed(const AtomicString& type) {
+  return type == EventTypeNames::webkitfullscreenchange ||
+         type == EventTypeNames::webkitfullscreenerror;
+}
 
-  // The fullscreen element ready check for |pending| returns false.
-  if (!fullscreenElementReady(pending))
-    return false;
-
-  // Fullscreen is supported.
-  if (!fullscreenIsSupported(document))
-    return false;
-
-  // This algorithm is allowed to request fullscreen.
-  if (!allowedToRequestFullscreen(document))
-    return false;
-
-  return true;
+Event* createEvent(const AtomicString& type, EventTarget& target) {
+  EventInit initializer;
+  initializer.setBubbles(isPrefixed(type));
+  Event* event = Event::create(type, initializer);
+  event->setTarget(&target);
+  return event;
 }
 
 // Walks the frame tree and returns the first local ancestor frame, if any.
@@ -224,7 +216,7 @@
   LocalFrame* frame = document.frame();
   if (!frame)
     return nullptr;
-  LocalFrame* next = nextLocalAncestor(*frame);
+  LocalFrame* next = nextLocalAncestor(*document.frame());
   if (!next)
     return nullptr;
   DCHECK(next->document());
@@ -242,94 +234,34 @@
   return document;
 }
 
-// https://fullscreen.spec.whatwg.org/#collect-documents-to-unfullscreen
-HeapVector<Member<Document>> collectDocumentsToUnfullscreen(
-    Document& doc,
-    Fullscreen::ExitType exitType) {
-  DCHECK(Fullscreen::fullscreenElementFrom(doc));
-
-  // 1. If |doc|'s top layer consists of more than a single element that has
-  // its fullscreen flag set, return the empty set.
-  // TODO(foolip): See TODO in |fullyExitFullscreen()|.
-  if (exitType != Fullscreen::ExitType::Fully &&
-      Fullscreen::fullscreenElementStackSizeFrom(doc) > 1)
-    return HeapVector<Member<Document>>();
-
-  // 2. Let |docs| be an ordered set consisting of |doc|.
-  HeapVector<Member<Document>> docs;
-  docs.push_back(&doc);
-
-  // 3. While |docs|'s last document ...
-  //
-  // OOPIF: Skip over remote frames, assuming that they have exactly one element
-  // in their fullscreen element stacks, thereby erring on the side of exiting
-  // fullscreen. TODO(alexmos): Deal with nested fullscreen cases, see
-  // https://crbug.com/617369.
-  for (Document* document = nextLocalAncestor(doc); document;
-       document = nextLocalAncestor(*document)) {
-    // ... has a browsing context container whose node document's top layer
-    // consists of a single element that has its fullscreen flag set and does
-    // not have its iframe fullscreen flag set (if any), append that node
-    // document to |docs|.
-    // TODO(foolip): Support the iframe fullscreen flag.
-    // https://crbug.com/644695
-    if (Fullscreen::fullscreenElementStackSizeFrom(*document) == 1)
-      docs.push_back(document);
-    else
-      break;
-  }
-
-  // 4. Return |docs|.
-  return docs;
+// Helper to find the browsing context container in |doc| that embeds the
+// |descendant| Document, possibly through multiple levels of nesting.  This
+// works even in OOPIF scenarios like A-B-A, where there may be remote frames
+// in between |doc| and |descendant|.
+HTMLFrameOwnerElement* findContainerForDescendant(const Document& doc,
+                                                  const Document& descendant) {
+  Frame* frame = descendant.frame();
+  while (frame->tree().parent() != doc.frame())
+    frame = frame->tree().parent();
+  return toHTMLFrameOwnerElement(frame->owner());
 }
 
-// Creates a non-bubbling event with |document| as its target.
-Event* createEvent(const AtomicString& type, Document& document) {
-  DCHECK(type == EventTypeNames::fullscreenchange ||
-         type == EventTypeNames::fullscreenerror);
+// Fullscreen status affects scroll paint properties through
+// FrameView::userInputScrollable().
+void setNeedsPaintPropertyUpdate(Document* document) {
+  if (!RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled() ||
+      RuntimeEnabledFeatures::rootLayerScrollingEnabled())
+    return;
 
-  Event* event = Event::create(type);
-  event->setTarget(&document);
-  return event;
-}
+  if (!document)
+    return;
 
-// Creates a bubbling event with |element| as its target. If |element| is not
-// connected to |document|, then |document| is used as the target instead.
-Event* createPrefixedEvent(const AtomicString& type,
-                           Element& element,
-                           Document& document) {
-  DCHECK(type == EventTypeNames::webkitfullscreenchange ||
-         type == EventTypeNames::webkitfullscreenerror);
+  LocalFrame* frame = document->frame();
+  if (!frame)
+    return;
 
-  Event* event = Event::createBubble(type);
-  if (element.isConnected() && element.document() == document)
-    event->setTarget(&element);
-  else
-    event->setTarget(&document);
-  return event;
-}
-
-Event* createChangeEvent(Document& document,
-                         Element& element,
-                         Fullscreen::RequestType requestType) {
-  if (requestType == Fullscreen::RequestType::Unprefixed)
-    return createEvent(EventTypeNames::fullscreenchange, document);
-  return createPrefixedEvent(EventTypeNames::webkitfullscreenchange, element,
-                             document);
-}
-
-Event* createErrorEvent(Document& document,
-                        Element& element,
-                        Fullscreen::RequestType requestType) {
-  if (requestType == Fullscreen::RequestType::Unprefixed)
-    return createEvent(EventTypeNames::fullscreenerror, document);
-  return createPrefixedEvent(EventTypeNames::webkitfullscreenerror, element,
-                             document);
-}
-
-void dispatchEvents(const HeapVector<Member<Event>>& events) {
-  for (Event* event : events)
-    event->target()->dispatchEvent(event);
+  if (FrameView* frameView = frame->view())
+    frameView->setNeedsPaintPropertyUpdate();
 }
 
 }  // anonymous namespace
@@ -359,12 +291,6 @@
   return nullptr;
 }
 
-size_t Fullscreen::fullscreenElementStackSizeFrom(Document& document) {
-  if (Fullscreen* found = fromIfExists(document))
-    return found->m_fullscreenElementStack.size();
-  return 0;
-}
-
 Element* Fullscreen::fullscreenElementForBindingFrom(TreeScope& scope) {
   Element* element = fullscreenElementFrom(scope.document());
   if (!element || !RuntimeEnabledFeatures::fullscreenUnprefixedEnabled())
@@ -386,43 +312,66 @@
   return scope.adjustedElement(*element);
 }
 
+Element* Fullscreen::currentFullScreenElementFrom(Document& document) {
+  if (Fullscreen* found = fromIfExists(document))
+    return found->currentFullScreenElement();
+  return nullptr;
+}
+
+Element* Fullscreen::currentFullScreenElementForBindingFrom(
+    Document& document) {
+  Element* element = currentFullScreenElementFrom(document);
+  if (!element || !RuntimeEnabledFeatures::fullscreenUnprefixedEnabled())
+    return element;
+
+  // For Shadow DOM V0 compatibility: We allow returning an element in V0 shadow
+  // tree, even though it leaks the Shadow DOM.
+  if (element->isInV0ShadowTree()) {
+    UseCounter::count(document,
+                      UseCounter::DocumentFullscreenElementInV0Shadow);
+    return element;
+  }
+  return document.adjustedElement(*element);
+}
+
 Fullscreen::Fullscreen(Document& document)
     : Supplement<Document>(document),
       ContextLifecycleObserver(&document),
-      m_fullScreenLayoutObject(nullptr) {
+      m_fullScreenLayoutObject(nullptr),
+      m_eventQueueTimer(TaskRunnerHelper::get(TaskType::Unthrottled, &document),
+                        this,
+                        &Fullscreen::eventQueueTimerFired),
+      m_forCrossProcessDescendant(false) {
   document.setHasFullscreenSupplement();
 }
 
 Fullscreen::~Fullscreen() {}
 
-Document* Fullscreen::document() {
+inline Document* Fullscreen::document() {
   return toDocument(lifecycleContext());
 }
 
 void Fullscreen::contextDestroyed(ExecutionContext*) {
+  m_eventQueue.clear();
+
   if (m_fullScreenLayoutObject)
     m_fullScreenLayoutObject->destroy();
 
-  m_pendingRequests.clear();
+  m_currentFullScreenElement = nullptr;
   m_fullscreenElementStack.clear();
 }
 
 // https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen
-void Fullscreen::requestFullscreen(Element& pending) {
+void Fullscreen::requestFullscreen(Element& element) {
   // TODO(foolip): Make RequestType::Unprefixed the default when the unprefixed
   // API is enabled. https://crbug.com/383813
-  requestFullscreen(pending, RequestType::Prefixed);
+  requestFullscreen(element, RequestType::Prefixed, false);
 }
 
-void Fullscreen::requestFullscreen(Element& pending, RequestType requestType) {
-  Document& document = pending.document();
-
-  // Ignore this call if the document is not in a live frame.
-  if (!document.isActive() || !document.frame())
-    return;
-
-  bool forCrossProcessDescendant =
-      requestType == RequestType::PrefixedForCrossProcessDescendant;
+void Fullscreen::requestFullscreen(Element& element,
+                                   RequestType requestType,
+                                   bool forCrossProcessDescendant) {
+  Document& document = element.document();
 
   // Use counters only need to be incremented in the process of the actual
   // fullscreen element.
@@ -436,326 +385,229 @@
     }
   }
 
-  // 1. Let |pending| be the context object.
-
-  // 2. Let |error| be false.
-  bool error = false;
-
-  // 3. Let |promise| be a new promise.
-  // TODO(foolip): Promises. https://crbug.com/644637
-
-  // 4. If any of the following conditions are false, then set |error| to true:
-  //
-  // OOPIF: If |requestFullscreen()| was already called in a descendant frame
-  // and passed the checks, do not check again here.
-  if (!forCrossProcessDescendant &&
-      !requestFullscreenConditionsMet(pending, document))
-    error = true;
-
-  // 5. Return |promise|, and run the remaining steps in parallel.
-  // TODO(foolip): Promises. https://crbug.com/644637
-
-  // 6. If |error| is false: Resize pending's top-level browsing context's
-  // document's viewport's dimensions to match the dimensions of the screen of
-  // the output device. Optionally display a message how the end user can
-  // revert this.
-  if (!error) {
-    from(document).m_pendingRequests.push_back(
-        std::make_pair(&pending, requestType));
-    LocalFrame& frame = *document.frame();
-    frame.chromeClient().enterFullscreen(frame);
-  } else {
-    enqueueTaskForRequest(document, pending, requestType, true);
-  }
-}
-
-void Fullscreen::didEnterFullscreen() {
-  if (!document())
+  // Ignore this call if the document is not in a live frame.
+  if (!document.isActive() || !document.frame())
     return;
 
-  ElementStack requests;
-  requests.swap(m_pendingRequests);
-  for (const ElementStackEntry& request : requests)
-    enqueueTaskForRequest(*document(), *request.first, request.second, false);
-}
-
-void Fullscreen::enqueueTaskForRequest(Document& document,
-                                       Element& pending,
-                                       RequestType requestType,
-                                       bool error) {
-  // 7. As part of the next animation frame task, run these substeps:
-  document.enqueueAnimationFrameTask(
-      WTF::bind(&runTaskForRequest, wrapPersistent(&document),
-                wrapPersistent(&pending), requestType, error));
-}
-
-void Fullscreen::runTaskForRequest(Document* document,
-                                   Element* element,
-                                   RequestType requestType,
-                                   bool error) {
-  DCHECK(document);
-  DCHECK(document->isActive());
-  DCHECK(document->frame());
-  DCHECK(element);
-
-  Document& pendingDoc = *document;
-  Element& pending = *element;
-
-  // TODO(foolip): Spec something like: If |pending|'s node document is not
-  // |pendingDoc|, then set |error| to true.
-  // https://github.com/whatwg/fullscreen/issues/33
-  if (pending.document() != pendingDoc)
-    error = true;
-
-  // 7.1. If either |error| is true or the fullscreen element ready check for
-  // |pending| returns false, fire an event named fullscreenerror on
-  // |pending|'s node document, reject |promise| with a TypeError exception,
-  // and terminate these steps.
-  // TODO(foolip): Promises. https://crbug.com/644637
-  if (error || !fullscreenElementReady(pending)) {
-    Event* event = createErrorEvent(pendingDoc, pending, requestType);
-    event->target()->dispatchEvent(event);
+  // If |element| is on top of |doc|'s fullscreen element stack, terminate these
+  // substeps.
+  if (&element == fullscreenElementFrom(document))
     return;
-  }
 
-  // 7.2. Let |fullscreenElements| be an ordered set initially consisting of
-  // |pending|.
-  HeapDeque<Member<Element>> fullscreenElements;
-  fullscreenElements.append(pending);
+  do {
+    // 1. If any of the following conditions are false, then terminate these
+    // steps and queue a task to fire an event named fullscreenerror with its
+    // bubbles attribute set to true on the context object's node document:
 
-  // 7.3. While the first element in |fullscreenElements| is in a nested
-  // browsing context, prepend its browsing context container to
-  // |fullscreenElements|.
-  //
-  // OOPIF: |fullscreenElements| will only contain elements for local ancestors,
-  // and remote ancestors will be processed in their respective processes. This
-  // preserves the spec's event firing order for local ancestors, but not for
-  // remote ancestors. However, that difference shouldn't be observable in
-  // practice: a fullscreenchange event handler would need to postMessage a
-  // frame in another renderer process, where the message should be queued up
-  // and processed after the IPC that dispatches fullscreenchange.
-  for (Frame* frame = pending.document().frame(); frame;
-       frame = frame->tree().parent()) {
-    if (!frame->owner() || !frame->owner()->isLocal())
-      continue;
-    Element* element = toHTMLFrameOwnerElement(frame->owner());
-    fullscreenElements.prepend(element);
-  }
+    // |element|'s namespace is the HTML namespace or |element| is an SVG
+    // svg or MathML math element.
+    // Note: MathML is not supported.
+    if (!element.isHTMLElement() && !isSVGSVGElement(element))
+      break;
 
-  // 7.4. Let |eventDocs| be an empty list.
-  // Note: For prefixed requests, the event target is an element, so instead
-  // let |events| be a list of events to dispatch.
-  HeapVector<Member<Event>> events;
+    // The fullscreen element ready check for |element| returns true.
+    if (!fullscreenElementReady(element))
+      break;
 
-  // 7.5. For each |element| in |fullscreenElements|, in order, run these
-  // subsubsteps:
-  for (Element* element : fullscreenElements) {
-    // 7.5.1. Let |doc| be |element|'s node document.
-    Document& doc = element->document();
+    // Fullscreen is supported.
+    if (!fullscreenIsSupported(document))
+      break;
 
-    // 7.5.2. If |element| is |doc|'s fullscreen element, terminate these
-    // subsubsteps.
-    if (element == fullscreenElementFrom(doc))
-      continue;
+    // This algorithm is allowed to request fullscreen.
+    // OOPIF: If |forCrossProcessDescendant| is true, requestFullscreen was
+    // already called on a descendant element in another process, and
+    // getting here means that it was already allowed to request fullscreen.
+    if (!forCrossProcessDescendant && !allowedToRequestFullscreen(document))
+      break;
 
-    // 7.5.3. Otherwise, append |doc| to |eventDocs|.
-    events.push_back(createChangeEvent(doc, *element, requestType));
+    // 2. Let doc be element's node document. (i.e. "this")
 
-    // 7.5.4. If |element| is |pending| and |pending| is an iframe element,
-    // set |element|'s iframe fullscreen flag.
-    // TODO(foolip): Support the iframe fullscreen flag.
-    // https://crbug.com/644695
+    // 3. Let docs be all doc's ancestor browsing context's documents (if any)
+    // and doc.
+    //
+    // For OOPIF scenarios, |docs| will only contain documents for local
+    // ancestors, and remote ancestors will be processed in their
+    // respective processes.  This preserves the spec's event firing order
+    // for local ancestors, but not for remote ancestors.  However, that
+    // difference shouldn't be observable in practice: a fullscreenchange
+    // event handler would need to postMessage a frame in another renderer
+    // process, where the message should be queued up and processed after
+    // the IPC that dispatches fullscreenchange.
+    HeapDeque<Member<Document>> docs;
+    for (Document* doc = &document; doc; doc = nextLocalAncestor(*doc))
+      docs.prepend(doc);
 
-    // 7.5.5. Fullscreen |element| within |doc|.
-    // TODO(foolip): Merge fullscreen element stack into top layer.
-    // https://crbug.com/627790
-    from(doc).pushFullscreenElementStack(*element, requestType);
-  }
+    // 4. For each document in docs, run these substeps:
+    HeapDeque<Member<Document>>::iterator current = docs.begin(),
+                                          following = docs.begin();
 
-  // 7.6. For each |doc| in |eventDocs|, in order, fire an event named
-  // fullscreenchange on |doc|.
-  dispatchEvents(events);
+    do {
+      ++following;
 
-  // 7.7. Fulfill |promise| with undefined.
-  // TODO(foolip): Promises. https://crbug.com/644637
+      // 1. Let following document be the document after document in docs, or
+      // null if there is no such document.
+      Document* currentDoc = *current;
+      Document* followingDoc = following != docs.end() ? *following : nullptr;
+
+      // 2. If following document is null, push context object on document's
+      // fullscreen element stack, and queue a task to fire an event named
+      // fullscreenchange with its bubbles attribute set to true on the
+      // document.
+      if (!followingDoc) {
+        from(*currentDoc).pushFullscreenElementStack(element, requestType);
+        from(document).enqueueChangeEvent(*currentDoc, requestType);
+        continue;
+      }
+
+      // 3. Otherwise, if document's fullscreen element stack is either empty or
+      // its top element is not following document's browsing context container,
+      Element* topElement = fullscreenElementFrom(*currentDoc);
+      HTMLFrameOwnerElement* followingOwner =
+          findContainerForDescendant(*currentDoc, *followingDoc);
+      if (!topElement || topElement != followingOwner) {
+        // ...push following document's browsing context container on document's
+        // fullscreen element stack, and queue a task to fire an event named
+        // fullscreenchange with its bubbles attribute set to true on document.
+        from(*currentDoc)
+            .pushFullscreenElementStack(*followingOwner, requestType);
+        from(document).enqueueChangeEvent(*currentDoc, requestType);
+        continue;
+      }
+
+      // 4. Otherwise, do nothing for this document. It stays the same.
+    } while (++current != docs.end());
+
+    from(document).m_forCrossProcessDescendant = forCrossProcessDescendant;
+
+    // 5. Return, and run the remaining steps asynchronously.
+    // 6. Optionally, perform some animation.
+    from(document).m_pendingFullscreenElement = &element;
+    document.frame()->chromeClient().enterFullscreen(*document.frame());
+
+    // 7. Optionally, display a message indicating how the user can exit
+    // displaying the context object fullscreen.
+    return;
+  } while (false);
+
+  from(document).enqueueErrorEvent(element, requestType);
 }
 
 // https://fullscreen.spec.whatwg.org/#fully-exit-fullscreen
 void Fullscreen::fullyExitFullscreen(Document& document) {
-  // 1. If |document|'s fullscreen element is null, terminate these steps.
+  // To fully exit fullscreen, run these steps:
 
-  // 2. Unfullscreen elements whose fullscreen flag is set, within
-  // |document|'s top layer, except for |document|'s fullscreen element.
+  // 1. Let |doc| be the top-level browsing context's document.
+  //
+  // Since the top-level browsing context's document might be unavailable in
+  // OOPIF scenarios (i.e., when the top frame is remote), this actually uses
+  // the Document of the topmost local ancestor frame.  Without OOPIF, this
+  // will be the top frame's document.  With OOPIF, each renderer process for
+  // the current page will separately call fullyExitFullscreen to cover all
+  // local frames in each process.
+  Document& doc = topmostLocalAncestor(document);
 
-  // 3. Exit fullscreen |document|.
+  // 2. If |doc|'s fullscreen element stack is empty, terminate these steps.
+  if (!fullscreenElementFrom(doc))
+    return;
 
-  // TODO(foolip): Change the spec. To remove elements from |document|'s top
-  // layer as in step 2 could leave descendant frames in fullscreen. It may work
-  // to give the "exit fullscreen" algorithm a |fully| flag that's used in the
-  // animation frame task after exit. Here, retain the old behavior of fully
-  // exiting fullscreen for the topmost local ancestor:
-  exitFullscreen(topmostLocalAncestor(document), ExitType::Fully);
+  // 3. Remove elements from |doc|'s fullscreen element stack until only the top
+  // element is left.
+  size_t stackSize = from(doc).m_fullscreenElementStack.size();
+  from(doc).m_fullscreenElementStack.remove(0, stackSize - 1);
+  DCHECK_EQ(from(doc).m_fullscreenElementStack.size(), 1u);
+
+  // 4. Act as if the exitFullscreen() method was invoked on |doc|.
+  exitFullscreen(doc);
 }
 
 // https://fullscreen.spec.whatwg.org/#exit-fullscreen
-void Fullscreen::exitFullscreen(Document& doc, ExitType exitType) {
-  if (!doc.isActive() || !doc.frame())
+void Fullscreen::exitFullscreen(Document& document) {
+  // The exitFullscreen() method must run these steps:
+
+  // Ignore this call if the document is not in a live frame.
+  if (!document.isActive() || !document.frame())
     return;
 
-  // 1. Let |promise| be a new promise.
-  // 2. If |doc|'s fullscreen element is null, reject |promise| with a
-  // TypeError exception, and return |promise|.
-  // TODO(foolip): Promises. https://crbug.com/644637
-  if (!fullscreenElementFrom(doc))
+  // 1. Let doc be the context object. (i.e. "this")
+  // 2. If doc's fullscreen element stack is empty, terminate these steps.
+  if (!fullscreenElementFrom(document))
     return;
 
-  // 3. Let |resize| be false.
-  bool resize = false;
-
-  // 4. Let |docs| be the result of collecting documents to unfullscreen given
-  // |doc|.
-  HeapVector<Member<Document>> docs =
-      collectDocumentsToUnfullscreen(doc, exitType);
-
-  // 5. Let |topLevelDoc| be |doc|'s top-level browsing context's document.
-  //
-  // OOPIF: Let |topLevelDoc| be the topmost local ancestor instead. If the main
-  // frame is in another process, we will still fully exit fullscreen even
-  // though that's wrong if the main frame was in nested fullscreen.
-  // TODO(alexmos): Deal with nested fullscreen cases, see
-  // https://crbug.com/617369.
-  Document& topLevelDoc = topmostLocalAncestor(doc);
-
-  // 6. If |topLevelDoc| is in |docs|, set |resize| to true.
-  if (!docs.isEmpty() && docs.back() == &topLevelDoc)
-    resize = true;
-
-  // 7. Return |promise|, and run the remaining steps in parallel.
-  // TODO(foolip): Promises. https://crbug.com/644637
-
-  // Note: |ExitType::Fully| is only used together with the topmost local
-  // ancestor in |fullyExitFullscreen()|, and so implies that |resize| is true.
-  // This would change if matching the spec for "fully exit fullscreen".
-  if (exitType == ExitType::Fully)
-    DCHECK(resize);
-
-  // 8. If |resize| is true, resize |topLevelDoc|'s viewport to its "normal"
-  // dimensions.
-  if (resize) {
-    LocalFrame& frame = *doc.frame();
-    frame.chromeClient().exitFullscreen(frame);
-  } else {
-    enqueueTaskForExit(doc, exitType);
-  }
-}
-
-void Fullscreen::didExitFullscreen() {
-  if (!document())
-    return;
-
-  DCHECK_EQ(document(), &topmostLocalAncestor(*document()));
-
-  enqueueTaskForExit(*document(), ExitType::Fully);
-}
-
-void Fullscreen::enqueueTaskForExit(Document& document, ExitType exitType) {
-  // 9. As part of the next animation frame task, run these substeps:
-  document.enqueueAnimationFrameTask(
-      WTF::bind(&runTaskForExit, wrapPersistent(&document), exitType));
-}
-
-void Fullscreen::runTaskForExit(Document* document, ExitType exitType) {
-  DCHECK(document);
-  DCHECK(document->isActive());
-  DCHECK(document->frame());
-
-  Document& doc = *document;
-
-  if (!fullscreenElementFrom(doc))
-    return;
-
-  // 9.1. Let |exitDocs| be the result of collecting documents to unfullscreen
-  // given |doc|.
-
-  // 9.2. If |resize| is true and |topLevelDoc| is not in |exitDocs|, fully
-  // exit fullscreen |topLevelDoc|, reject promise with a TypeError exception,
-  // and terminate these steps.
-
-  // TODO(foolip): See TODO in |fullyExitFullscreen()|. Instead of using "fully
-  // exit fullscreen" in step 9.2 (which is async), give "exit fullscreen" a
-  // |fully| flag which is always true if |resize| was true.
-
-  HeapVector<Member<Document>> exitDocs =
-      collectDocumentsToUnfullscreen(doc, exitType);
-
-  // 9.3. If |exitDocs| is the empty set, append |doc| to |exitDocs|.
-  if (exitDocs.isEmpty())
-    exitDocs.push_back(&doc);
-
-  // 9.4. If |exitDocs|'s last document has a browsing context container,
-  // append that browsing context container's node document to |exitDocs|.
-  //
-  // OOPIF: Skip over remote frames, assuming that they have exactly one element
-  // in their fullscreen element stacks, thereby erring on the side of exiting
-  // fullscreen. TODO(alexmos): Deal with nested fullscreen cases, see
-  // https://crbug.com/617369.
-  if (Document* document = nextLocalAncestor(*exitDocs.back()))
-    exitDocs.push_back(document);
-
-  // 9.5. Let |descendantDocs| be an ordered set consisting of |doc|'s
-  // descendant browsing contexts' documents whose fullscreen element is
-  // non-null, if any, in *reverse* tree order.
-  HeapDeque<Member<Document>> descendantDocs;
-  for (Frame* descendant = doc.frame()->tree().firstChild(); descendant;
-       descendant = descendant->tree().traverseNext(doc.frame())) {
+  // 3. Let descendants be all the doc's descendant browsing context's documents
+  // with a non-empty fullscreen element stack (if any), ordered so that the
+  // child of the doc is last and the document furthest away from the doc is
+  // first.
+  HeapDeque<Member<Document>> descendants;
+  for (Frame* descendant = document.frame()->tree().traverseNext(); descendant;
+       descendant = descendant->tree().traverseNext()) {
     if (!descendant->isLocalFrame())
       continue;
     DCHECK(toLocalFrame(descendant)->document());
     if (fullscreenElementFrom(*toLocalFrame(descendant)->document()))
-      descendantDocs.prepend(toLocalFrame(descendant)->document());
+      descendants.prepend(toLocalFrame(descendant)->document());
   }
 
-  // Note: For prefixed requests, the event target is an element, so let
-  // |events| be a list of events to dispatch.
-  HeapVector<Member<Event>> events;
-
-  // 9.6. For each |descendantDoc| in |descendantDocs|, in order, unfullscreen
-  // |descendantDoc|.
-  for (Document* descendantDoc : descendantDocs) {
-    Fullscreen& fullscreen = from(*descendantDoc);
-    ElementStack& stack = fullscreen.m_fullscreenElementStack;
-    DCHECK(!stack.isEmpty());
-    events.push_back(createChangeEvent(*descendantDoc, *stack.back().first,
-                                       stack.back().second));
-    while (!stack.isEmpty())
-      fullscreen.popFullscreenElementStack();
+  // 4. For each descendant in descendants, empty descendant's fullscreen
+  // element stack, and queue a task to fire an event named fullscreenchange
+  // with its bubbles attribute set to true on descendant.
+  for (auto& descendant : descendants) {
+    DCHECK(descendant);
+    RequestType requestType =
+        from(*descendant).m_fullscreenElementStack.back().second;
+    from(*descendant).clearFullscreenElementStack();
+    from(document).enqueueChangeEvent(*descendant, requestType);
   }
 
-  // 9.7. For each |exitDoc| in |exitDocs|, in order, unfullscreen |exitDoc|'s
-  // fullscreen element.
-  for (Document* exitDoc : exitDocs) {
-    Fullscreen& fullscreen = from(*exitDoc);
-    ElementStack& stack = fullscreen.m_fullscreenElementStack;
-    DCHECK(!stack.isEmpty());
-    events.push_back(
-        createChangeEvent(*exitDoc, *stack.back().first, stack.back().second));
-    fullscreen.popFullscreenElementStack();
+  // 5. While doc is not null, run these substeps:
+  Element* newTop = nullptr;
+  for (Document* currentDoc = &document; currentDoc;) {
+    RequestType requestType =
+        from(*currentDoc).m_fullscreenElementStack.back().second;
 
-    // TODO(foolip): See TODO in |fullyExitFullscreen()|.
-    if (exitDoc == &doc && exitType == ExitType::Fully) {
-      while (!stack.isEmpty())
-        fullscreen.popFullscreenElementStack();
+    // 1. Pop the top element of doc's fullscreen element stack.
+    from(*currentDoc).popFullscreenElementStack();
+
+    //    If doc's fullscreen element stack is non-empty and the element now at
+    //    the top is either not in a document or its node document is not doc,
+    //    repeat this substep.
+    newTop = fullscreenElementFrom(*currentDoc);
+    if (newTop && (!newTop->isConnected() || newTop->document() != currentDoc))
+      continue;
+
+    // 2. Queue a task to fire an event named fullscreenchange with its bubbles
+    // attribute set to true on doc.
+    from(document).enqueueChangeEvent(*currentDoc, requestType);
+
+    // 3. If doc's fullscreen element stack is empty and doc's browsing context
+    // has a browsing context container, set doc to that browsing context
+    // container's node document.
+    //
+    // OOPIF: If browsing context container's document is in another
+    // process, keep moving up the ancestor chain and looking for a
+    // browsing context container with a local document.
+    // TODO(alexmos): Deal with nested fullscreen cases, see
+    // https://crbug.com/617369.
+    if (!newTop) {
+      currentDoc = nextLocalAncestor(*currentDoc);
+      continue;
     }
+
+    // 4. Otherwise, set doc to null.
+    currentDoc = nullptr;
   }
 
-  // 9.8. For each |descendantDoc| in |descendantDocs|, in order, fire an
-  // event named fullscreenchange on |descendantDoc|.
-  // 9.9. For each |exitDoc| in |exitDocs|, in order, fire an event named
-  // fullscreenchange on |exitDoc|.
-  dispatchEvents(events);
+  // 6. Return, and run the remaining steps asynchronously.
+  // 7. Optionally, perform some animation.
 
-  // 9.10. Fulfill |promise| with undefined.
-  // TODO(foolip): Promises. https://crbug.com/644637
+  // Only exit fullscreen mode if the fullscreen element stack is empty.
+  if (!newTop) {
+    document.frame()->chromeClient().exitFullscreen(*document.frame());
+    return;
+  }
+
+  // Otherwise, enter fullscreen for the fullscreen element stack's top element.
+  from(document).m_pendingFullscreenElement = newTop;
+  from(document).didEnterFullscreen();
 }
 
 // https://fullscreen.spec.whatwg.org/#dom-document-fullscreenenabled
@@ -767,6 +619,124 @@
          fullscreenIsSupported(document);
 }
 
+void Fullscreen::didEnterFullscreen() {
+  if (!document()->isActive() || !document()->frame())
+    return;
+
+  // Start the timer for events enqueued by |requestFullscreen()|. The hover
+  // state update is scheduled first so that it's done when the events fire.
+  document()->frame()->eventHandler().scheduleHoverStateUpdate();
+  m_eventQueueTimer.startOneShot(0, BLINK_FROM_HERE);
+
+  Element* element = m_pendingFullscreenElement.release();
+  if (!element)
+    return;
+
+  if (m_currentFullScreenElement == element)
+    return;
+
+  if (!element->isConnected() || &element->document() != document()) {
+    // The element was removed or has moved to another document since the
+    // |requestFullscreen()| call. Exit fullscreen again to recover.
+    // TODO(foolip): Fire a fullscreenerror event. This is currently difficult
+    // because the fullscreenchange event has already been enqueued and possibly
+    // even fired. https://crbug.com/402376
+    LocalFrame& frame = *document()->frame();
+    frame.chromeClient().exitFullscreen(frame);
+    return;
+  }
+
+  if (m_fullScreenLayoutObject)
+    m_fullScreenLayoutObject->unwrapLayoutObject();
+
+  Element* previousElement = m_currentFullScreenElement;
+  m_currentFullScreenElement = element;
+
+  // Create a placeholder block for a the full-screen element, to keep the page
+  // from reflowing when the element is removed from the normal flow. Only do
+  // this for a LayoutBox, as only a box will have a frameRect. The placeholder
+  // will be created in setFullScreenLayoutObject() during layout.
+  LayoutObject* layoutObject = m_currentFullScreenElement->layoutObject();
+  bool shouldCreatePlaceholder = layoutObject && layoutObject->isBox();
+  if (shouldCreatePlaceholder) {
+    m_savedPlaceholderFrameRect = toLayoutBox(layoutObject)->frameRect();
+    m_savedPlaceholderComputedStyle =
+        ComputedStyle::clone(layoutObject->styleRef());
+  }
+
+  // TODO(alexmos): When |m_forCrossProcessDescendant| is true, some of
+  // this layout work has already been done in another process, so it should
+  // not be necessary to repeat it here.
+  if (m_currentFullScreenElement != document()->documentElement()) {
+    LayoutFullScreen::wrapLayoutObject(
+        layoutObject, layoutObject ? layoutObject->parent() : 0, document());
+  }
+
+  // When |m_forCrossProcessDescendant| is true, m_currentFullScreenElement
+  // corresponds to the HTMLFrameOwnerElement for the out-of-process iframe
+  // that contains the actual fullscreen element.   Hence, it must also set
+  // the ContainsFullScreenElement flag (so that it gains the
+  // -webkit-full-screen-ancestor style).
+  if (m_forCrossProcessDescendant) {
+    DCHECK(m_currentFullScreenElement->isFrameOwnerElement());
+    DCHECK(toHTMLFrameOwnerElement(m_currentFullScreenElement)
+               ->contentFrame()
+               ->isRemoteFrame());
+    m_currentFullScreenElement->setContainsFullScreenElement(true);
+  }
+
+  m_currentFullScreenElement
+      ->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(true);
+
+  document()->styleEngine().ensureUAStyleForFullscreen();
+  m_currentFullScreenElement->pseudoStateChanged(CSSSelector::PseudoFullScreen);
+
+  // FIXME: This should not call updateStyleAndLayoutTree.
+  document()->updateStyleAndLayoutTree();
+
+  document()->frame()->chromeClient().fullscreenElementChanged(previousElement,
+                                                               element);
+}
+
+void Fullscreen::didExitFullscreen() {
+  if (!document()->isActive() || !document()->frame())
+    return;
+
+  // Start the timer for events enqueued by |exitFullscreen()|. The hover state
+  // update is scheduled first so that it's done when the events fire.
+  document()->frame()->eventHandler().scheduleHoverStateUpdate();
+  m_eventQueueTimer.startOneShot(0, BLINK_FROM_HERE);
+
+  // If fullscreen was canceled by the browser, e.g. if the user pressed Esc,
+  // then |exitFullscreen()| was never called. Let |fullyExitFullscreen()| clear
+  // the fullscreen element stack and fire any events as necessary.
+  // TODO(foolip): Remove this when state changes and events are synchronized
+  // with animation frames. https://crbug.com/402376
+  fullyExitFullscreen(*document());
+
+  if (!m_currentFullScreenElement)
+    return;
+
+  if (m_forCrossProcessDescendant)
+    m_currentFullScreenElement->setContainsFullScreenElement(false);
+
+  m_currentFullScreenElement
+      ->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false);
+
+  if (m_fullScreenLayoutObject)
+    LayoutFullScreenItem(m_fullScreenLayoutObject).unwrapLayoutObject();
+
+  document()->styleEngine().ensureUAStyleForFullscreen();
+  m_currentFullScreenElement->pseudoStateChanged(CSSSelector::PseudoFullScreen);
+  Element* previousElement = m_currentFullScreenElement;
+  m_currentFullScreenElement = nullptr;
+
+  m_forCrossProcessDescendant = false;
+
+  document()->frame()->chromeClient().fullscreenElementChanged(previousElement,
+                                                               nullptr);
+}
+
 void Fullscreen::setFullScreenLayoutObject(LayoutFullScreen* layoutObject) {
   if (layoutObject == m_fullScreenLayoutObject)
     return;
@@ -793,9 +763,56 @@
   m_fullScreenLayoutObject = nullptr;
 }
 
-void Fullscreen::elementRemoved(Element& oldNode) {
-  DCHECK_EQ(document(), &oldNode.document());
+void Fullscreen::enqueueChangeEvent(Document& document,
+                                    RequestType requestType) {
+  Event* event;
+  if (requestType == RequestType::Unprefixed) {
+    event = createEvent(EventTypeNames::fullscreenchange, document);
+  } else {
+    DCHECK(document.hasFullscreenSupplement());
+    Fullscreen& fullscreen = from(document);
+    EventTarget* target = fullscreen.fullscreenElement();
+    if (!target)
+      target = fullscreen.currentFullScreenElement();
+    if (!target)
+      target = &document;
+    event = createEvent(EventTypeNames::webkitfullscreenchange, *target);
+  }
+  m_eventQueue.append(event);
+  // NOTE: The timer is started in didEnterFullscreen/didExitFullscreen.
+}
 
+void Fullscreen::enqueueErrorEvent(Element& element, RequestType requestType) {
+  Event* event;
+  if (requestType == RequestType::Unprefixed)
+    event = createEvent(EventTypeNames::fullscreenerror, element.document());
+  else
+    event = createEvent(EventTypeNames::webkitfullscreenerror, element);
+  m_eventQueue.append(event);
+  m_eventQueueTimer.startOneShot(0, BLINK_FROM_HERE);
+}
+
+void Fullscreen::eventQueueTimerFired(TimerBase*) {
+  HeapDeque<Member<Event>> eventQueue;
+  m_eventQueue.swap(eventQueue);
+
+  while (!eventQueue.isEmpty()) {
+    Event* event = eventQueue.takeFirst();
+    Node* target = event->target()->toNode();
+
+    // If the element was removed from our tree, also message the
+    // documentElement.
+    if (!target->isConnected() && document()->documentElement()) {
+      DCHECK(isPrefixed(event->type()));
+      eventQueue.append(
+          createEvent(event->type(), *document()->documentElement()));
+    }
+
+    target->dispatchEvent(event);
+  }
+}
+
+void Fullscreen::elementRemoved(Element& oldNode) {
   // Whenever the removing steps run with an |oldNode| and |oldNode| is in its
   // node document's fullscreen element stack, run these steps:
 
@@ -818,106 +835,36 @@
   // NOTE: |oldNode| was not in the fullscreen element stack.
 }
 
-void Fullscreen::popFullscreenElementStack() {
-  DCHECK(!m_fullscreenElementStack.isEmpty());
+void Fullscreen::clearFullscreenElementStack() {
+  if (m_fullscreenElementStack.isEmpty())
+    return;
 
-  Element* previousElement = fullscreenElement();
+  m_fullscreenElementStack.clear();
+
+  setNeedsPaintPropertyUpdate(document());
+}
+
+void Fullscreen::popFullscreenElementStack() {
+  if (m_fullscreenElementStack.isEmpty())
+    return;
+
   m_fullscreenElementStack.pop_back();
 
-  // Note: |requestType| is only used if |fullscreenElement()| is non-null.
-  RequestType requestType = m_fullscreenElementStack.isEmpty()
-                                ? RequestType::Unprefixed
-                                : m_fullscreenElementStack.back().second;
-  fullscreenElementChanged(previousElement, fullscreenElement(), requestType);
+  setNeedsPaintPropertyUpdate(document());
 }
 
 void Fullscreen::pushFullscreenElementStack(Element& element,
                                             RequestType requestType) {
-  Element* previousElement = fullscreenElement();
   m_fullscreenElementStack.push_back(std::make_pair(&element, requestType));
 
-  fullscreenElementChanged(previousElement, &element, requestType);
-}
-
-void Fullscreen::fullscreenElementChanged(Element* fromElement,
-                                          Element* toElement,
-                                          RequestType toRequestType) {
-  DCHECK_NE(fromElement, toElement);
-
-  if (!document())
-    return;
-
-  document()->styleEngine().ensureUAStyleForFullscreen();
-
-  if (m_fullScreenLayoutObject)
-    m_fullScreenLayoutObject->unwrapLayoutObject();
-  DCHECK(!m_fullScreenLayoutObject);
-
-  if (fromElement) {
-    DCHECK_NE(fromElement, fullscreenElement());
-
-    fromElement->pseudoStateChanged(CSSSelector::PseudoFullScreen);
-
-    fromElement->setContainsFullScreenElement(false);
-    fromElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(
-        false);
-  }
-
-  if (toElement) {
-    DCHECK_EQ(toElement, fullscreenElement());
-
-    toElement->pseudoStateChanged(CSSSelector::PseudoFullScreen);
-
-    // OOPIF: For RequestType::PrefixedForCrossProcessDescendant, |toElement| is
-    // the iframe element for the out-of-process frame that contains the
-    // fullscreen element. Hence, it must match :-webkit-full-screen-ancestor.
-    if (toRequestType == RequestType::PrefixedForCrossProcessDescendant) {
-      DCHECK(isHTMLIFrameElement(toElement));
-      toElement->setContainsFullScreenElement(true);
-    }
-    toElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(
-        true);
-
-    // Create a placeholder block for the fullscreen element, to keep the page
-    // from reflowing when the element is removed from the normal flow. Only do
-    // this for a LayoutBox, as only a box will have a frameRect. The
-    // placeholder will be created in setFullScreenLayoutObject() during layout.
-    LayoutObject* layoutObject = toElement->layoutObject();
-    bool shouldCreatePlaceholder = layoutObject && layoutObject->isBox();
-    if (shouldCreatePlaceholder) {
-      m_savedPlaceholderFrameRect = toLayoutBox(layoutObject)->frameRect();
-      m_savedPlaceholderComputedStyle =
-          ComputedStyle::clone(layoutObject->styleRef());
-    }
-
-    if (toElement != document()->documentElement()) {
-      LayoutFullScreen::wrapLayoutObject(
-          layoutObject, layoutObject ? layoutObject->parent() : 0, document());
-    }
-  }
-
-  if (LocalFrame* frame = document()->frame()) {
-    // TODO(foolip): Synchronize hover state changes with animation frames.
-    // https://crbug.com/668758
-    frame->eventHandler().scheduleHoverStateUpdate();
-    frame->chromeClient().fullscreenElementChanged(fromElement, toElement);
-
-    if (RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled() &&
-        !RuntimeEnabledFeatures::rootLayerScrollingEnabled()) {
-      // Fullscreen status affects scroll paint properties through
-      // FrameView::userInputScrollable().
-      if (FrameView* frameView = frame->view())
-        frameView->setNeedsPaintPropertyUpdate();
-    }
-  }
-
-  // TODO(foolip): This should not call updateStyleAndLayoutTree.
-  document()->updateStyleAndLayoutTree();
+  setNeedsPaintPropertyUpdate(document());
 }
 
 DEFINE_TRACE(Fullscreen) {
-  visitor->trace(m_pendingRequests);
+  visitor->trace(m_pendingFullscreenElement);
   visitor->trace(m_fullscreenElementStack);
+  visitor->trace(m_currentFullScreenElement);
+  visitor->trace(m_eventQueue);
   Supplement<Document>::trace(visitor);
   ContextLifecycleObserver::trace(visitor);
 }
diff --git a/third_party/WebKit/Source/core/dom/Fullscreen.h b/third_party/WebKit/Source/core/dom/Fullscreen.h
index 3cdc8331..36f961a 100644
--- a/third_party/WebKit/Source/core/dom/Fullscreen.h
+++ b/third_party/WebKit/Source/core/dom/Fullscreen.h
@@ -35,6 +35,7 @@
 #include "core/dom/Document.h"
 #include "core/dom/Element.h"
 #include "platform/Supplementable.h"
+#include "platform/Timer.h"
 #include "platform/geometry/LayoutRect.h"
 #include "wtf/Deque.h"
 #include "wtf/RefPtr.h"
@@ -58,8 +59,9 @@
   static Fullscreen* fromIfExists(Document&);
   static Element* fullscreenElementFrom(Document&);
   static Element* fullscreenElementForBindingFrom(TreeScope&);
-  static size_t fullscreenElementStackSizeFrom(Document&);
-  static bool isFullscreenElement(const Element&);
+  static Element* currentFullScreenElementFrom(Document&);
+  static Element* currentFullScreenElementForBindingFrom(Document&);
+  static bool isCurrentFullScreenElement(const Element&);
 
   enum class RequestType {
     // Element.requestFullscreen()
@@ -67,26 +69,25 @@
     // Element.webkitRequestFullscreen()/webkitRequestFullScreen() and
     // HTMLVideoElement.webkitEnterFullscreen()/webkitEnterFullScreen()
     Prefixed,
-    // For WebRemoteFrameImpl to notify that a cross-process descendant frame
-    // has requested and is about to enter fullscreen.
-    PrefixedForCrossProcessDescendant,
   };
 
   static void requestFullscreen(Element&);
-  static void requestFullscreen(Element&, RequestType);
+
+  // |forCrossProcessDescendant| is used in OOPIF scenarios and is set to
+  // true when fullscreen is requested for an out-of-process descendant
+  // element.
+  static void requestFullscreen(Element&,
+                                RequestType,
+                                bool forCrossProcessDescendant = false);
 
   static void fullyExitFullscreen(Document&);
-
-  enum class ExitType {
-    // Exits fullscreen for one element in the document.
-    Default,
-    // Fully exits fullscreen for the document.
-    Fully,
-  };
-
-  static void exitFullscreen(Document&, ExitType = ExitType::Default);
+  static void exitFullscreen(Document&);
 
   static bool fullscreenEnabled(Document&);
+  // TODO(foolip): The fullscreen element stack is modified synchronously in
+  // requestFullscreen(), which is not per spec and means that
+  // |fullscreenElement()| is not always the same as
+  // |currentFullScreenElement()|, see https://crbug.com/402421.
   Element* fullscreenElement() const {
     return !m_fullscreenElementStack.isEmpty()
                ? m_fullscreenElementStack.back().first.get()
@@ -106,6 +107,20 @@
 
   void elementRemoved(Element&);
 
+  // Returns true if the current fullscreen element stack corresponds to a
+  // container for an actual fullscreen element in a descendant
+  // out-of-process iframe.
+  bool forCrossProcessDescendant() { return m_forCrossProcessDescendant; }
+
+  // Mozilla API
+  // TODO(foolip): |currentFullScreenElement()| is a remnant from before the
+  // fullscreen element stack. It is still maintained separately from the
+  // stack and is is what the :-webkit-full-screen pseudo-class depends on. It
+  // should be removed, see https://crbug.com/402421.
+  Element* currentFullScreenElement() const {
+    return m_currentFullScreenElement.get();
+  }
+
   // ContextLifecycleObserver:
   void contextDestroyed(ExecutionContext*) override;
 
@@ -118,30 +133,33 @@
 
   Document* document();
 
-  static void enqueueTaskForRequest(Document&,
-                                    Element&,
-                                    RequestType,
-                                    bool error);
-  static void runTaskForRequest(Document*, Element*, RequestType, bool error);
-
-  static void enqueueTaskForExit(Document&, ExitType);
-  static void runTaskForExit(Document*, ExitType);
-
   void clearFullscreenElementStack();
   void popFullscreenElementStack();
   void pushFullscreenElementStack(Element&, RequestType);
-  void fullscreenElementChanged(Element* fromElement,
-                                Element* toElement,
-                                RequestType toRequestType);
 
-  using ElementStackEntry = std::pair<Member<Element>, RequestType>;
-  using ElementStack = HeapVector<ElementStackEntry>;
-  ElementStack m_pendingRequests;
-  ElementStack m_fullscreenElementStack;
+  void enqueueChangeEvent(Document&, RequestType);
+  void enqueueErrorEvent(Element&, RequestType);
+  void eventQueueTimerFired(TimerBase*);
 
+  Member<Element> m_pendingFullscreenElement;
+  HeapVector<std::pair<Member<Element>, RequestType>> m_fullscreenElementStack;
+  Member<Element> m_currentFullScreenElement;
   LayoutFullScreen* m_fullScreenLayoutObject;
+  TaskRunnerTimer<Fullscreen> m_eventQueueTimer;
+  HeapDeque<Member<Event>> m_eventQueue;
   LayoutRect m_savedPlaceholderFrameRect;
   RefPtr<ComputedStyle> m_savedPlaceholderComputedStyle;
+
+  // TODO(alexmos, dcheng): Currently, this assumes that if fullscreen was
+  // entered for an element in an out-of-process iframe, then it's not
+  // possible to re-enter fullscreen for a different element in this
+  // document, since that requires a user gesture, which can't be obtained
+  // since nothing in this document is visible, and since user gestures can't
+  // be forwarded across processes. However, the latter assumption could
+  // change if https://crbug.com/161068 is fixed so that cross-process
+  // postMessage can carry user gestures.  If that happens, this should be
+  // moved to be part of |m_fullscreenElementStack|.
+  bool m_forCrossProcessDescendant;
 };
 
 inline Fullscreen* Fullscreen::fromIfExists(Document& document) {
@@ -150,9 +168,9 @@
   return fromIfExistsSlow(document);
 }
 
-inline bool Fullscreen::isFullscreenElement(const Element& element) {
+inline bool Fullscreen::isCurrentFullScreenElement(const Element& element) {
   if (Fullscreen* found = fromIfExists(element.document()))
-    return found->fullscreenElement() == &element;
+    return found->currentFullScreenElement() == &element;
   return false;
 }
 
diff --git a/third_party/WebKit/Source/core/dom/LayoutTreeBuilder.cpp b/third_party/WebKit/Source/core/dom/LayoutTreeBuilder.cpp
index 5b16dac..77046c3 100644
--- a/third_party/WebKit/Source/core/dom/LayoutTreeBuilder.cpp
+++ b/third_party/WebKit/Source/core/dom/LayoutTreeBuilder.cpp
@@ -143,7 +143,7 @@
   newLayoutObject->setStyle(
       &style);  // setStyle() can depend on layoutObject() already being set.
 
-  if (Fullscreen::isFullscreenElement(*m_node)) {
+  if (Fullscreen::isCurrentFullScreenElement(*m_node)) {
     newLayoutObject = LayoutFullScreen::wrapLayoutObject(
         newLayoutObject, parentLayoutObject, &m_node->document());
     if (!newLayoutObject)
diff --git a/third_party/WebKit/Source/core/frame/Window.idl b/third_party/WebKit/Source/core/frame/Window.idl
index 8b45bb3a..c4a747f 100644
--- a/third_party/WebKit/Source/core/frame/Window.idl
+++ b/third_party/WebKit/Source/core/frame/Window.idl
@@ -38,7 +38,7 @@
     [Replaceable, CrossOrigin] readonly attribute Window self;
     [Unforgeable, CachedAccessor] readonly attribute Document document;
     attribute DOMString name;
-    [PutForwards=href, Unforgeable, CrossOrigin=(Getter,Setter)] readonly attribute Location location;
+    [PutForwards=href, Unforgeable, CrossOrigin=(Getter,Setter), Custom=Getter] readonly attribute Location location;
     readonly attribute History history;
     [Replaceable, MeasureAs=BarPropLocationbar] readonly attribute BarProp locationbar;
     [Replaceable, MeasureAs=BarPropMenubar] readonly attribute BarProp menubar;
diff --git a/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp b/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp
index c12b486..afd5e73 100644
--- a/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp
@@ -3449,7 +3449,7 @@
 }
 
 bool HTMLMediaElement::isFullscreen() const {
-  return Fullscreen::isFullscreenElement(*this);
+  return Fullscreen::isCurrentFullScreenElement(*this);
 }
 
 void HTMLMediaElement::didEnterFullscreen() {
diff --git a/third_party/WebKit/Source/core/html/shadow/MediaControlsOrientationLockDelegateTest.cpp b/third_party/WebKit/Source/core/html/shadow/MediaControlsOrientationLockDelegateTest.cpp
index ad490be..cb58274 100644
--- a/third_party/WebKit/Source/core/html/shadow/MediaControlsOrientationLockDelegateTest.cpp
+++ b/third_party/WebKit/Source/core/html/shadow/MediaControlsOrientationLockDelegateTest.cpp
@@ -169,13 +169,13 @@
 
     Fullscreen::requestFullscreen(video());
     Fullscreen::from(document()).didEnterFullscreen();
-    document().serviceScriptedAnimations(WTF::monotonicallyIncreasingTime());
+    testing::runPendingTasks();
   }
 
   void simulateExitFullscreen() {
     Fullscreen::exitFullscreen(document());
     Fullscreen::from(document()).didExitFullscreen();
-    document().serviceScriptedAnimations(WTF::monotonicallyIncreasingTime());
+    testing::runPendingTasks();
   }
 
   void simulateOrientationLock() {
diff --git a/third_party/WebKit/Source/core/layout/LayoutGrid.cpp b/third_party/WebKit/Source/core/layout/LayoutGrid.cpp
index 34621286..2067dba 100644
--- a/third_party/WebKit/Source/core/layout/LayoutGrid.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutGrid.cpp
@@ -2042,6 +2042,13 @@
   if (needsToFulfillMinimumSize)
     ++repetitions;
 
+  // Clamp the number of repetitions so we don't end up with too many tracks.
+  if (repetitions > kGridMaxTracks) {
+    DCHECK_GT(autoRepeatTrackListLength, 0u);
+    repetitions =
+        (kGridMaxTracks - trackSizes.size()) / autoRepeatTrackListLength;
+  }
+
   return repetitions * autoRepeatTrackListLength;
 }
 
diff --git a/third_party/WebKit/Source/core/layout/LayoutInline.cpp b/third_party/WebKit/Source/core/layout/LayoutInline.cpp
index 4b1b406c..1e9173c2 100644
--- a/third_party/WebKit/Source/core/layout/LayoutInline.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutInline.cpp
@@ -402,9 +402,9 @@
   // not its parent. Since the splitting logic expects |this| to be the parent,
   // set |beforeChild| to be the LayoutFullScreen.
   if (Fullscreen* fullscreen = Fullscreen::fromIfExists(document())) {
-    const Element* fullscreenElement = fullscreen->fullscreenElement();
-    if (fullscreenElement && beforeChild &&
-        beforeChild->node() == fullscreenElement)
+    const Element* fullScreenElement = fullscreen->currentFullScreenElement();
+    if (fullScreenElement && beforeChild &&
+        beforeChild->node() == fullScreenElement)
       beforeChild = fullscreen->fullScreenLayoutObject();
   }
 
diff --git a/third_party/WebKit/Source/core/layout/compositing/PaintLayerCompositor.cpp b/third_party/WebKit/Source/core/layout/compositing/PaintLayerCompositor.cpp
index d649037..10265b8 100644
--- a/third_party/WebKit/Source/core/layout/compositing/PaintLayerCompositor.cpp
+++ b/third_party/WebKit/Source/core/layout/compositing/PaintLayerCompositor.cpp
@@ -170,6 +170,11 @@
       return nullptr;
     fullscreenElement = Fullscreen::fullscreenElementFrom(*contentDocument);
   }
+  // Get the current fullscreen element from the document.
+  // TODO(foolip): When |currentFullScreenElementFrom| is removed, this will
+  // become a no-op and can be removed. https://crbug.com/402421
+  fullscreenElement =
+      Fullscreen::currentFullScreenElementFrom(*contentDocument);
   if (!isHTMLVideoElement(fullscreenElement))
     return nullptr;
   LayoutObject* layoutObject = fullscreenElement->layoutObject();
diff --git a/third_party/WebKit/Source/core/page/ChromeClient.h b/third_party/WebKit/Source/core/page/ChromeClient.h
index 7d91e79..f4492e9b 100644
--- a/third_party/WebKit/Source/core/page/ChromeClient.h
+++ b/third_party/WebKit/Source/core/page/ChromeClient.h
@@ -251,8 +251,7 @@
 
   virtual void enterFullscreen(LocalFrame&) {}
   virtual void exitFullscreen(LocalFrame&) {}
-  virtual void fullscreenElementChanged(Element* fromElement,
-                                        Element* toElement) {}
+  virtual void fullscreenElementChanged(Element*, Element*) {}
 
   virtual void clearCompositedSelection(LocalFrame*) {}
   virtual void updateCompositedSelection(LocalFrame*,
diff --git a/third_party/WebKit/Source/modules/vr/VRDisplay.cpp b/third_party/WebKit/Source/modules/vr/VRDisplay.cpp
index 829d1de2..43cd97d4 100644
--- a/third_party/WebKit/Source/modules/vr/VRDisplay.cpp
+++ b/third_party/WebKit/Source/modules/vr/VRDisplay.cpp
@@ -669,7 +669,7 @@
   // depend on the Fullscreen API to fake VR presentation, so this will
   // become unnessecary. Until that point, though, this seems preferable to
   // adding a bunch of notification plumbing to Fullscreen.
-  if (!Fullscreen::isFullscreenElement(*m_layer.source())) {
+  if (!Fullscreen::isCurrentFullScreenElement(*m_layer.source())) {
     // TODO(mthiesse): Due to asynchronous resizing, we might get kicked out of
     // fullscreen when changing display parameters upon entering WebVR. So one
     // time only, we reenter fullscreen after having left it; otherwise we exit
diff --git a/third_party/WebKit/Source/platform/exported/WebString.cpp b/third_party/WebKit/Source/platform/exported/WebString.cpp
index 4dc2a65..29205801 100644
--- a/third_party/WebKit/Source/platform/exported/WebString.cpp
+++ b/third_party/WebKit/Source/platform/exported/WebString.cpp
@@ -90,10 +90,6 @@
   return String::fromUTF8(data, length);
 }
 
-WebString WebString::fromUTF8(const char* data) {
-  return String::fromUTF8(data);
-}
-
 WebString WebString::fromUTF16(const base::string16& s) {
   WebString string;
   string.assign(s.data(), s.length());
diff --git a/third_party/WebKit/Source/web/ChromeClientImpl.h b/third_party/WebKit/Source/web/ChromeClientImpl.h
index c74b0ab..1daf994 100644
--- a/third_party/WebKit/Source/web/ChromeClientImpl.h
+++ b/third_party/WebKit/Source/web/ChromeClientImpl.h
@@ -163,8 +163,7 @@
 
   void enterFullscreen(LocalFrame&) override;
   void exitFullscreen(LocalFrame&) override;
-  void fullscreenElementChanged(Element* fromElement,
-                                Element* toElement) override;
+  void fullscreenElementChanged(Element*, Element*) override;
 
   void clearCompositedSelection(LocalFrame*) override;
   void updateCompositedSelection(LocalFrame*,
diff --git a/third_party/WebKit/Source/web/FullscreenController.cpp b/third_party/WebKit/Source/web/FullscreenController.cpp
index 7470776..4380a01 100644
--- a/third_party/WebKit/Source/web/FullscreenController.cpp
+++ b/third_party/WebKit/Source/web/FullscreenController.cpp
@@ -88,9 +88,6 @@
         fullscreen->didEnterFullscreen();
     }
   }
-
-  // TODO(foolip): If the top level browsing context (main frame) ends up with
-  // no fullscreen element, exit fullscreen again to recover.
 }
 
 void FullscreenController::didExitFullscreen() {
@@ -103,32 +100,33 @@
 
   updatePageScaleConstraints(true);
 
-  // We need to wait until style and layout are updated in order to properly
-  // restore scroll offsets since content may not be overflowing in the same way
-  // until they are.
-  m_state = State::NeedsScrollAndScaleRestore;
+  // Set |m_state| so that any |exitFullscreen()| calls from within
+  // |Fullscreen::didExitFullscreen()| do not call
+  // |WebFrameClient::exitFullscreen()| again.
+  // TODO(foolip): Remove this when state changes and events are synchronized
+  // with animation frames. https://crbug.com/402376
+  m_state = State::ExitingFullscreen;
 
-  // Notify the topmost local frames that we have exited fullscreen.
-  // |Fullscreen::didExitFullscreen()| will take care of descendant frames.
-  for (Frame* frame = m_webViewImpl->page()->mainFrame(); frame;) {
-    Frame* nextFrame = frame->tree().traverseNext();
-
-    if (frame->isRemoteFrame()) {
-      frame = nextFrame;
+  // Notify all local frames that we have exited fullscreen.
+  // TODO(foolip): This should only need to notify the topmost local roots. That
+  // doesn't currently work because |Fullscreen::m_currentFullScreenElement|
+  // isn't set for the topmost document when an iframe goes fullscreen, but can
+  // be done once |m_currentFullScreenElement| is gone and all state is in the
+  // fullscreen element stack. https://crbug.com/402421
+  for (Frame* frame = m_webViewImpl->page()->mainFrame(); frame;
+       frame = frame->tree().traverseNext()) {
+    if (!frame->isLocalFrame())
       continue;
-    }
-
-    DCHECK(frame->isLocalRoot());
     if (Document* document = toLocalFrame(frame)->document()) {
       if (Fullscreen* fullscreen = Fullscreen::fromIfExists(*document))
         fullscreen->didExitFullscreen();
     }
-
-    // Skip over all descendant frames.
-    while (nextFrame && nextFrame->tree().isDescendantOf(frame))
-      nextFrame = nextFrame->tree().traverseNext();
-    frame = nextFrame;
   }
+
+  // We need to wait until style and layout are updated in order to properly
+  // restore scroll offsets since content may not be overflowing in the same way
+  // until they are.
+  m_state = State::NeedsScrollAndScaleRestore;
 }
 
 void FullscreenController::enterFullscreen(LocalFrame& frame) {
@@ -185,7 +183,7 @@
   DCHECK_NE(fromElement, toElement);
 
   if (toElement) {
-    DCHECK(Fullscreen::isFullscreenElement(*toElement));
+    DCHECK(Fullscreen::isCurrentFullScreenElement(*toElement));
 
     if (isHTMLVideoElement(*toElement)) {
       HTMLVideoElement& videoElement = toHTMLVideoElement(*toElement);
@@ -201,7 +199,7 @@
   }
 
   if (fromElement) {
-    DCHECK(!Fullscreen::isFullscreenElement(*fromElement));
+    DCHECK(!Fullscreen::isCurrentFullScreenElement(*fromElement));
 
     if (isHTMLVideoElement(*fromElement)) {
       // If the video used overlay fullscreen mode, restore the transparency.
diff --git a/third_party/WebKit/Source/web/FullscreenController.h b/third_party/WebKit/Source/web/FullscreenController.h
index de3e34d..4ffbd6d 100644
--- a/third_party/WebKit/Source/web/FullscreenController.h
+++ b/third_party/WebKit/Source/web/FullscreenController.h
@@ -58,7 +58,7 @@
 
   // Called by Fullscreen (via ChromeClient) to notify that the fullscreen
   // element has changed.
-  void fullscreenElementChanged(Element* fromElement, Element* toElement);
+  void fullscreenElementChanged(Element*, Element*);
 
   bool isFullscreen() { return m_state == State::Fullscreen; }
 
diff --git a/third_party/WebKit/Source/web/WebPluginContainerImpl.cpp b/third_party/WebKit/Source/web/WebPluginContainerImpl.cpp
index f30d629..4301e30 100644
--- a/third_party/WebKit/Source/web/WebPluginContainerImpl.cpp
+++ b/third_party/WebKit/Source/web/WebPluginContainerImpl.cpp
@@ -311,7 +311,7 @@
 }
 
 bool WebPluginContainerImpl::isFullscreenElement() const {
-  return Fullscreen::isFullscreenElement(*m_element);
+  return Fullscreen::isCurrentFullScreenElement(*m_element);
 }
 
 void WebPluginContainerImpl::cancelFullscreen() {
diff --git a/third_party/WebKit/Source/web/WebRemoteFrameImpl.cpp b/third_party/WebKit/Source/web/WebRemoteFrameImpl.cpp
index ec573b5..29479f8 100644
--- a/third_party/WebKit/Source/web/WebRemoteFrameImpl.cpp
+++ b/third_party/WebKit/Source/web/WebRemoteFrameImpl.cpp
@@ -506,21 +506,24 @@
   HTMLFrameOwnerElement* ownerElement =
       toHTMLFrameOwnerElement(frame()->owner());
 
-  // Call |requestFullscreen()| on |ownerElement| to make it the pending
-  // fullscreen element in anticipation of the coming |didEnterFullscreen()|
-  // call.
+  // Call requestFullscreen() on |ownerElement| to make it the provisional
+  // fullscreen element in FullscreenController, and to prepare
+  // fullscreenchange events that will need to fire on it and its (local)
+  // ancestors. The events will be triggered if/when fullscreen is entered.
   //
-  // PrefixedForCrossProcessDescendant is necessary because:
-  //  - The fullscreen element ready check and other checks should be bypassed.
-  //  - |ownerElement| will need :-webkit-full-screen-ancestor style in addition
-  //    to :-webkit-full-screen.
+  // Passing |forCrossProcessAncestor| to requestFullscreen is necessary
+  // because:
+  // - |ownerElement| will need :-webkit-full-screen-ancestor style in
+  //   addition to :-webkit-full-screen.
+  // - there's no need to resend the ToggleFullscreen IPC to the browser
+  //   process.
   //
   // TODO(alexmos): currently, this assumes prefixed requests, but in the
   // future, this should plumb in information about which request type
   // (prefixed or unprefixed) to use for firing fullscreen events.
-  Fullscreen::requestFullscreen(
-      *ownerElement,
-      Fullscreen::RequestType::PrefixedForCrossProcessDescendant);
+  Fullscreen::requestFullscreen(*ownerElement,
+                                Fullscreen::RequestType::Prefixed,
+                                true /* forCrossProcessAncestor */);
 }
 
 void WebRemoteFrameImpl::setHasReceivedUserGesture() {
diff --git a/third_party/WebKit/Source/web/WebViewImpl.h b/third_party/WebKit/Source/web/WebViewImpl.h
index e88da4c..f648bad5 100644
--- a/third_party/WebKit/Source/web/WebViewImpl.h
+++ b/third_party/WebKit/Source/web/WebViewImpl.h
@@ -443,7 +443,7 @@
 
   void enterFullscreen(LocalFrame&);
   void exitFullscreen(LocalFrame&);
-  void fullscreenElementChanged(Element* fromElement, Element* toElement);
+  void fullscreenElementChanged(Element*, Element*);
 
   // Exposed for the purpose of overriding device metrics.
   void sendResizeEventAndRepaint();
diff --git a/third_party/WebKit/Source/web/tests/WebFrameTest.cpp b/third_party/WebKit/Source/web/tests/WebFrameTest.cpp
index cceac88..70d4e1f8d 100644
--- a/third_party/WebKit/Source/web/tests/WebFrameTest.cpp
+++ b/third_party/WebKit/Source/web/tests/WebFrameTest.cpp
@@ -7666,11 +7666,14 @@
   UserGestureIndicator gesture(DocumentUserGestureToken::create(document));
   Element* divFullscreen = document->getElementById("div1");
   Fullscreen::requestFullscreen(*divFullscreen);
+  EXPECT_EQ(nullptr, Fullscreen::currentFullScreenElementFrom(*document));
+  EXPECT_EQ(divFullscreen, Fullscreen::fullscreenElementFrom(*document));
   webViewImpl->didEnterFullscreen();
-  EXPECT_EQ(nullptr, Fullscreen::fullscreenElementFrom(*document));
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
+  EXPECT_EQ(divFullscreen, Fullscreen::currentFullScreenElementFrom(*document));
   EXPECT_EQ(divFullscreen, Fullscreen::fullscreenElementFrom(*document));
   webViewImpl->updateAllLifecyclePhases();
+  EXPECT_EQ(divFullscreen, Fullscreen::currentFullScreenElementFrom(*document));
+  EXPECT_EQ(divFullscreen, Fullscreen::fullscreenElementFrom(*document));
 
   // Verify that the element is sized to the viewport.
   LayoutFullScreen* fullscreenLayoutObject =
@@ -7703,11 +7706,14 @@
   UserGestureIndicator gesture(DocumentUserGestureToken::create(document));
   Element* divFullscreen = document->getElementById("div1");
   Fullscreen::requestFullscreen(*divFullscreen);
+  EXPECT_EQ(nullptr, Fullscreen::currentFullScreenElementFrom(*document));
+  EXPECT_EQ(divFullscreen, Fullscreen::fullscreenElementFrom(*document));
   webViewImpl->didEnterFullscreen();
-  EXPECT_EQ(nullptr, Fullscreen::fullscreenElementFrom(*document));
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
+  EXPECT_EQ(divFullscreen, Fullscreen::currentFullScreenElementFrom(*document));
   EXPECT_EQ(divFullscreen, Fullscreen::fullscreenElementFrom(*document));
   webViewImpl->updateAllLifecyclePhases();
+  EXPECT_EQ(divFullscreen, Fullscreen::currentFullScreenElementFrom(*document));
+  EXPECT_EQ(divFullscreen, Fullscreen::fullscreenElementFrom(*document));
 
   // Verify that the viewports are nonscrollable.
   FrameView* frameView = webViewHelper.webView()->mainFrameImpl()->frameView();
@@ -7724,11 +7730,14 @@
   ASSERT_FALSE(visualViewportScrollLayer->userScrollableVertical());
 
   // Verify that the viewports are scrollable upon exiting fullscreen.
-  webViewImpl->didExitFullscreen();
+  EXPECT_EQ(divFullscreen, Fullscreen::currentFullScreenElementFrom(*document));
   EXPECT_EQ(divFullscreen, Fullscreen::fullscreenElementFrom(*document));
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
+  webViewImpl->didExitFullscreen();
+  EXPECT_EQ(nullptr, Fullscreen::currentFullScreenElementFrom(*document));
   EXPECT_EQ(nullptr, Fullscreen::fullscreenElementFrom(*document));
   webViewImpl->updateAllLifecyclePhases();
+  EXPECT_EQ(nullptr, Fullscreen::currentFullScreenElementFrom(*document));
+  EXPECT_EQ(nullptr, Fullscreen::fullscreenElementFrom(*document));
   ASSERT_TRUE(layoutViewportScrollLayer->userScrollableHorizontal());
   ASSERT_TRUE(layoutViewportScrollLayer->userScrollableVertical());
   ASSERT_TRUE(visualViewportScrollLayer->userScrollableHorizontal());
@@ -7750,12 +7759,20 @@
   Document* document = webViewImpl->mainFrameImpl()->frame()->document();
   UserGestureIndicator gesture(DocumentUserGestureToken::create(document));
   Fullscreen::requestFullscreen(*document->documentElement());
-  webViewImpl->didEnterFullscreen();
-  EXPECT_EQ(nullptr, Fullscreen::fullscreenElementFrom(*document));
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
+  EXPECT_EQ(nullptr, Fullscreen::currentFullScreenElementFrom(*document));
   EXPECT_EQ(document->documentElement(),
             Fullscreen::fullscreenElementFrom(*document));
+  webViewImpl->didEnterFullscreen();
+  EXPECT_EQ(document->documentElement(),
+            Fullscreen::currentFullScreenElementFrom(*document));
+  EXPECT_EQ(document->documentElement(),
+            Fullscreen::fullscreenElementFrom(*document));
+
   webViewImpl->updateAllLifecyclePhases();
+  EXPECT_EQ(document->documentElement(),
+            Fullscreen::currentFullScreenElementFrom(*document));
+  EXPECT_EQ(document->documentElement(),
+            Fullscreen::fullscreenElementFrom(*document));
 
   // Verify that the main frame is still scrollable.
   WebLayer* webScrollLayer =
@@ -7794,7 +7811,6 @@
   Element* divFullscreen = document->getElementById("div1");
   Fullscreen::requestFullscreen(*divFullscreen);
   webViewImpl->didEnterFullscreen();
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
   webViewImpl->updateAllLifecyclePhases();
 
   // Verify that the element is sized to the viewport.
@@ -7836,11 +7852,6 @@
     Fullscreen::requestFullscreen(*topBody);
   }
   webViewImpl->didEnterFullscreen();
-  EXPECT_EQ(nullptr, Fullscreen::fullscreenElementFrom(*topDoc));
-  EXPECT_EQ(nullptr, Fullscreen::fullscreenElementFrom(*iframeDoc));
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
-  EXPECT_EQ(topBody, Fullscreen::fullscreenElementFrom(*topDoc));
-  EXPECT_EQ(nullptr, Fullscreen::fullscreenElementFrom(*iframeDoc));
   webViewImpl->updateAllLifecyclePhases();
 
   {
@@ -7848,25 +7859,23 @@
     Fullscreen::requestFullscreen(*iframeBody);
   }
   webViewImpl->didEnterFullscreen();
-  EXPECT_EQ(topBody, Fullscreen::fullscreenElementFrom(*topDoc));
-  EXPECT_EQ(nullptr, Fullscreen::fullscreenElementFrom(*iframeDoc));
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
-  EXPECT_EQ(iframe, Fullscreen::fullscreenElementFrom(*topDoc));
-  EXPECT_EQ(iframeBody, Fullscreen::fullscreenElementFrom(*iframeDoc));
   webViewImpl->updateAllLifecyclePhases();
 
   // We are now in nested fullscreen, with both documents having a non-empty
   // fullscreen element stack.
+  EXPECT_EQ(topBody, Fullscreen::currentFullScreenElementFrom(*topDoc));
+  EXPECT_EQ(iframe, Fullscreen::fullscreenElementFrom(*topDoc));
+  EXPECT_EQ(iframeBody, Fullscreen::currentFullScreenElementFrom(*iframeDoc));
+  EXPECT_EQ(iframeBody, Fullscreen::fullscreenElementFrom(*iframeDoc));
 
   webViewImpl->didExitFullscreen();
-  EXPECT_EQ(iframe, Fullscreen::fullscreenElementFrom(*topDoc));
-  EXPECT_EQ(iframeBody, Fullscreen::fullscreenElementFrom(*iframeDoc));
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
-  EXPECT_EQ(nullptr, Fullscreen::fullscreenElementFrom(*topDoc));
-  EXPECT_EQ(nullptr, Fullscreen::fullscreenElementFrom(*iframeDoc));
   webViewImpl->updateAllLifecyclePhases();
 
-  // We have now fully exited fullscreen.
+  // We should now have fully exited fullscreen.
+  EXPECT_EQ(nullptr, Fullscreen::currentFullScreenElementFrom(*topDoc));
+  EXPECT_EQ(nullptr, Fullscreen::fullscreenElementFrom(*topDoc));
+  EXPECT_EQ(nullptr, Fullscreen::currentFullScreenElementFrom(*iframeDoc));
+  EXPECT_EQ(nullptr, Fullscreen::fullscreenElementFrom(*iframeDoc));
 }
 
 TEST_P(ParameterizedWebFrameTest, FullscreenWithTinyViewport) {
@@ -7895,7 +7904,6 @@
   UserGestureIndicator gesture(DocumentUserGestureToken::create(document));
   Fullscreen::requestFullscreen(*document->documentElement());
   webViewImpl->didEnterFullscreen();
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
   webViewImpl->updateAllLifecyclePhases();
   EXPECT_EQ(384, layoutViewItem.logicalWidth().floor());
   EXPECT_EQ(640, layoutViewItem.logicalHeight().floor());
@@ -7904,7 +7912,6 @@
   EXPECT_FLOAT_EQ(1.0, webViewImpl->maximumPageScaleFactor());
 
   webViewImpl->didExitFullscreen();
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
   webViewImpl->updateAllLifecyclePhases();
   EXPECT_EQ(320, layoutViewItem.logicalWidth().floor());
   EXPECT_EQ(533, layoutViewItem.logicalHeight().floor());
@@ -7933,7 +7940,6 @@
   UserGestureIndicator gesture(DocumentUserGestureToken::create(document));
   Fullscreen::requestFullscreen(*document->documentElement());
   webViewImpl->didEnterFullscreen();
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
   webViewImpl->updateAllLifecyclePhases();
   EXPECT_EQ(384, layoutViewItem.logicalWidth().floor());
   EXPECT_EQ(640, layoutViewItem.logicalHeight().floor());
@@ -7954,7 +7960,6 @@
   EXPECT_FLOAT_EQ(1.0, webViewImpl->maximumPageScaleFactor());
 
   webViewImpl->didExitFullscreen();
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
   webViewImpl->updateAllLifecyclePhases();
   EXPECT_EQ(320, layoutViewItem.logicalWidth().floor());
   EXPECT_EQ(192, layoutViewItem.logicalHeight().floor());
@@ -7998,7 +8003,6 @@
   }
 
   webViewImpl->didEnterFullscreen();
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
   webViewImpl->updateAllLifecyclePhases();
   client.m_screenInfo.rect.width = screenSizeMinusStatusBars.width;
   client.m_screenInfo.rect.height = screenSizeMinusStatusBars.height;
@@ -8013,7 +8017,6 @@
   EXPECT_FLOAT_EQ(1.0, webViewImpl->maximumPageScaleFactor());
 
   webViewImpl->didExitFullscreen();
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
   webViewImpl->updateAllLifecyclePhases();
   client.m_screenInfo.rect.width = screenSizeMinusStatusBars.width;
   client.m_screenInfo.rect.height = screenSizeMinusStatusBars.height;
@@ -8059,7 +8062,6 @@
       DocumentUserGestureToken::create(document, UserGestureToken::NewGesture));
   Fullscreen::requestFullscreen(*document->documentElement());
   webViewImpl->didEnterFullscreen();
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
   webViewImpl->updateAllLifecyclePhases();
 
   // Entering fullscreen causes layout size and page scale limits to be
@@ -8077,7 +8079,6 @@
   WebFrame* frame = webViewHelper.webView()->mainFrame();
   FrameTestHelpers::loadHTMLString(frame, source, testURL);
   webViewImpl->didExitFullscreen();
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
   webViewImpl->updateAllLifecyclePhases();
 
   // Make sure the new page's layout size and scale factor limits aren't
@@ -8125,23 +8126,19 @@
   HTMLVideoElement* video =
       toHTMLVideoElement(document->getElementById("video"));
   EXPECT_TRUE(video->usesOverlayFullscreenVideo());
+  EXPECT_FALSE(video->isFullscreen());
+  EXPECT_FALSE(layerTreeView.hasTransparentBackground);
 
   video->webkitEnterFullscreen();
   webViewImpl->didEnterFullscreen();
-  EXPECT_FALSE(video->isFullscreen());
-  EXPECT_FALSE(layerTreeView.hasTransparentBackground);
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
+  webViewImpl->updateAllLifecyclePhases();
   EXPECT_TRUE(video->isFullscreen());
   EXPECT_TRUE(layerTreeView.hasTransparentBackground);
-  webViewImpl->updateAllLifecyclePhases();
 
   webViewImpl->didExitFullscreen();
-  EXPECT_TRUE(video->isFullscreen());
-  EXPECT_TRUE(layerTreeView.hasTransparentBackground);
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
+  webViewImpl->updateAllLifecyclePhases();
   EXPECT_FALSE(video->isFullscreen());
   EXPECT_FALSE(layerTreeView.hasTransparentBackground);
-  webViewImpl->updateAllLifecyclePhases();
 }
 
 TEST_P(ParameterizedWebFrameTest, LayoutBlockPercentHeightDescendants) {
diff --git a/third_party/WebKit/Source/web/tests/WebViewTest.cpp b/third_party/WebKit/Source/web/tests/WebViewTest.cpp
index 4a6c50a..a727e0d3 100644
--- a/third_party/WebKit/Source/web/tests/WebViewTest.cpp
+++ b/third_party/WebKit/Source/web/tests/WebViewTest.cpp
@@ -1753,7 +1753,6 @@
   UserGestureIndicator gesture(DocumentUserGestureToken::create(document));
   Fullscreen::requestFullscreen(*element);
   webViewImpl->didEnterFullscreen();
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
   webViewImpl->updateAllLifecyclePhases();
 
   // Sanity-check. There should be no scrolling possible.
@@ -1767,12 +1766,10 @@
   // parameters are reset. The page sets display: none on overflowing elements
   // while in fullscreen so if we try to restore before the style and layout
   // is applied the offsets will be clamped.
-  EXPECT_FALSE(webViewImpl->mainFrameImpl()->frameView()->needsLayout());
   webViewImpl->didExitFullscreen();
   EXPECT_TRUE(webViewImpl->mainFrameImpl()->frameView()->needsLayout());
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
-  EXPECT_EQ(0, webViewImpl->mainFrame()->getScrollOffset().height);
   webViewImpl->updateAllLifecyclePhases();
+
   EXPECT_EQ(2000, webViewImpl->mainFrame()->getScrollOffset().height);
 }
 
@@ -1797,7 +1794,6 @@
   UserGestureIndicator gesture(DocumentUserGestureToken::create(document));
   Fullscreen::requestFullscreen(*element);
   webViewImpl->didEnterFullscreen();
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
   webViewImpl->updateAllLifecyclePhases();
 
   // Sanity-check. There should be no scrolling possible.
@@ -1813,7 +1809,6 @@
   webViewImpl->didExitFullscreen();
   Fullscreen::requestFullscreen(*element);
   webViewImpl->didEnterFullscreen();
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
   webViewImpl->updateAllLifecyclePhases();
 
   // Sanity-check. There should be no scrolling possible.
@@ -1825,7 +1820,6 @@
 
   // When we exit now, we should restore the original scroll value.
   webViewImpl->didExitFullscreen();
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
   webViewImpl->updateAllLifecyclePhases();
 
   EXPECT_EQ(2000, webViewImpl->mainFrame()->getScrollOffset().height);
@@ -1857,8 +1851,6 @@
   UserGestureIndicator gesture(DocumentUserGestureToken::create(document));
   Fullscreen::requestFullscreen(*element);
   webViewImpl->didEnterFullscreen();
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
-  webViewImpl->updateAllLifecyclePhases();
 
   // Page scale factor must be 1.0 during fullscreen for elements to be sized
   // properly.
@@ -1870,7 +1862,6 @@
 
   // Confirm that exiting fullscreen restores the parameters.
   webViewImpl->didExitFullscreen();
-  webViewImpl->beginFrame(WTF::monotonicallyIncreasingTime());
   webViewImpl->updateAllLifecyclePhases();
 
   EXPECT_EQ(2.0f, webViewImpl->pageScaleFactor());
diff --git a/third_party/WebKit/public/platform/WebString.h b/third_party/WebKit/public/platform/WebString.h
index 85a6832..c8cbf0f0 100644
--- a/third_party/WebKit/public/platform/WebString.h
+++ b/third_party/WebKit/public/platform/WebString.h
@@ -113,6 +113,9 @@
 
   BLINK_COMMON_EXPORT bool equals(const WebString&) const;
   BLINK_COMMON_EXPORT bool equals(const char* characters, size_t len) const;
+  bool equals(const char* characters) const {
+    return equals(characters, characters ? strlen(characters) : 0);
+  }
 
   BLINK_COMMON_EXPORT size_t length() const;
 
@@ -124,8 +127,6 @@
 
   BLINK_COMMON_EXPORT static WebString fromUTF8(const char* data,
                                                 size_t length);
-  BLINK_COMMON_EXPORT static WebString fromUTF8(const char* data);
-
   static WebString fromUTF8(const std::string& s) {
     return fromUTF8(s.data(), s.length());
   }
@@ -229,7 +230,7 @@
 };
 
 inline bool operator==(const WebString& a, const char* b) {
-  return a.equals(b, b ? strlen(b) : 0);
+  return a.equals(b);
 }
 
 inline bool operator!=(const WebString& a, const char* b) {
diff --git a/third_party/openh264/BUILD.gn b/third_party/openh264/BUILD.gn
index 4301abd..7b9d458 100644
--- a/third_party/openh264/BUILD.gn
+++ b/third_party/openh264/BUILD.gn
@@ -2,8 +2,10 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//build/config/sanitizers/sanitizers.gni")
 import("//third_party/openh264/openh264_args.gni")
 import("//third_party/openh264/openh264_sources.gni")
+import("//third_party/yasm/yasm_assemble.gni")
 
 # Config shared by all openh264 targets.
 config("config") {
@@ -28,6 +30,77 @@
   }
 }
 
+# YASM assembly is only checked to be working on Windows and Linux.
+# Mac is known to fail certain tests when building, but actual assembly
+# is believed to work.
+# MSAN builds are flaky with assembler. crbug.com/685168
+
+use_assembler = (is_win || is_linux) &&
+                (target_cpu == "x86" || target_cpu == "x64") && !is_msan
+
+# This IF statement will make the targets visible only on specific builds,
+# which will lead to failures on other platforms if accidentally invoked.
+if (use_assembler) {
+  yasm_assemble("openh264_common_yasm") {
+    include_dirs = openh264_common_include_dirs
+    sources = openh264_common_sources_asm_x86
+    if (target_cpu == "x86") {
+      defines = [ "X86_32" ]
+    } else {  # x64
+      if (is_mac) {
+        defines = [
+          "PREFIX",
+          "UNIX64",
+        ]
+      } else if (is_win) {
+        defines = [ "WIN64" ]
+      } else if (is_linux) {
+        defines = [ "UNIX64" ]
+      }
+    }
+  }
+
+  yasm_assemble("openh264_processing_yasm") {
+    include_dirs = openh264_processing_include_dirs
+    include_dirs += [ "./src/codec/common/x86" ]
+    sources = openh264_processing_sources_asm_x86
+    if (target_cpu == "x86") {
+      defines = [ "X86_32" ]
+    } else {  # x64
+      if (is_mac) {
+        defines = [
+          "PREFIX",
+          "UNIX64",
+        ]
+      } else if (is_win) {
+        defines = [ "WIN64" ]
+      } else if (is_linux) {
+        defines = [ "UNIX64" ]
+      }
+    }
+  }
+
+  yasm_assemble("openh264_encoder_yasm") {
+    include_dirs = openh264_encoder_include_dirs
+    include_dirs += [ "./src/codec/common/x86" ]
+    sources = openh264_encoder_sources_asm_x86
+    if (target_cpu == "x86") {
+      defines = [ "X86_32" ]
+    } else {  # x64
+      if (is_mac) {
+        defines = [
+          "PREFIX",
+          "UNIX64",
+        ]
+      } else if (is_win) {
+        defines = [ "WIN64" ]
+      } else if (is_linux) {
+        defines = [ "UNIX64" ]
+      }
+    }
+  }
+}  # if (is_win || is_linux)
+
 source_set("common") {
   sources = openh264_common_sources
   include_dirs = openh264_common_include_dirs
@@ -36,6 +109,10 @@
   configs += [ "//build/config/compiler:no_chromium_code" ]
   configs += [ ":config" ]
   deps = []
+  if (use_assembler) {
+    defines = [ "X86_ASM" ]
+    deps += [ ":openh264_common_yasm" ]
+  }
   if (is_android) {
     deps += [
       # Defines "android_get/setCpu..." functions. The original OpenH264 build
@@ -57,6 +134,10 @@
   deps = [
     ":common",
   ]
+  if (use_assembler) {
+    defines = [ "X86_ASM" ]
+    deps += [ ":openh264_processing_yasm" ]
+  }
 }
 
 source_set("encoder") {
@@ -76,4 +157,8 @@
     ":common",
     ":processing",
   ]
+  if (use_assembler) {
+    defines = [ "X86_ASM" ]
+    deps += [ ":openh264_encoder_yasm" ]
+  }
 }
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 47e4ad8..eef0a37 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -5053,6 +5053,19 @@
   </summary>
 </histogram>
 
+<histogram name="Bluetooth.Web.Descriptor.ReadValue.Outcome"
+    enum="WebBluetoothGATTOperationOutcome">
+  <owner>jyasskin@chromium.org</owner>
+  <owner>ortuno@chromium.org</owner>
+  <owner>scheib@chromium.org</owner>
+  <summary>
+    Records the outcome of a call to descriptor.readValue(). Used to know what
+    types of errors users are seeing. The results will be used to determine how
+    common these errors are and if we need to provide better error messages to
+    the users.
+  </summary>
+</histogram>
+
 <histogram name="Bluetooth.Web.FunctionCall.Count" enum="WebBluetoothFunction">
   <owner>jyasskin@chromium.org</owner>
   <owner>ortuno@chromium.org</owner>
@@ -54065,10 +54078,7 @@
 </histogram>
 
 <histogram name="ResourcePrefetchPredictor.HistoryVisitCountForUrl">
-  <obsolete>
-    Deprecated September 2016. No longer recorded.
-  </obsolete>
-  <owner>zhenw@chromium.org</owner>
+  <owner>alexilin@chromium.org</owner>
   <summary>
     The visit count of a URL in the history database, measured when the onload
     fires for the URL. Helpful in figuring out what visit count should be used
@@ -54280,6 +54290,11 @@
 </histogram>
 
 <histogram name="ResourcePrefetchPredictor.HostTableHostCount">
+  <obsolete>
+    Deprecated January 2017. This is effectively the same as
+    ResourcePrefetchPredictor.HostTableRowCount2 after refactoring of the
+    predictor database.
+  </obsolete>
   <owner>zhenw@chromium.org</owner>
   <summary>
     The count of number of unique hosts in the HostTable, i.e. the number of
@@ -54290,6 +54305,10 @@
 </histogram>
 
 <histogram name="ResourcePrefetchPredictor.HostTableRowCount">
+  <obsolete>
+    Deprecated January 2017. Replaced by
+    ResourcePrefetchPredictor.HostTableRowCount2.
+  </obsolete>
   <owner>zhenw@chromium.org</owner>
   <summary>
     The count of number of rows in the HostTable. This is effecively the number
@@ -54298,6 +54317,15 @@
   </summary>
 </histogram>
 
+<histogram name="ResourcePrefetchPredictor.HostTableRowCount2" units="hosts">
+  <owner>alexilin@chromium.org</owner>
+  <summary>
+    The number of rows in the HostTable. This is effectively the number of hosts
+    in the database. This is measured at startup and used to get an estimate of
+    the data size.
+  </summary>
+</histogram>
+
 <histogram name="ResourcePrefetchPredictor.LearningPrecision" units="%">
   <owner>lizeb@chromium.org</owner>
   <summary>
@@ -54318,7 +54346,7 @@
 
 <histogram name="ResourcePrefetchPredictor.MainFrameRequestStats"
     enum="ResourcePrefetchPredictorMainFrameRequestStats">
-  <owner>zhenw@chromium.org</owner>
+  <owner>alexilin@chromium.org</owner>
   <summary>
     Records stats about main frame navigations. Records the total number of
     requests/responses/redirects for main frame urls along with the numbers for
@@ -54464,7 +54492,7 @@
 
 <histogram name="ResourcePrefetchPredictor.ReportingEvent"
     enum="ResourcePrefetchPredictorReportingEvent">
-  <owner>zhenw@chromium.org</owner>
+  <owner>alexilin@chromium.org</owner>
   <summary>
     Records stats about various interesting events such as - when partial or all
     of history is cleared. It will include events which do not necessarily
@@ -54475,7 +54503,7 @@
 
 <histogram name="ResourcePrefetchPredictor.RequestStats"
     enum="ResourcePrefetchPredictorRequestStats">
-  <owner>zhenw@chromium.org</owner>
+  <owner>alexilin@chromium.org</owner>
   <summary>
     Records stats about requests, redirects, and responses observed by the
     ResourcePrefetchPredictorObserver. These stats are useful as a baseline for
@@ -54702,7 +54730,9 @@
 
 <histogram name="ResourcePrefetchPredictor.UrlTableMainFrameUrlCount">
   <obsolete>
-    Deprecated September 2016. No longer recorded.
+    Deprecated September 2016. This is effectively the same as
+    ResourcePrefetchPredictor.UrlTableRowCount2 after refactoring of the
+    predictor database.
   </obsolete>
   <owner>zhenw@chromium.org</owner>
   <summary>
@@ -54715,6 +54745,9 @@
 
 <histogram
     name="ResourcePrefetchPredictor.UrlTableMainFrameUrlsDeletedNotInHistory">
+  <obsolete>
+    Deprecated October 2012. No longer recorded.
+  </obsolete>
   <owner>zhenw@chromium.org</owner>
   <summary>
     The count of number of unique main frame urls that are deleted from the URL
@@ -54726,6 +54759,9 @@
 <histogram
     name="ResourcePrefetchPredictor.UrlTableMainFrameUrlsDeletedNotInHistoryPercent"
     units="%">
+  <obsolete>
+    Deprecated October 2012. No longer recorded.
+  </obsolete>
   <owner>zhenw@chromium.org</owner>
   <summary>
     Same as ResourcePrefetchPredictor.UrlTableMainFrameUrlsDeletedNotInHistory
@@ -54734,6 +54770,10 @@
 </histogram>
 
 <histogram name="ResourcePrefetchPredictor.UrlTableRowCount">
+  <obsolete>
+    Deprecated January 2017. Replaced by
+    ResourcePrefetchPredictor.UrlTableRowCount2.
+  </obsolete>
   <owner>zhenw@chromium.org</owner>
   <summary>
     The count of number of rows in the UrlTable. This is effecively the number
@@ -54742,6 +54782,15 @@
   </summary>
 </histogram>
 
+<histogram name="ResourcePrefetchPredictor.UrlTableRowCount2" units="urls">
+  <owner>alexilin@chromium.org</owner>
+  <summary>
+    The number of rows in the UrlTable. This is effectively the number of main
+    frame URLs in the database. This is measured at startup and used to get an
+    estimate of the data size.
+  </summary>
+</histogram>
+
 <histogram name="ResourceReporter.BrowserProcess.CpuUsage"
     enum="ResourceReporterCpuUsage">
   <owner>afakhry@chromium.org</owner>
@@ -117080,6 +117129,9 @@
 </histogram_suffixes>
 
 <histogram_suffixes name="ResourcePrefetchPredictorPLTPrefetch" separator=".">
+  <obsolete>
+    Deprecated September 2016. No longer recorded.
+  </obsolete>
   <suffix name="NotPrefetched"
       label="Page load time for non-prefetched pages."/>
   <suffix name="Prefetched" label="Page load time for prefetched pages."/>
@@ -117088,6 +117140,9 @@
 
 <histogram_suffixes name="ResourcePrefetchPredictorPLTPrefetchType"
     separator=".">
+  <obsolete>
+    Deprecated September 2016. No longer recorded.
+  </obsolete>
   <suffix name="Host"
       label="Page load time for prefetched pages based on main frame host."/>
   <suffix name="Url"
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
index 3b848bc..04c5a94 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
@@ -1094,7 +1094,13 @@
   // X server opacity is in terms of 32 bit unsigned int space, and counts from
   // the opposite direction.
   // XChangeProperty() expects "cardinality" to be long.
-  unsigned long cardinality = static_cast<int>(opacity * 255) * 0x1010101;
+
+  // Scale opacity to [0 .. 255] range.
+  unsigned long opacity_8bit =
+      static_cast<unsigned long>(opacity * 255.0f) & 0xFF;
+  // Use opacity value for all channels.
+  const unsigned long channel_multiplier = 0x1010101;
+  unsigned long cardinality = opacity_8bit * channel_multiplier;
 
   if (cardinality == 0xffffffff) {
     XDeleteProperty(xdisplay_, xwindow_,
diff --git a/ui/views/widget/widget.cc b/ui/views/widget/widget.cc
index 4d0e7cf9..6063a224 100644
--- a/ui/views/widget/widget.cc
+++ b/ui/views/widget/widget.cc
@@ -700,6 +700,8 @@
 }
 
 void Widget::SetOpacity(float opacity) {
+  DCHECK(opacity >= 0.0f);
+  DCHECK(opacity <= 1.0f);
   native_widget_->SetOpacity(opacity);
 }