diff --git a/AUTHORS b/AUTHORS
index dd1590a1..dfef463 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -745,7 +745,6 @@
 Sanghyun Park <sh919.park@samsung.com>
 Sanghyup Lee <sh53.lee@samsung.com>
 Sangjoon Je <htamop@gmail.com>
-Sangwoo Ko <sangwoo.ko@navercorp.com>
 Sangwoo Ko <sangwoo108@gmail.com>
 Sanjoy Pal <ncj674@motorola.com>
 Sanjoy Pal <sanjoy.pal@samsung.com>
diff --git a/DEPS b/DEPS
index 8ebb442..280d92e6 100644
--- a/DEPS
+++ b/DEPS
@@ -105,11 +105,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '227d4e10276c0b9524dd985bf51a2708a930e050',
+  'skia_revision': '656cefe65d620f9aa7f689e412fa7720fe01c447',
   # 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': '3ca11d726de428f324f063cc792fdfec376cfeb9',
+  'v8_revision': 'c0b96b43766ee4efd9f75247763fd936be76dff8',
   # 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.
@@ -117,7 +117,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'beb0c946de6dec6c40bb438c5843e47fdb67aa6c',
+  'angle_revision': 'e8dd07969872491eeb18cedae67ec892433aa5ca',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling build tools
   # and whatever else without interference from each other.
@@ -165,7 +165,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '4e651334d46bfffe71894f956ad284f479170940',
+  'catapult_revision': '920acc5657065dbcce54ce3093368ce6db5aa66b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -213,7 +213,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'spv_tools_revision': 'ad0232dee5f5c15a5713d5b14e1763fcca6b02b8',
+  'spv_tools_revision': '6ff2f64702dcf298bcbbb33182ce82aa206e2b0a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -620,7 +620,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'cf257cec18f051e361065e89473b651fb32f1855',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '95d4c855637a039f4befbef17af1708ae06386b0',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -1106,7 +1106,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '6d2f3f4cb8bac1f7c4a945c73d07a33df74f22f9',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '56db013e24cfa3e048cc7ee842e0747294bb2590',
+    Var('webrtc_git') + '/src.git' + '@' + 'b5c6dd4349074a1ce98541a7959734772d833073',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1137,7 +1137,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@74a2dc070e4ec70f155f343e3795bd9bfd8a32e8',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@c0e1056a6c8eb6f2cba9bcaa92c64e9c0ebcfaa7',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContentsBackgroundThreadClient.java b/android_webview/java/src/org/chromium/android_webview/AwContentsBackgroundThreadClient.java
index aa0307d1..55eec96 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwContentsBackgroundThreadClient.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwContentsBackgroundThreadClient.java
@@ -7,8 +7,6 @@
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
 
-import java.util.HashMap;
-
 /**
  * Delegate for handling callbacks. All methods are called on the background thread.
  * "Background" means something that isn't UI or IO.
@@ -25,16 +23,7 @@
     private AwWebResourceResponse shouldInterceptRequestFromNative(String url, boolean isMainFrame,
             boolean hasUserGesture, String method, String[] requestHeaderNames,
             String[] requestHeaderValues) {
-        AwContentsClient.AwWebResourceRequest request =
-                new AwContentsClient.AwWebResourceRequest();
-        request.url = url;
-        request.isMainFrame = isMainFrame;
-        request.hasUserGesture = hasUserGesture;
-        request.method = method;
-        request.requestHeaders = new HashMap<String, String>(requestHeaderNames.length);
-        for (int i = 0; i < requestHeaderNames.length; ++i) {
-            request.requestHeaders.put(requestHeaderNames[i], requestHeaderValues[i]);
-        }
-        return shouldInterceptRequest(request);
+        return shouldInterceptRequest(new AwContentsClient.AwWebResourceRequest(
+                url, isMainFrame, hasUserGesture, method, requestHeaderNames, requestHeaderValues));
     }
 }
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContentsClient.java b/android_webview/java/src/org/chromium/android_webview/AwContentsClient.java
index a688369..022d5fb 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwContentsClient.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwContentsClient.java
@@ -15,6 +15,8 @@
 import android.os.Looper;
 import android.os.Message;
 import android.provider.Browser;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.text.TextUtils;
 import android.view.KeyEvent;
 import android.view.View;
@@ -97,6 +99,30 @@
      * Parameters for the {@link AwContentsClient#shouldInterceptRequest} method.
      */
     public static class AwWebResourceRequest {
+        // Prefer using other constructors over this one.
+        public AwWebResourceRequest() {}
+
+        public AwWebResourceRequest(String url, boolean isMainFrame, boolean hasUserGesture,
+                String method, @Nullable HashMap<String, String> requestHeaders) {
+            this.url = url;
+            this.isMainFrame = isMainFrame;
+            this.hasUserGesture = hasUserGesture;
+            // Note: we intentionally let isRedirect default initialize to false. This is because we
+            // don't always know if this request is associated with a redirect or not.
+            this.method = method;
+            this.requestHeaders = requestHeaders;
+        }
+
+        public AwWebResourceRequest(String url, boolean isMainFrame, boolean hasUserGesture,
+                String method, @NonNull String[] requestHeaderNames,
+                @NonNull String[] requestHeaderValues) {
+            this(url, isMainFrame, hasUserGesture, method,
+                    new HashMap<String, String>(requestHeaderValues.length));
+            for (int i = 0; i < requestHeaderNames.length; ++i) {
+                this.requestHeaders.put(requestHeaderNames[i], requestHeaderValues[i]);
+            }
+        }
+
         // Url of the request.
         public String url;
         // Is this for the main frame or a child iframe?
@@ -167,12 +193,10 @@
         if (poller != null && poller.shouldCancelAllCallbacks()) return false;
 
         if (hasWebViewClient()) {
-            AwWebResourceRequest request = new AwWebResourceRequest();
-            request.url = url;
-            request.isMainFrame = isMainFrame;
-            request.hasUserGesture = hasUserGesture;
+            // Note: only GET requests can be overridden, so we hardcode the method.
+            AwWebResourceRequest request =
+                    new AwWebResourceRequest(url, isMainFrame, hasUserGesture, "GET", null);
             request.isRedirect = isRedirect;
-            request.method = "GET";  // Only GET requests can be overridden.
             return shouldOverrideUrlLoading(request);
         } else {
             return sendBrowsingIntent(context, url, hasUserGesture, isRedirect);
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContentsClientBridge.java b/android_webview/java/src/org/chromium/android_webview/AwContentsClientBridge.java
index 6e19c61..d3f6b994 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwContentsClientBridge.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwContentsClientBridge.java
@@ -278,15 +278,8 @@
             String[] requestHeaderNames, String[] requestHeaderValues,
             // WebResourceError
             int errorCode, String description, boolean safebrowsingHit) {
-        AwContentsClient.AwWebResourceRequest request = new AwContentsClient.AwWebResourceRequest();
-        request.url = url;
-        request.isMainFrame = isMainFrame;
-        request.hasUserGesture = hasUserGesture;
-        request.method = method;
-        request.requestHeaders = new HashMap<String, String>(requestHeaderNames.length);
-        for (int i = 0; i < requestHeaderNames.length; ++i) {
-            request.requestHeaders.put(requestHeaderNames[i], requestHeaderValues[i]);
-        }
+        AwContentsClient.AwWebResourceRequest request = new AwContentsClient.AwWebResourceRequest(
+                url, isMainFrame, hasUserGesture, method, requestHeaderNames, requestHeaderValues);
         AwContentsClient.AwWebResourceError error = new AwContentsClient.AwWebResourceError();
         error.errorCode = errorCode;
         error.description = description;
@@ -324,15 +317,8 @@
             String url, boolean isMainFrame, boolean hasUserGesture, String method,
             String[] requestHeaderNames, String[] requestHeaderValues, int threatType,
             final int requestId) {
-        AwContentsClient.AwWebResourceRequest request = new AwContentsClient.AwWebResourceRequest();
-        request.url = url;
-        request.isMainFrame = isMainFrame;
-        request.hasUserGesture = hasUserGesture;
-        request.method = method;
-        request.requestHeaders = new HashMap<String, String>(requestHeaderNames.length);
-        for (int i = 0; i < requestHeaderNames.length; ++i) {
-            request.requestHeaders.put(requestHeaderNames[i], requestHeaderValues[i]);
-        }
+        AwContentsClient.AwWebResourceRequest request = new AwContentsClient.AwWebResourceRequest(
+                url, isMainFrame, hasUserGesture, method, requestHeaderNames, requestHeaderValues);
 
         // TODO(ntfschr): remove clang-format directives once crbug/764582 is resolved
         // clang-format off
@@ -354,15 +340,8 @@
             // WebResourceResponse
             String mimeType, String encoding, int statusCode, String reasonPhrase,
             String[] responseHeaderNames, String[] responseHeaderValues) {
-        AwContentsClient.AwWebResourceRequest request = new AwContentsClient.AwWebResourceRequest();
-        request.url = url;
-        request.isMainFrame = isMainFrame;
-        request.hasUserGesture = hasUserGesture;
-        request.method = method;
-        request.requestHeaders = new HashMap<String, String>(requestHeaderNames.length);
-        for (int i = 0; i < requestHeaderNames.length; ++i) {
-            request.requestHeaders.put(requestHeaderNames[i], requestHeaderValues[i]);
-        }
+        AwContentsClient.AwWebResourceRequest request = new AwContentsClient.AwWebResourceRequest(
+                url, isMainFrame, hasUserGesture, method, requestHeaderNames, requestHeaderValues);
         Map<String, String> responseHeaders =
                 new HashMap<String, String>(responseHeaderNames.length);
         // Note that we receive un-coalesced response header lines, thus we need to combine
diff --git a/ash/accessibility/accessibility_controller.h b/ash/accessibility/accessibility_controller.h
index 1548b80..93a9b7d 100644
--- a/ash/accessibility/accessibility_controller.h
+++ b/ash/accessibility/accessibility_controller.h
@@ -17,6 +17,7 @@
 #include "base/time/time.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
 #include "ui/accessibility/ax_enums.mojom.h"
+#include "ui/accessibility/ax_tree_id.h"
 
 class PrefChangeRegistrar;
 class PrefRegistrySimple;
@@ -154,6 +155,11 @@
   // this controller.
   void NotifyAccessibilityStatusChanged();
 
+  void set_remote_ax_tree_id(const ui::AXTreeID& tree_id) {
+    remote_ax_tree_id_ = tree_id;
+  }
+  const ui::AXTreeID& remote_ax_tree_id() const { return remote_ax_tree_id_; }
+
   // mojom::AccessibilityController:
   void SetClient(mojom::AccessibilityControllerClientPtr client) override;
   void SetDarkenScreen(bool darken) override;
@@ -242,6 +248,10 @@
   // Used to force the backlights off to darken the screen.
   std::unique_ptr<ScopedBacklightsForcedOff> scoped_backlights_forced_off_;
 
+  // Tree ID to use for remote mojo applications.
+  // TODO(jamescook): Remove this when ash generates multiple AXTreeIDs.
+  ui::AXTreeID remote_ax_tree_id_;
+
   base::ObserverList<AccessibilityObserver>::Unchecked observers_;
 
   DISALLOW_COPY_AND_ASSIGN(AccessibilityController);
diff --git a/ash/assistant/ui/logo_view/logo_view.cc b/ash/assistant/ui/logo_view/logo_view.cc
index 2d76c9f..2df76d7 100644
--- a/ash/assistant/ui/logo_view/logo_view.cc
+++ b/ash/assistant/ui/logo_view/logo_view.cc
@@ -72,7 +72,7 @@
 }
 
 void LogoView::SetSpeechLevel(float speech_level) {
-  sound_level_input_value_provider_.set_speech_level(speech_level);
+  sound_level_input_value_provider_.SetSpeechLevel(speech_level);
 }
 
 int64_t LogoView::StartTimer() {
diff --git a/ash/display/window_tree_host_manager.cc b/ash/display/window_tree_host_manager.cc
index 81304dd..7d28fa3 100644
--- a/ash/display/window_tree_host_manager.cc
+++ b/ash/display/window_tree_host_manager.cc
@@ -770,7 +770,8 @@
 }
 
 ui::EventDispatchDetails WindowTreeHostManager::DispatchKeyEventPostIME(
-    ui::KeyEvent* event) {
+    ui::KeyEvent* event,
+    base::OnceCallback<void(bool)> ack_callback) {
   aura::Window* root_window = nullptr;
   if (event->target()) {
     root_window = static_cast<aura::Window*>(event->target())->GetRootWindow();
@@ -783,7 +784,8 @@
     root_window = active_window ? active_window->GetRootWindow()
                                 : Shell::GetPrimaryRootWindow();
   }
-  return root_window->GetHost()->DispatchKeyEventPostIME(event);
+  return root_window->GetHost()->DispatchKeyEventPostIME(
+      event, std::move(ack_callback));
 }
 
 AshWindowTreeHost* WindowTreeHostManager::AddWindowTreeHostForDisplay(
diff --git a/ash/display/window_tree_host_manager.h b/ash/display/window_tree_host_manager.h
index 8805ecf..67d8b722 100644
--- a/ash/display/window_tree_host_manager.h
+++ b/ash/display/window_tree_host_manager.h
@@ -178,7 +178,8 @@
 
   // ui::internal::InputMethodDelegate overrides:
   ui::EventDispatchDetails DispatchKeyEventPostIME(
-      ui::KeyEvent* event) override;
+      ui::KeyEvent* event,
+      base::OnceCallback<void(bool)> ack_callback) override;
 
  private:
   FRIEND_TEST_ALL_PREFIXES(WindowTreeHostManagerTest, BoundsUpdated);
diff --git a/ash/display/window_tree_host_manager_unittest.cc b/ash/display/window_tree_host_manager_unittest.cc
index 6a38208b..791f9ee 100644
--- a/ash/display/window_tree_host_manager_unittest.cc
+++ b/ash/display/window_tree_host_manager_unittest.cc
@@ -1704,7 +1704,8 @@
   dispatcher_api.set_target(
       Shell::Get()->window_tree_host_manager()->GetRootWindowForDisplayId(
           GetSecondaryDisplay().id()));
-  Shell::Get()->window_tree_host_manager()->DispatchKeyEventPostIME(&key_event);
+  Shell::Get()->window_tree_host_manager()->DispatchKeyEventPostIME(
+      &key_event, base::NullCallback());
   // As long as nothing crashes, we're good.
 }
 
diff --git a/ash/login/ui/lock_contents_view.cc b/ash/login/ui/lock_contents_view.cc
index 002c83f6..0bbed66d 100644
--- a/ash/login/ui/lock_contents_view.cc
+++ b/ash/login/ui/lock_contents_view.cc
@@ -1009,9 +1009,9 @@
 
 void LockContentsView::SuspendImminent(
     power_manager::SuspendImminent::Reason reason) {
-  LoginAuthUserView* auth_user = CurrentBigUserView()->auth_user();
-  if (auth_user)
-    auth_user->password_view()->Clear();
+  LoginBigUserView* big_user = CurrentBigUserView();
+  if (big_user && big_user->auth_user())
+    big_user->auth_user()->password_view()->Clear();
 }
 
 void LockContentsView::ShowAuthErrorMessageForDebug(int unlock_attempt) {
diff --git a/ash/system/tray/tray_event_filter.cc b/ash/system/tray/tray_event_filter.cc
index b90cc1b..385f26f0 100644
--- a/ash/system/tray/tray_event_filter.cc
+++ b/ash/system/tray/tray_event_filter.cc
@@ -25,30 +25,30 @@
 void TrayEventFilter::AddBubble(TrayBubbleBase* bubble) {
   bool was_empty = bubbles_.empty();
   bubbles_.insert(bubble);
-  if (was_empty && !bubbles_.empty()) {
-    Shell::Get()->AddPointerWatcher(this,
-                                    views::PointerWatcherEventTypes::BASIC);
-  }
+  if (was_empty && !bubbles_.empty())
+    Shell::Get()->AddPreTargetHandler(this);
 }
 
 void TrayEventFilter::RemoveBubble(TrayBubbleBase* bubble) {
   bubbles_.erase(bubble);
   if (bubbles_.empty())
-    Shell::Get()->RemovePointerWatcher(this);
+    Shell::Get()->RemovePreTargetHandler(this);
 }
 
-void TrayEventFilter::OnPointerEventObserved(
-    const ui::PointerEvent& event,
-    const gfx::Point& location_in_screen,
-    gfx::NativeView target) {
-  if (event.type() == ui::ET_POINTER_DOWN)
-    ProcessPressedEvent(location_in_screen, target);
+void TrayEventFilter::OnMouseEvent(ui::MouseEvent* event) {
+  if (event->type() == ui::ET_MOUSE_PRESSED)
+    ProcessPressedEvent(*event);
 }
 
-void TrayEventFilter::ProcessPressedEvent(const gfx::Point& location_in_screen,
-                                          gfx::NativeView target) {
+void TrayEventFilter::OnTouchEvent(ui::TouchEvent* event) {
+  if (event->type() == ui::ET_TOUCH_PRESSED)
+    ProcessPressedEvent(*event);
+}
+
+void TrayEventFilter::ProcessPressedEvent(const ui::LocatedEvent& event) {
   // The hit target window for the virtual keyboard isn't the same as its
   // views::Widget.
+  aura::Window* target = static_cast<aura::Window*>(event.target());
   const views::Widget* target_widget =
       views::Widget::GetTopLevelWidgetForNativeView(target);
   const aura::Window* container =
@@ -74,9 +74,10 @@
   std::set<TrayBackgroundView*> trays;
   // Check the boundary for all bubbles, and do not handle the event if it
   // happens inside of any of those bubbles.
-  for (std::set<TrayBubbleBase*>::const_iterator iter = bubbles_.begin();
-       iter != bubbles_.end(); ++iter) {
-    const TrayBubbleBase* bubble = *iter;
+  const gfx::Point screen_location =
+      event.target() ? event.target()->GetScreenLocation(event)
+                     : event.root_location();
+  for (const TrayBubbleBase* bubble : bubbles_) {
     const views::Widget* bubble_widget = bubble->GetBubbleWidget();
     if (!bubble_widget)
       continue;
@@ -96,24 +97,21 @@
         bubble_container_id == kShellWindowId_SettingBubbleContainer) {
       bounds.Intersect(bubble_widget->GetWorkAreaBoundsInScreen());
     }
-    if (bounds.Contains(location_in_screen))
+    if (bounds.Contains(screen_location))
       continue;
     if (bubble->GetTray()) {
       // If the user clicks on the parent tray, don't process the event here,
       // let the tray logic handle the event and determine show/hide behavior.
       bounds = bubble->GetTray()->GetBoundsInScreen();
-      if (bounds.Contains(location_in_screen))
+      if (bounds.Contains(screen_location))
         continue;
     }
-    trays.insert((*iter)->GetTray());
+    trays.insert(bubble->GetTray());
   }
 
-  // Close all bubbles other than the one a user clicked on the tray
-  // or its bubble.
-  for (std::set<TrayBackgroundView*>::iterator iter = trays.begin();
-       iter != trays.end(); ++iter) {
-    (*iter)->ClickedOutsideBubble();
-  }
+  // Close all bubbles other than the one that the user clicked on.
+  for (TrayBackgroundView* tray_background_view : trays)
+    tray_background_view->ClickedOutsideBubble();
 }
 
 }  // namespace ash
diff --git a/ash/system/tray/tray_event_filter.h b/ash/system/tray/tray_event_filter.h
index f8f37ba..678999a 100644
--- a/ash/system/tray/tray_event_filter.h
+++ b/ash/system/tray/tray_event_filter.h
@@ -9,14 +9,10 @@
 
 #include "ash/ash_export.h"
 #include "base/macros.h"
-#include "ui/views/pointer_watcher.h"
-
-namespace gfx {
-class Point;
-}
+#include "ui/events/event_handler.h"
 
 namespace ui {
-class PointerEvent;
+class LocatedEvent;
 }
 
 namespace ash {
@@ -24,7 +20,7 @@
 
 // Handles events for a tray bubble, e.g. to close the system tray bubble when
 // the user clicks outside it.
-class ASH_EXPORT TrayEventFilter : public views::PointerWatcher {
+class ASH_EXPORT TrayEventFilter : public ui::EventHandler {
  public:
   TrayEventFilter();
   ~TrayEventFilter() override;
@@ -32,14 +28,12 @@
   void AddBubble(TrayBubbleBase* bubble);
   void RemoveBubble(TrayBubbleBase* bubble);
 
-  // views::PointerWatcher:
-  void OnPointerEventObserved(const ui::PointerEvent& event,
-                              const gfx::Point& location_in_screen,
-                              gfx::NativeView target) override;
+  // ui::EventHandler:
+  void OnMouseEvent(ui::MouseEvent* event) override;
+  void OnTouchEvent(ui::TouchEvent* event) override;
 
  private:
-  void ProcessPressedEvent(const gfx::Point& location_in_screen,
-                           gfx::NativeView target);
+  void ProcessPressedEvent(const ui::LocatedEvent& event);
 
   std::set<TrayBubbleBase*> bubbles_;
 
diff --git a/ash/system/tray/tray_event_filter_unittest.cc b/ash/system/tray/tray_event_filter_unittest.cc
index ab7ad58..cb837e3 100644
--- a/ash/system/tray/tray_event_filter_unittest.cc
+++ b/ash/system/tray/tray_event_filter_unittest.cc
@@ -25,24 +25,18 @@
   TrayEventFilterTest() = default;
   ~TrayEventFilterTest() override = default;
 
-  gfx::Point outside_point() {
-    gfx::Rect tray_bounds = GetSystemTrayBoundsInScreen();
-    return tray_bounds.bottom_right() + gfx::Vector2d(1, 1);
-  }
-  ui::PointerEvent outside_event() {
+  ui::MouseEvent outside_event() {
+    const gfx::Rect tray_bounds = GetSystemTrayBoundsInScreen();
+    const gfx::Point point = tray_bounds.bottom_right() + gfx::Vector2d(1, 1);
     const base::TimeTicks time = base::TimeTicks::Now();
-    return ui::PointerEvent(ui::MouseEvent(
-        ui::ET_MOUSE_PRESSED, outside_point(), outside_point(), time, 0, 0));
+    return ui::MouseEvent(ui::ET_MOUSE_PRESSED, point, point, time, 0, 0);
   }
 
-  gfx::Point inside_point() {
-    gfx::Rect tray_bounds = GetSystemTrayBoundsInScreen();
-    return tray_bounds.origin();
-  }
-  ui::PointerEvent inside_event() {
+  ui::MouseEvent inside_event() {
+    const gfx::Rect tray_bounds = GetSystemTrayBoundsInScreen();
+    const gfx::Point point = tray_bounds.origin();
     const base::TimeTicks time = base::TimeTicks::Now();
-    return ui::PointerEvent(ui::MouseEvent(ui::ET_MOUSE_PRESSED, inside_point(),
-                                           inside_point(), time, 0, 0));
+    return ui::MouseEvent(ui::ET_MOUSE_PRESSED, point, point, time, 0, 0);
   }
 
  protected:
@@ -93,8 +87,8 @@
   EXPECT_TRUE(IsBubbleShown());
 
   // Clicking outside should close the bubble.
-  TrayEventFilter* filter = GetTrayEventFilter();
-  filter->OnPointerEventObserved(outside_event(), outside_point(), nullptr);
+  ui::MouseEvent event = outside_event();
+  GetTrayEventFilter()->OnMouseEvent(&event);
   EXPECT_FALSE(IsBubbleShown());
 }
 
@@ -103,8 +97,8 @@
   EXPECT_TRUE(IsBubbleShown());
 
   // Clicking inside should not close the bubble
-  TrayEventFilter* filter = GetTrayEventFilter();
-  filter->OnPointerEventObserved(inside_event(), inside_point(), nullptr);
+  ui::MouseEvent event = inside_event();
+  GetTrayEventFilter()->OnMouseEvent(&event);
   EXPECT_TRUE(IsBubbleShown());
 }
 
@@ -120,9 +114,9 @@
   EXPECT_TRUE(IsBubbleShown());
 
   // Clicking on MenuContainer should not close the bubble.
-  TrayEventFilter* filter = GetTrayEventFilter();
-  filter->OnPointerEventObserved(outside_event(), outside_point(),
-                                 menu_window.get());
+  ui::MouseEvent event = outside_event();
+  ui::Event::DispatcherApi(&event).set_target(menu_window.get());
+  GetTrayEventFilter()->OnMouseEvent(&event);
   EXPECT_TRUE(IsBubbleShown());
 }
 
@@ -141,9 +135,9 @@
   EXPECT_TRUE(IsBubbleShown());
 
   // Clicking on StatusContainer should not close the bubble.
-  TrayEventFilter* filter = GetTrayEventFilter();
-  filter->OnPointerEventObserved(outside_event(), outside_point(),
-                                 popup_window.get());
+  ui::MouseEvent event = outside_event();
+  ui::Event::DispatcherApi(&event).set_target(popup_window.get());
+  GetTrayEventFilter()->OnMouseEvent(&event);
   EXPECT_TRUE(IsBubbleShown());
 }
 
@@ -161,9 +155,9 @@
   EXPECT_TRUE(IsBubbleShown());
 
   // Clicking on KeyboardContainer should not close the bubble.
-  TrayEventFilter* filter = GetTrayEventFilter();
-  filter->OnPointerEventObserved(outside_event(), outside_point(),
-                                 keyboard_window.get());
+  ui::MouseEvent event = outside_event();
+  ui::Event::DispatcherApi(&event).set_target(keyboard_window.get());
+  GetTrayEventFilter()->OnMouseEvent(&event);
   EXPECT_TRUE(IsBubbleShown());
 }
 
diff --git a/ash/wm/non_client_frame_controller.cc b/ash/wm/non_client_frame_controller.cc
index d38fa01..6a34225 100644
--- a/ash/wm/non_client_frame_controller.cc
+++ b/ash/wm/non_client_frame_controller.cc
@@ -10,6 +10,7 @@
 #include <string>
 #include <vector>
 
+#include "ash/accessibility/accessibility_controller.h"
 #include "ash/frame/non_client_frame_view_ash.h"
 #include "ash/public/cpp/ash_constants.h"
 #include "ash/public/cpp/ash_layout_constants.h"
@@ -27,6 +28,7 @@
 #include "services/ws/window_properties.h"
 #include "services/ws/window_service.h"
 #include "ui/accessibility/ax_node_data.h"
+#include "ui/accessibility/ax_tree_id.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/mus/property_converter.h"
 #include "ui/aura/mus/property_utils.h"
@@ -36,7 +38,6 @@
 #include "ui/base/hit_test.h"
 #include "ui/compositor/layer.h"
 #include "ui/gfx/geometry/vector2d.h"
-#include "ui/views/mus/ax_remote_host.h"
 #include "ui/views/widget/native_widget_aura.h"
 #include "ui/views/widget/widget.h"
 #include "ui/wm/core/coordinate_conversion.h"
@@ -149,8 +150,9 @@
   // views::View:
   const char* GetClassName() const override { return "ContentsViewMus"; }
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override {
-    node_data->AddStringAttribute(ax::mojom::StringAttribute::kChildTreeId,
-                                  views::RemoteAXTreeID());
+    node_data->AddStringAttribute(
+        ax::mojom::StringAttribute::kChildTreeId,
+        Shell::Get()->accessibility_controller()->remote_ax_tree_id());
     node_data->role = ax::mojom::Role::kClient;
   }
 
diff --git a/ash/wm/non_client_frame_controller_unittest.cc b/ash/wm/non_client_frame_controller_unittest.cc
index cf437ff..20b61c5 100644
--- a/ash/wm/non_client_frame_controller_unittest.cc
+++ b/ash/wm/non_client_frame_controller_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "ash/wm/non_client_frame_controller.h"
 
+#include "ash/accessibility/accessibility_controller.h"
 #include "ash/public/cpp/ash_layout_constants.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
@@ -26,7 +27,6 @@
 #include "ui/compositor/compositor.h"
 #include "ui/compositor/test/draw_waiter_for_test.h"
 #include "ui/compositor/test/fake_context_factory.h"
-#include "ui/views/mus/ax_remote_host.h"
 #include "ui/views/widget/widget.h"
 
 namespace ash {
@@ -226,15 +226,16 @@
 }
 
 TEST_F(NonClientFrameControllerTest, ExposesChildTreeIdToAccessibility) {
+  const ui::AXTreeID ax_tree_id("123");
+  Shell::Get()->accessibility_controller()->set_remote_ax_tree_id(ax_tree_id);
   std::unique_ptr<aura::Window> window = CreateTestWindow();
   NonClientFrameController* non_client_frame_controller =
       NonClientFrameController::Get(window.get());
   views::View* contents_view = non_client_frame_controller->GetContentsView();
   ui::AXNodeData ax_node_data;
   contents_view->GetAccessibleNodeData(&ax_node_data);
-  EXPECT_EQ(views::RemoteAXTreeID(),
-            ax_node_data.GetStringAttribute(
-                ax::mojom::StringAttribute::kChildTreeId));
+  EXPECT_EQ(ax_tree_id, ax_node_data.GetStringAttribute(
+                            ax::mojom::StringAttribute::kChildTreeId));
   EXPECT_EQ(ax::mojom::Role::kClient, ax_node_data.role);
 }
 
diff --git a/base/metrics/histogram.cc b/base/metrics/histogram.cc
index 0f4efae09..009c88f 100644
--- a/base/metrics/histogram.cc
+++ b/base/metrics/histogram.cc
@@ -192,23 +192,23 @@
     }
 #endif
 
-    const BucketRanges* registered_ranges =
-        StatisticsRecorder::RegisterOrDeleteDuplicateRanges(created_ranges);
-
 // Temporary check for https://crbug.com/836238
 #if defined(OS_WIN)  // Only Windows has a debugger that makes this useful.
     std::unique_ptr<const BucketRanges> recreated_ranges(CreateRanges());
     for (uint32_t i = 0; i < bucket_count_; ++i) {
-      uint32_t created_range = recreated_ranges->range(i);
-      uint32_t registered_range = registered_ranges->range(i);
-      if (created_range != registered_range) {
+      uint32_t created_range = created_ranges->range(i);
+      uint32_t recreated_range = recreated_ranges->range(i);
+      debug::Alias(&created_range);
+      debug::Alias(&recreated_range);
+      if (created_range != recreated_range) {
         // Create local copies of the parameters to be sure they'll be available
         // in the crash dump for the debugger to see.
         DEBUG_ALIAS_FOR_CSTR(h_name, name_.c_str(), 100);
         HistogramType h_type = histogram_type_;
         uint32_t b_count = bucket_count_;
-        size_t c_count = recreated_ranges->size();
-        size_t r_count = registered_ranges->size();
+        size_t c_count = recreated_ranges->size() - 1;
+        size_t r_count = recreated_ranges->size() - 1;
+        CHECK(recreated_ranges->Equals(created_ranges)) << name_;
         debug::Alias(&h_type);
         debug::Alias(&b_count);
         debug::Alias(&c_count);
@@ -216,6 +216,40 @@
         CHECK(false) << name_;
       }
     }
+    CHECK(recreated_ranges->Equals(created_ranges));
+#endif
+
+    const BucketRanges* registered_ranges =
+        StatisticsRecorder::RegisterOrDeleteDuplicateRanges(created_ranges);
+
+// Temporary check for https://crbug.com/836238
+#if defined(OS_WIN)  // Only Windows has a debugger that makes this useful.
+    bool using_created_ranges = (registered_ranges == created_ranges);
+    bool equal_ranges = registered_ranges->Equals(recreated_ranges.get());
+    debug::Alias(&using_created_ranges);
+    debug::Alias(&equal_ranges);
+    for (uint32_t i = 0; i < bucket_count_; ++i) {
+      uint32_t created_range = recreated_ranges->range(i);
+      uint32_t registered_range = registered_ranges->range(i);
+      debug::Alias(&created_range);
+      debug::Alias(&registered_range);
+      if (created_range != registered_range) {
+        // Create local copies of the parameters to be sure they'll be available
+        // in the crash dump for the debugger to see.
+        DEBUG_ALIAS_FOR_CSTR(h_name, name_.c_str(), 100);
+        HistogramType h_type = histogram_type_;
+        uint32_t b_count = bucket_count_;
+        size_t c_count = recreated_ranges->size() - 1;
+        size_t r_count = registered_ranges->size() - 1;
+        CHECK(recreated_ranges->Equals(registered_ranges)) << name_;
+        debug::Alias(&h_type);
+        debug::Alias(&b_count);
+        debug::Alias(&c_count);
+        debug::Alias(&r_count);
+        CHECK(false) << name_;
+      }
+    }
+    CHECK(recreated_ranges->Equals(registered_ranges));
 #endif
 
     // In most cases, the bucket-count, minimum, and maximum values are known
diff --git a/build/android/apk_operations.py b/build/android/apk_operations.py
index 18b0420..66e210c1 100755
--- a/build/android/apk_operations.py
+++ b/build/android/apk_operations.py
@@ -953,11 +953,13 @@
 
     self.devices = []
     if self.need_device_args:
+      # See https://crbug.com/887964 regarding bundle support in apk_helper.
+      abis = self.apk_helper.GetAbis() if not self.is_bundle else None
       self.devices = device_utils.DeviceUtils.HealthyDevices(
           device_arg=args.devices,
           enable_device_files_cache=bool(args.output_directory),
           default_retries=0,
-          abis=self.apk_helper.GetAbis())
+          abis=abis)
       # TODO(agrieve): Device cache should not depend on output directory.
       #     Maybe put int /tmp?
       _LoadDeviceCaches(self.devices, args.output_directory)
diff --git a/build/config/win/BUILD.gn b/build/config/win/BUILD.gn
index c5c44479..38a1a97 100644
--- a/build/config/win/BUILD.gn
+++ b/build/config/win/BUILD.gn
@@ -136,13 +136,18 @@
     ldflags += [ "/TIMESTAMP:" + build_timestamp ]
   }
 
-  if (!is_debug && !is_component_build) {
+  if (!is_debug && !is_component_build && !use_libfuzzer) {
     # Enable standard linker optimizations like GC (/OPT:REF) and ICF in static
     # release builds. These are implied by /PROFILE below, but /PROFILE is
     # incompatible with /debug:fastlink and LLD ignores it as of this writing.
     # Release builds always want these optimizations, so enable them explicitly.
+    # TODO(crbug.com/884545): Remove the checks for use_libfuzzer when
+    # libFuzzer's issues with /OPT:REF are resolved upstream.
+    if (!use_libfuzzer) {
+      ldflags += [ "/OPT:REF" ]
+    }
+
     ldflags += [
-      "/OPT:REF",
       "/OPT:ICF",
       "/INCREMENTAL:NO",
       "/FIXED:NO",
@@ -159,7 +164,9 @@
     # PDB file by about 5%) but does not otherwise alter the output binary. It
     # is enabled opportunistically for builds where it is not prohibited (not
     # supported when incrementally linking, or using /debug:fastlink).
-    if (!is_win_fastlink) {
+    # /PROFILE implies /OPT:REF. Don't use it with libFuzzer while /OPT:REF
+    # can't be used with libFuzzer. See crbug.com/884545 for more details.
+    if (!is_win_fastlink && !use_libfuzzer) {
       ldflags += [ "/PROFILE" ]
     }
   }
diff --git a/chrome/android/java/res/layout/page_info.xml b/chrome/android/java/res/layout/page_info.xml
index 1fce45e..594c84e 100644
--- a/chrome/android/java/res/layout/page_info.xml
+++ b/chrome/android/java/res/layout/page_info.xml
@@ -65,6 +65,13 @@
             android:visibility="gone" />
 
         <TextView
+            android:id="@+id/page_info_stale_preview_timestamp"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAppearance="@style/BlackCaption"
+            android:visibility="gone" />
+
+        <TextView
             android:id="@+id/page_info_preview_load_original"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
diff --git a/chrome/android/java/res/values/colors.xml b/chrome/android/java/res/values/colors.xml
index c6f3376f..131293c 100644
--- a/chrome/android/java/res/values/colors.xml
+++ b/chrome/android/java/res/values/colors.xml
@@ -166,9 +166,6 @@
     <!-- Bookmark UI colors -->
     <color name="bookmark_detail_section">#7C7B79</color>
 
-    <!-- Download home colors -->
-    <color name="download_image_loading_color">#BDC1C6</color>
-
     <!-- Favicon colors -->
     <color name="default_favicon_background_color">#787878</color>
 
diff --git a/chrome/android/java/res_download/drawable/async_image_view_unavailable.xml b/chrome/android/java/res_download/drawable/async_image_view_unavailable.xml
new file mode 100644
index 0000000..17d7157
--- /dev/null
+++ b/chrome/android/java/res_download/drawable/async_image_view_unavailable.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:targetApi="21"
+    android:width="100dp"
+    android:height="100dp"
+    android:viewportWidth="100.0"
+    android:viewportHeight="100.0">
+    <path
+        android:name="circle"
+        android:fillColor="@color/modern_grey_400"
+        android:pathData="M 20,60 c 0,-11 9,-20 20,-20 l0, 20 l20,0 c 0,11 -9,20 -20,20 c -11,0 -20,-9 -20,-20"/>
+    <path
+        android:name="square"
+        android:fillColor="@color/modern_grey_400"
+        android:pathData="M 40,40 l0,-15 l35,0 l0,35 l-15,0 c 0,-11 -9,-20 -20,-20 C"/>
+    <path
+        android:name="arc"
+        android:fillColor="@color/modern_grey_400"
+        android:fillAlpha="0"
+        android:pathData="M 40,60 l 0,-20 c 11,0 20,9 20,20"/>
+</vector>
\ No newline at end of file
diff --git a/chrome/android/java/res_download/drawable/image_loading_progress.xml b/chrome/android/java/res_download/drawable/async_image_view_waiting.xml
similarity index 77%
rename from chrome/android/java/res_download/drawable/image_loading_progress.xml
rename to chrome/android/java/res_download/drawable/async_image_view_waiting.xml
index 44a5c4f6..2fa4e1c 100644
--- a/chrome/android/java/res_download/drawable/image_loading_progress.xml
+++ b/chrome/android/java/res_download/drawable/async_image_view_waiting.xml
@@ -2,34 +2,12 @@
 <!-- Copyright 2018 The Chromium Authors. All rights reserved.
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
-
+     
 <animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     xmlns:aapt="http://schemas.android.com/aapt"
-    tools:targetApi="21">
-
-    <aapt:attr name="android:drawable">
-        <vector
-            android:width="100dp"
-            android:height="100dp"
-            android:viewportWidth="100.0"
-            android:viewportHeight="100.0">
-
-            <path
-                android:name="circle"
-                android:fillColor="@color/download_image_loading_color"
-                android:pathData="M 20,60 c 0,-11 9,-20 20,-20 l0, 20 l20,0 c 0,11 -9,20 -20,20 c -11,0 -20,-9 -20,-20"/>
-            <path
-                android:name="square"
-                android:fillColor="@color/download_image_loading_color"
-                android:pathData="M 40,40 l0,-15 l35,0 l0,35 l-15,0 c 0,-11 -9,-20 -20,-20 C"/>
-            <path
-                android:name="arc"
-                android:fillColor="@color/download_image_loading_color"
-                android:pathData="M 40,60 l 0,-20 c 11,0 20,9 20,20"/>
-
-        </vector>
-    </aapt:attr>
+    tools:targetApi="21"
+    android:drawable="@drawable/async_image_view_unavailable">
 
     <target android:name="circle">
         <aapt:attr name="android:animation">
@@ -100,4 +78,4 @@
         </aapt:attr>
     </target>
 
-</animated-vector>
+</animated-vector>
\ No newline at end of file
diff --git a/chrome/android/java/res_download/layout/download_manager_image_item.xml b/chrome/android/java/res_download/layout/download_manager_image_item.xml
index c27990a..264f2baa 100644
--- a/chrome/android/java/res_download/layout/download_manager_image_item.xml
+++ b/chrome/android/java/res_download/layout/download_manager_image_item.xml
@@ -13,14 +13,15 @@
     android:clickable="true"
     android:background="@color/modern_grey_100" >
 
-    <ImageView
+    <org.chromium.chrome.browser.download.home.list.view.SquareAsyncImageView
         android:id="@+id/thumbnail"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:background="@color/placeholder_thumbnail_bg_color"
+        android:layout_height="match_parent"
+        android:background="@color/modern_grey_300"
         android:scaleType="centerCrop"
         android:layout_gravity="center"
         android:adjustViewBounds="true"
+        style="@style/AsyncImageView"
         tools:ignore="ContentDescription" />
 
     <org.chromium.chrome.browser.download.home.view.SelectionView
diff --git a/chrome/android/java/res_download/layout/download_manager_in_progress_video_item.xml b/chrome/android/java/res_download/layout/download_manager_in_progress_video_item.xml
index 3342d3b..8557e4a 100644
--- a/chrome/android/java/res_download/layout/download_manager_in_progress_video_item.xml
+++ b/chrome/android/java/res_download/layout/download_manager_in_progress_video_item.xml
@@ -15,19 +15,20 @@
     app:columnCount="2"
     app:rowCount="3">
 
-    <org.chromium.ui.widget.RoundedCornerImageView
+    <org.chromium.chrome.browser.download.home.list.view.ForegroundRoundedCornerImageView
         android:id="@+id/thumbnail"
         android:layout_width="match_parent"
         android:layout_height="200dp"
         android:scaleType="centerInside"
         android:adjustViewBounds="true"
-        android:src="@color/modern_grey_300"
         app:layout_column="0"
         app:layout_row="0"
         app:layout_columnSpan="2"
         app:layout_gravity="center"
         app:cornerRadiusTopStart="@dimen/download_manager_prefetch_thumbnail_corner_radius"
-        app:cornerRadiusTopEnd="@dimen/download_manager_prefetch_thumbnail_corner_radius"/>
+        app:cornerRadiusTopEnd="@dimen/download_manager_prefetch_thumbnail_corner_radius"
+        app:roundedfillColor="@color/modern_grey_300"
+        app:foregroundCompat="@drawable/async_image_view_waiting" />
 
     <org.chromium.chrome.browser.download.home.list.view.CircularProgressView
         android:id="@+id/action_button"
diff --git a/chrome/android/java/res_download/layout/download_manager_video_item.xml b/chrome/android/java/res_download/layout/download_manager_video_item.xml
index d6aa37a..f113ee7f 100644
--- a/chrome/android/java/res_download/layout/download_manager_video_item.xml
+++ b/chrome/android/java/res_download/layout/download_manager_video_item.xml
@@ -15,19 +15,20 @@
     app:columnCount="2"
     app:rowCount="3">
 
-    <org.chromium.ui.widget.RoundedCornerImageView
+    <org.chromium.chrome.browser.download.home.list.view.AsyncImageView
         android:id="@+id/thumbnail"
         android:layout_width="match_parent"
         android:layout_height="200dp"
         android:scaleType="centerInside"
         android:adjustViewBounds="true"
-        android:src="@color/modern_grey_300"
         app:layout_column="0"
         app:layout_row="0"
         app:layout_columnSpan="2"
         app:layout_gravity="center"
         app:cornerRadiusTopStart="@dimen/download_manager_prefetch_thumbnail_corner_radius"
-        app:cornerRadiusTopEnd="@dimen/download_manager_prefetch_thumbnail_corner_radius"/>
+        app:cornerRadiusTopEnd="@dimen/download_manager_prefetch_thumbnail_corner_radius"
+        app:roundedfillColor="@color/modern_grey_300"
+        style="@style/AsyncImageView"/>
 
    <org.chromium.chrome.browser.download.home.view.SelectionView
        android:id="@+id/selection"
diff --git a/chrome/android/java/res_download/values-v17/attrs.xml b/chrome/android/java/res_download/values-v17/attrs.xml
index 91d0f9b..d98896f 100644
--- a/chrome/android/java/res_download/values-v17/attrs.xml
+++ b/chrome/android/java/res_download/values-v17/attrs.xml
@@ -11,4 +11,11 @@
         <attr name="pauseSrc" format="reference" />
         <attr name="retrySrc" format="reference" />
     </declare-styleable>
+    <declare-styleable name="AsyncImageView">
+        <attr name="unavailableSrc" format="reference" />
+        <attr name="waitingSrc" format="reference" />
+    </declare-styleable>
+    <declare-styleable name="ForegroundRoundedCornerImageView">
+        <attr name="foregroundCompat" format="reference" />
+    </declare-styleable>
 </resources>
\ No newline at end of file
diff --git a/chrome/android/java/res_download/values-v17/ids.xml b/chrome/android/java/res_download/values-v17/ids.xml
new file mode 100644
index 0000000..a002f28
--- /dev/null
+++ b/chrome/android/java/res_download/values-v17/ids.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<resources>
+    <item name="circular_progress_view_action" type="id" />
+    <item name="circular_progress_view_progress" type="id" />
+</resources>
\ No newline at end of file
diff --git a/chrome/android/java/res_download/values-v17/styles.xml b/chrome/android/java/res_download/values-v17/styles.xml
index c4a125d..2eac51e 100644
--- a/chrome/android/java/res_download/values-v17/styles.xml
+++ b/chrome/android/java/res_download/values-v17/styles.xml
@@ -72,6 +72,8 @@
         <item name="android:layout_marginStart">8dp</item>
         <item name="android:layout_marginTop">8dp</item>
     </style>
+
+    <!-- Download Home V2 Snowflake Contender Styles -->
     <style name="SmallCircularProgress">
         <item name="android:layout_width">36dp</item>
         <item name="android:layout_height">36dp</item>
@@ -96,4 +98,8 @@
         <item name="retrySrc">@drawable/ic_play_arrow_white_36dp</item>
         <item name="pauseSrc">@drawable/ic_pause_white_36dp</item>
     </style>
+    <style name="AsyncImageView">
+        <item name="unavailableSrc">@drawable/async_image_view_unavailable</item>
+        <item name="waitingSrc">@drawable/async_image_view_waiting</item>
+    </style>
 </resources>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index ff156841..bb9a6d4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -2058,10 +2058,23 @@
     public boolean onKeyUp(int keyCode, KeyEvent event) {
         if (keyCode == KeyEvent.KEYCODE_BACK && !isTablet()) {
             mHandler.removeCallbacks(mShowHistoryRunnable);
+            mShowHistoryRunnable = null;
         }
         return super.onKeyUp(keyCode, event);
     }
 
+    @VisibleForTesting
+    public NavigationPopup getNavigationPopupForTesting() {
+        ThreadUtils.assertOnUiThread();
+        return mNavigationPopup;
+    }
+
+    @VisibleForTesting
+    public boolean hasPendingNavigationPopupForTesting() {
+        ThreadUtils.assertOnUiThread();
+        return mShowHistoryRunnable != null;
+    }
+
     private void showFullHistoryForTab() {
         if (!ChromeFeatureList.isInitialized()
                 || !ChromeFeatureList.isEnabled(ChromeFeatureList.LONG_PRESS_BACK_FOR_HISTORY)) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchContext.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchContext.java
index 6ec7a85..a4f88392 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchContext.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchContext.java
@@ -227,6 +227,15 @@
         onSelectionChanged();
     }
 
+    /** @return the current selection, or an empty string if data is invalid or nothing selected. */
+    String getSelection() {
+        if (TextUtils.isEmpty(mSurroundingText) || mSelectionEndOffset < mSelectionStartOffset
+                || mSelectionStartOffset < 0 || mSelectionEndOffset > mSurroundingText.length()) {
+            return "";
+        }
+        return mSurroundingText.substring(mSelectionStartOffset, mSelectionEndOffset);
+    }
+
     /**
      * Notifies this instance that the selection has been changed.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
index 5d4e464..29803be 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
@@ -1326,6 +1326,14 @@
             if (didSelect) {
                 assert mContext != null;
                 mContext.onSelectionAdjusted(startAdjust, endAdjust);
+                // There's a race condition when we select the word between this Ack response and
+                // the onSelectionChanged call.  Update the selection in case this method won the
+                // race so we ensure that there's a valid selected word.
+                // See https://crbug.com/889657 for details.
+                String adjustedSelection = mContext.getSelection();
+                if (!TextUtils.isEmpty(adjustedSelection)) {
+                    mSelectionController.setSelectedText(adjustedSelection);
+                }
                 showSelectionAsSearchInBar(mSelectionController.getSelectedText());
                 mInternalStateController.notifyFinishedWorkOn(InternalState.START_SHOWING_TAP_UI);
             } else {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java
index 41f76e2..8adae32 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java
@@ -197,6 +197,15 @@
     }
 
     /**
+     * Overrides the current internal setting that tracks the selection.
+     *
+     * @param selection The new selection value.
+     */
+    void setSelectedText(String selection) {
+        mSelectedText = selection;
+    }
+
+    /**
      * @return The Pixel to Device independent Pixel ratio.
      */
     float getPxToDp() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfo.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfo.java
index ab07ca9d..308e8d3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfo.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfo.java
@@ -248,6 +248,7 @@
 
         return new DownloadInfo.Builder()
                 .setContentId(item.id)
+                .setDownloadGuid(item.id.id)
                 .setFileName(item.title)
                 .setFilePath(item.filePath)
                 .setDescription(item.description)
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/glue/ThumbnailRequestGlue.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/glue/ThumbnailRequestGlue.java
index 9ec41e03..a5ff269c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/glue/ThumbnailRequestGlue.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/glue/ThumbnailRequestGlue.java
@@ -5,14 +5,10 @@
 package org.chromium.chrome.browser.download.home.glue;
 
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
 
 import org.chromium.base.Callback;
-import org.chromium.base.ContextUtils;
-import org.chromium.chrome.R;
 import org.chromium.chrome.browser.widget.ThumbnailProvider.ThumbnailRequest;
 import org.chromium.components.offline_items_collection.OfflineItem;
-import org.chromium.components.offline_items_collection.OfflineItemFilter;
 import org.chromium.components.offline_items_collection.OfflineItemVisuals;
 import org.chromium.components.offline_items_collection.VisualsCallback;
 
@@ -66,14 +62,6 @@
 
     @Override
     public boolean getThumbnail(Callback<Bitmap> callback) {
-        // TODO(shaktisahu, xingliu): Remove this after video thumbnail generation pipeline is done.
-        if (mItem.filter == OfflineItemFilter.FILTER_VIDEO) {
-            callback.onResult(BitmapFactory.decodeResource(
-                    ContextUtils.getApplicationContext().getResources(),
-                    R.drawable.audio_playing_square));
-            return true;
-        }
-
         return mProvider.getVisualsForItem(mItem.id, (id, visuals) -> {
             if (visuals == null) {
                 callback.onResult(null);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/UiUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/UiUtils.java
index 35ca576..de3eed5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/UiUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/UiUtils.java
@@ -117,7 +117,9 @@
     public static boolean canHaveThumbnails(OfflineItem item) {
         switch (item.filter) {
             case OfflineItemFilter.FILTER_PAGE:
-            case OfflineItemFilter.FILTER_VIDEO:
+            // TODO(shaktisahu, xingliu): Remove this after video thumbnail generation pipeline is
+            // done.
+            // case OfflineItemFilter.FILTER_VIDEO:
             case OfflineItemFilter.FILTER_IMAGE:
                 return true;
             default:
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/ImageViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/ImageViewHolder.java
index 637ac7e6..2ab9f1a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/ImageViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/ImageViewHolder.java
@@ -4,60 +4,58 @@
 
 package org.chromium.chrome.browser.download.home.list.holder;
 
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.ImageView;
 
 import org.chromium.chrome.browser.download.home.list.ListItem;
-import org.chromium.chrome.browser.download.home.view.LoadingBackground;
+import org.chromium.chrome.browser.download.home.list.ListProperties;
+import org.chromium.chrome.browser.download.home.list.view.AsyncImageView;
 import org.chromium.chrome.browser.modelutil.PropertyModel;
 import org.chromium.chrome.download.R;
-import org.chromium.components.offline_items_collection.OfflineItemVisuals;
+import org.chromium.components.offline_items_collection.ContentId;
+import org.chromium.components.offline_items_collection.OfflineItem;
 
 /** A {@link RecyclerView.ViewHolder} specifically meant to display an image {@code OfflineItem}. */
-public class ImageViewHolder extends ThumbnailAwareViewHolder {
-    private final int mImageHeightPx;
-    private LoadingBackground mLoadingBackground;
+public class ImageViewHolder extends ListItemViewHolder {
+    private final AsyncImageView mThumbnail;
+
+    private ContentId mBoundItemId;
 
     public static ImageViewHolder create(ViewGroup parent) {
         View view = LayoutInflater.from(parent.getContext())
                             .inflate(R.layout.download_manager_image_item, null);
-        int imageSize = parent.getContext().getResources().getDimensionPixelSize(
-                R.dimen.download_manager_image_width);
-        return new ImageViewHolder(view, imageSize);
+        return new ImageViewHolder(view);
     }
 
-    public ImageViewHolder(View view, int thumbnailSizePx) {
-        super(view, thumbnailSizePx, thumbnailSizePx);
-        mImageHeightPx = thumbnailSizePx;
-        mLoadingBackground = new LoadingBackground(view.getContext());
+    public ImageViewHolder(View view) {
+        super(view);
+        mThumbnail = itemView.findViewById(R.id.thumbnail);
     }
 
     // ThumbnailAwareViewHolder implementation.
     @Override
     public void bind(PropertyModel properties, ListItem item) {
-        super.bind(properties, item);
-        ListItem.OfflineItemListItem offlineItem = (ListItem.OfflineItemListItem) item;
-        View imageView = itemView.findViewById(R.id.thumbnail);
-        imageView.setContentDescription(offlineItem.item.title);
-        ViewGroup.LayoutParams layoutParams = imageView.getLayoutParams();
-        layoutParams.height =
-                offlineItem.spanFullWidth ? ViewGroup.LayoutParams.WRAP_CONTENT : mImageHeightPx;
-    }
+        OfflineItem offlineItem = ((ListItem.OfflineItemListItem) item).item;
 
-    @Override
-    void onVisualsChanged(ImageView view, OfflineItemVisuals visuals) {
-        view.setImageBitmap(visuals == null ? null : visuals.icon);
-    }
+        mThumbnail.setContentDescription(offlineItem.title);
 
-    @Override
-    protected void showLoadingView(ImageView view) {
-        mLoadingBackground.show(view);
-    }
+        if (offlineItem.id.equals(mBoundItemId)) return;
+        mBoundItemId = offlineItem.id;
 
-    @Override
-    protected void hideLoadingView() {
-        mLoadingBackground.hide();
+        mThumbnail.setAsyncImageDrawable((consumer, width, height) -> {
+            return properties.get(ListProperties.PROVIDER_VISUALS)
+                    .getVisuals(offlineItem, width, height, (id, visuals) -> {
+                        if (!id.equals(mBoundItemId)) return;
+
+                        Drawable drawable = null;
+                        if (visuals != null && visuals.icon != null) {
+                            drawable = new BitmapDrawable(itemView.getResources(), visuals.icon);
+                        }
+                        consumer.onResult(drawable);
+                    });
+        });
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/VideoViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/VideoViewHolder.java
index ef11049e..6b462f1c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/VideoViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/VideoViewHolder.java
@@ -4,28 +4,31 @@
 
 package org.chromium.chrome.browser.download.home.list.holder;
 
-import android.support.annotation.Nullable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.ImageView;
 import android.widget.TextView;
 
 import org.chromium.chrome.browser.download.home.list.ListItem;
+import org.chromium.chrome.browser.download.home.list.ListProperties;
 import org.chromium.chrome.browser.download.home.list.UiUtils;
-import org.chromium.chrome.browser.download.home.view.LoadingBackground;
+import org.chromium.chrome.browser.download.home.list.view.AsyncImageView;
 import org.chromium.chrome.browser.modelutil.PropertyModel;
 import org.chromium.chrome.download.R;
-import org.chromium.components.offline_items_collection.OfflineItemVisuals;
+import org.chromium.components.offline_items_collection.ContentId;
+import org.chromium.components.offline_items_collection.OfflineItem;
 
 /**
  * A {@link RecyclerView.ViewHolder} specifically meant to display a video {@code OfflineItem}.
  */
-public class VideoViewHolder extends ThumbnailAwareViewHolder {
+public class VideoViewHolder extends MoreButtonViewHolder {
     private final TextView mTitle;
     private final TextView mCaption;
-    private final ImageView mThumbnailView;
-    private LoadingBackground mLoadingBackground;
+    private final AsyncImageView mThumbnail;
+
+    private ContentId mBoundItemId;
 
     /**
      * Creates a new {@link VideoViewHolder} instance.
@@ -33,44 +36,42 @@
     public static VideoViewHolder create(ViewGroup parent) {
         View view = LayoutInflater.from(parent.getContext())
                             .inflate(R.layout.download_manager_video_item, null);
-        int thumbnailWidth = parent.getContext().getResources().getDimensionPixelSize(
-                R.dimen.download_manager_image_width);
-        int thumbnailHeight = parent.getContext().getResources().getDimensionPixelSize(
-                R.dimen.download_manager_image_width);
-        return new VideoViewHolder(view, thumbnailWidth, thumbnailHeight);
+
+        return new VideoViewHolder(view);
     }
 
-    public VideoViewHolder(View view, int thumbnailWidthPx, int thumbnailHeightPx) {
-        super(view, thumbnailWidthPx, thumbnailHeightPx);
+    public VideoViewHolder(View view) {
+        super(view);
 
-        mTitle = (TextView) itemView.findViewById(R.id.title);
-        mCaption = (TextView) itemView.findViewById(R.id.caption);
-        mThumbnailView = (ImageView) itemView.findViewById(R.id.thumbnail);
-        mLoadingBackground = new LoadingBackground(view.getContext());
+        mTitle = itemView.findViewById(R.id.title);
+        mCaption = itemView.findViewById(R.id.caption);
+        mThumbnail = itemView.findViewById(R.id.thumbnail);
     }
 
     @Override
     public void bind(PropertyModel properties, ListItem item) {
         super.bind(properties, item);
-        ListItem.OfflineItemListItem offlineItem = (ListItem.OfflineItemListItem) item;
 
-        mTitle.setText(offlineItem.item.title);
-        mCaption.setText(UiUtils.generateGenericCaption(offlineItem.item));
-        mThumbnailView.setContentDescription(offlineItem.item.title);
-    }
+        OfflineItem offlineItem = ((ListItem.OfflineItemListItem) item).item;
 
-    @Override
-    void onVisualsChanged(ImageView view, @Nullable OfflineItemVisuals visuals) {
-        view.setImageBitmap(visuals == null ? null : visuals.icon);
-    }
+        mTitle.setText(offlineItem.title);
+        mCaption.setText(UiUtils.generateGenericCaption(offlineItem));
+        mThumbnail.setContentDescription(offlineItem.title);
 
-    @Override
-    protected void showLoadingView(ImageView view) {
-        mLoadingBackground.show(view);
-    }
+        if (offlineItem.id.equals(mBoundItemId)) return;
+        mBoundItemId = offlineItem.id;
 
-    @Override
-    protected void hideLoadingView() {
-        mLoadingBackground.hide();
+        mThumbnail.setAsyncImageDrawable((consumer, width, height) -> {
+            return properties.get(ListProperties.PROVIDER_VISUALS)
+                    .getVisuals(offlineItem, width, height, (id, visuals) -> {
+                        if (!id.equals(mBoundItemId)) return;
+
+                        Drawable drawable = null;
+                        if (visuals != null && visuals.icon != null) {
+                            drawable = new BitmapDrawable(itemView.getResources(), visuals.icon);
+                        }
+                        consumer.onResult(drawable);
+                    });
+        });
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/AsyncImageView.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/AsyncImageView.java
new file mode 100644
index 0000000..54c1d5f
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/AsyncImageView.java
@@ -0,0 +1,111 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.download.home.list.view;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+
+import org.chromium.base.Callback;
+import org.chromium.chrome.download.R;
+
+/**
+ * Helper class to handle asynchronously loading an image and displaying it when ready.  This class
+ * supports both a 'waiting' drawable and an 'unavailable' drawable that will be used in the
+ * foreground when the async image isn't present yet.
+ */
+public class AsyncImageView extends ForegroundRoundedCornerImageView {
+    /** An interface that provides a way for this class to query for a {@link Drawable}. */
+    @FunctionalInterface
+    public interface Factory {
+        /**
+         * Called by {@link AsyncImageView} to start the process of asynchronously loading a
+         * {@link Drawable}.
+         *
+         * @param consumer The {@link Callback} to notify with the result.
+         * @param widthPx  The desired width of the {@link Drawable} if applicable (not required to
+         * match).
+         * @param heightPx The desired height of the {@link Drawable} if applicable (not required to
+         * match).
+         * @return         A {@link Runnable} that can be triggered to cancel the outstanding
+         * request.
+         */
+        Runnable get(Callback<Drawable> consumer, int widthPx, int heightPx);
+    }
+
+    private final Drawable mUnavailableDrawable;
+    private final Drawable mWaitingDrawable;
+
+    /**
+     * Used to handle synchronous responses to the callback in
+     * {@link #setAsyncImageDrawable(Factory)}.
+     */
+    private boolean mWaitingForResponse;
+    private Runnable mCancelable;
+
+    /** Creates an {@link AsyncImageDrawable instance. */
+    public AsyncImageView(Context context) {
+        this(context, null, 0);
+    }
+
+    /** Creates an {@link AsyncImageDrawable instance. */
+    public AsyncImageView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    /** Creates an {@link AsyncImageDrawable instance. */
+    public AsyncImageView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray types = attrs == null
+                ? null
+                : context.obtainStyledAttributes(attrs, R.styleable.AsyncImageView, 0, 0);
+
+        mUnavailableDrawable = AutoAnimatorDrawable.wrap(
+                UiUtils.getDrawable(context, types, R.styleable.AsyncImageView_unavailableSrc));
+        mWaitingDrawable = AutoAnimatorDrawable.wrap(
+                UiUtils.getDrawable(context, types, R.styleable.AsyncImageView_waitingSrc));
+
+        if (types != null) types.recycle();
+    }
+
+    /**
+     * Starts loading a {@link Drawable} from {@code factory}.  This will automatically clear out
+     * any outstanding request state and start a new one.
+     *
+     * @param factory The {@link Factory} to use that will provide the {@link Drawable}.
+     */
+    public void setAsyncImageDrawable(Factory factory) {
+        // This will clear out any outstanding request.
+        setImageDrawable(null);
+
+        setForegroundDrawableCompat(mWaitingDrawable);
+        mCancelable = factory.get(this ::setAsyncImageDrawableResponse, getWidth(), getHeight());
+        if (!mWaitingForResponse) mCancelable = null;
+    }
+
+    // RoundedCornerImageView implementation.
+    @Override
+    public void setImageDrawable(Drawable drawable) {
+        // If we had an outstanding async request, cancel it because we're now setting the drawable
+        // to something else.
+        if (mWaitingForResponse) {
+            if (mCancelable != null) mCancelable.run();
+            mCancelable = null;
+            mWaitingForResponse = false;
+            setForegroundDrawableCompat(null);
+        }
+
+        super.setImageDrawable(drawable);
+    }
+
+    private void setAsyncImageDrawableResponse(Drawable drawable) {
+        mCancelable = null;
+        mWaitingForResponse = false;
+        setForegroundDrawableCompat(drawable == null ? mUnavailableDrawable : null);
+        setImageDrawable(drawable);
+    }
+}
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/AutoAnimatorDrawable.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/AutoAnimatorDrawable.java
index 8e6a351..0bb8793 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/AutoAnimatorDrawable.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/AutoAnimatorDrawable.java
@@ -4,7 +4,18 @@
 
 package org.chromium.chrome.browser.download.home.list.view;
 
+import android.annotation.TargetApi;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Animatable2;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.RotateDrawable;
+import android.graphics.drawable.ScaleDrawable;
+import android.os.Build;
+import android.os.Handler;
+import android.support.annotation.Nullable;
+import android.support.graphics.drawable.Animatable2Compat;
 import android.support.v7.graphics.drawable.DrawableWrapper;
 
 /**
@@ -17,8 +28,22 @@
     // animation on the first visibility request.
     private boolean mGotVisibilityCall;
 
-    public AutoAnimatorDrawable(Drawable drawable) {
+    /**
+     * Wraps {@code drawable} and returns a new {@link Drawable} that will automatically start
+     * animating all sub-drawables if possible when the {@link Drawable} is visible.  Stops
+     * animating when the {@link Drawable} is no longer visible.
+     * @param drawable The {@link Drawable} to wrap.
+     * @return         A new {@link Drawable} that will automaticaly animate or {@code null} if
+     *                 {@code drawable} is {@code null}.
+     */
+    public static Drawable wrap(@Nullable Drawable drawable) {
+        if (drawable == null) return null;
+        return new AutoAnimatorDrawable(drawable);
+    }
+
+    private AutoAnimatorDrawable(Drawable drawable) {
         super(drawable);
+        AutoAnimatorDrawable.attachRestartListeners(this);
     }
 
     // DrawableWrapper implementation.
@@ -26,12 +51,115 @@
     public boolean setVisible(boolean visible, boolean restart) {
         boolean changed = super.setVisible(visible, restart);
         if (visible) {
-            if (changed || restart || !mGotVisibilityCall) UiUtils.startAnimatedDrawables(this);
+            if (changed || restart || !mGotVisibilityCall) {
+                AutoAnimatorDrawable.startAnimatedDrawables(this);
+            }
         } else {
-            UiUtils.stopAnimatedDrawables(this);
+            AutoAnimatorDrawable.stopAnimatedDrawables(this);
         }
 
         mGotVisibilityCall = true;
         return changed;
     }
+
+    private static void startAnimatedDrawables(@Nullable Drawable drawable) {
+        AutoAnimatorDrawable.animatedDrawableHelper(drawable, animatable -> animatable.start());
+    }
+
+    private static void stopAnimatedDrawables(@Nullable Drawable drawable) {
+        AutoAnimatorDrawable.animatedDrawableHelper(drawable, animatable -> animatable.stop());
+    }
+
+    @TargetApi(Build.VERSION_CODES.M)
+    private static void attachRestartListeners(@Nullable Drawable drawable) {
+        AutoAnimatorDrawable.animatedDrawableHelper(drawable, animatable -> {
+            if (animatable instanceof Animatable2Compat) {
+                ((Animatable2Compat) animatable)
+                        .registerAnimationCallback(LazyHolderCompat.INSTANCE);
+            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+                    && animatable instanceof Animatable2) {
+                ((Animatable2) animatable).registerAnimationCallback(LazyHolder.INSTANCE);
+            }
+        });
+    }
+
+    @TargetApi(Build.VERSION_CODES.KITKAT)
+    private static void animatedDrawableHelper(
+            @Nullable Drawable drawable, org.chromium.base.Callback<Animatable> consumer) {
+        if (drawable == null) return;
+
+        if (drawable instanceof Animatable) {
+            consumer.onResult((Animatable) drawable);
+
+            // Assume Animatable drawables can handle animating their own internals/sub drawables.
+            return;
+        }
+
+        if (drawable != drawable.getCurrent()) {
+            // Check obvious cases where the current drawable isn't actually being shown.  This
+            // should support all {@link DrawableContainer} instances.
+            AutoAnimatorDrawable.animatedDrawableHelper(drawable.getCurrent(), consumer);
+        }
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+                && drawable instanceof android.graphics.drawable.DrawableWrapper) {
+            // Support all modern versions of drawables that wrap other ones.  This won't cover old
+            // versions of Android (see below for other if/else blocks).
+            AutoAnimatorDrawable.animatedDrawableHelper(
+                    ((android.graphics.drawable.DrawableWrapper) drawable).getDrawable(), consumer);
+        } else if (drawable instanceof DrawableWrapper) {
+            // Support the AppCompat DrawableWrapper.
+            AutoAnimatorDrawable.animatedDrawableHelper(
+                    ((DrawableWrapper) drawable).getWrappedDrawable(), consumer);
+        } else if (drawable instanceof LayerDrawable) {
+            // Support a LayerDrawable and try to animate all layers.
+            LayerDrawable layerDrawable = (LayerDrawable) drawable;
+            for (int i = 0; i < layerDrawable.getNumberOfLayers(); i++) {
+                AutoAnimatorDrawable.animatedDrawableHelper(layerDrawable.getDrawable(i), consumer);
+            }
+        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
+                && drawable instanceof InsetDrawable) {
+            // Support legacy versions of InsetDrawable.
+            AutoAnimatorDrawable.animatedDrawableHelper(
+                    ((InsetDrawable) drawable).getDrawable(), consumer);
+        } else if (drawable instanceof RotateDrawable) {
+            // Support legacy versions of RotateDrawable.
+            AutoAnimatorDrawable.animatedDrawableHelper(
+                    ((RotateDrawable) drawable).getDrawable(), consumer);
+        } else if (drawable instanceof ScaleDrawable) {
+            // Support legacy versions of ScaleDrawable.
+            AutoAnimatorDrawable.animatedDrawableHelper(
+                    ((ScaleDrawable) drawable).getDrawable(), consumer);
+        }
+    }
+
+    private static final class LazyHolder {
+        private static final AutoRestarter INSTANCE = new AutoRestarter();
+    }
+
+    private static final class LazyHolderCompat {
+        private static final AutoRestarterCompat INSTANCE = new AutoRestarterCompat();
+    }
+
+    private static final class AutoRestarterCompat extends Animatable2Compat.AnimationCallback {
+        private final Handler mHandler = new Handler();
+
+        // Animatable2Compat.AnimationCallback implementation.
+        @Override
+        public void onAnimationEnd(Drawable drawable) {
+            if (!(drawable instanceof Animatable)) return;
+            mHandler.post(() -> {
+                if (drawable.isVisible()) ((Animatable) drawable).start();
+            });
+        }
+    }
+
+    @TargetApi(Build.VERSION_CODES.M)
+    private static final class AutoRestarter extends Animatable2.AnimationCallback {
+        // Animatable2.AnimationCallback implementation.
+        @Override
+        public void onAnimationEnd(Drawable drawable) {
+            LazyHolderCompat.INSTANCE.onAnimationEnd(drawable);
+        }
+    }
 }
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/CircularProgressView.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/CircularProgressView.java
index 498f324..cdbd65d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/CircularProgressView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/CircularProgressView.java
@@ -13,6 +13,7 @@
 import android.support.v7.widget.AppCompatImageButton;
 import android.util.AttributeSet;
 import android.view.View;
+import android.widget.ImageView;
 
 import org.chromium.chrome.browser.util.MathUtils;
 import org.chromium.chrome.download.R;
@@ -70,14 +71,15 @@
         super(context, attrs);
 
         mForegroundHelper = new ForegroundDrawableCompat(this);
+        mForegroundHelper.setScaleType(ImageView.ScaleType.FIT_XY);
 
         TypedArray types = attrs == null
                 ? null
                 : context.obtainStyledAttributes(attrs, R.styleable.CircularProgressView, 0, 0);
 
-        mIndeterminateProgress = UiUtils.autoAnimateDrawable(UiUtils.getDrawable(
+        mIndeterminateProgress = AutoAnimatorDrawable.wrap(UiUtils.getDrawable(
                 context, types, R.styleable.CircularProgressView_indeterminateProgress));
-        mDeterminateProgress = UiUtils.autoAnimateDrawable(UiUtils.getDrawable(
+        mDeterminateProgress = AutoAnimatorDrawable.wrap(UiUtils.getDrawable(
                 context, types, R.styleable.CircularProgressView_determinateProgress));
         mResumeButtonSrc =
                 UiUtils.getDrawable(context, types, R.styleable.CircularProgressView_resumeSrc);
@@ -86,7 +88,7 @@
         mRetryButtonSrc =
                 UiUtils.getDrawable(context, types, R.styleable.CircularProgressView_retrySrc);
 
-        types.recycle();
+        if (types != null) types.recycle();
     }
 
     /**
@@ -99,9 +101,7 @@
      */
     public void setProgress(int progress) {
         if (progress == INDETERMINATE) {
-            if (mForegroundHelper.getDrawable() != mIndeterminateProgress) {
-                mForegroundHelper.setDrawable(mIndeterminateProgress);
-            }
+            mForegroundHelper.setDrawable(mIndeterminateProgress);
         } else {
             progress = MathUtils.clamp(progress, 0, 100);
             mDeterminateProgress.setLevel(progress * MAX_LEVEL / 100);
@@ -138,7 +138,7 @@
         setContentDescription(getContext().getText(contentDescription));
     }
 
-    // View implementation.
+    // AppCompatImageButton implementation.
     @Override
     public void draw(Canvas canvas) {
         super.draw(canvas);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/ForegroundDrawableCompat.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/ForegroundDrawableCompat.java
index d88946f..ba6487f3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/ForegroundDrawableCompat.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/ForegroundDrawableCompat.java
@@ -5,6 +5,9 @@
 package org.chromium.chrome.browser.download.home.list.view;
 
 import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Matrix.ScaleToFit;
+import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.support.v4.graphics.drawable.DrawableCompat;
 import android.support.v4.view.ViewCompat;
@@ -13,6 +16,7 @@
 import android.view.View.OnLayoutChangeListener;
 import android.view.ViewGroup;
 import android.view.ViewParent;
+import android.widget.ImageView;
 
 /**
  * A helper class to simulate {@link View#getForeground()} on older versions of Android.  This class
@@ -67,11 +71,19 @@
  */
 public class ForegroundDrawableCompat
         implements OnAttachStateChangeListener, OnLayoutChangeListener {
+    private final RectF mTempSrc = new RectF();
+    private final RectF mTempDst = new RectF();
+    private final Matrix mDrawMatrix = new Matrix();
+
     private final View mView;
 
     private boolean mOnBoundsChanged;
     private Drawable mDrawable;
 
+    // TODO(dtrainor): Add support for more scale types.
+    // Right now the only two supported types are FIT_* tyes.
+    private ImageView.ScaleType mScaleType = ImageView.ScaleType.FIT_CENTER;
+
     /**
      * Builds a {@link ForegroundDrawableCompat} around {@code View}.  This will enable setting a
      * {@link Drawable} to draw on top of {@link View} at draw time.
@@ -123,16 +135,33 @@
         return mDrawable;
     }
 
+    /**
+     * Determines how the foreground {@code Drawable} will be drawn in front of the {@link View}.
+     * @param type The type of scale to apply to the {@Drawable} (see {@link ImageView.ScaleType}).
+     */
+    public void setScaleType(ImageView.ScaleType type) {
+        if (mScaleType == type) return;
+        mScaleType = type;
+        mOnBoundsChanged = true;
+
+        if (mDrawable != null) mView.invalidate();
+    }
+
     /** Meant to be called from {@link View#onDraw(Canvas)}. */
     public void draw(Canvas canvas) {
         if (mDrawable == null) return;
 
-        if (mOnBoundsChanged) {
-            mOnBoundsChanged = false;
-            mDrawable.setBounds(0, 0, mView.getWidth(), mView.getHeight());
-        }
+        computeBounds();
 
-        mDrawable.draw(canvas);
+        if (mDrawMatrix.isIdentity()) {
+            mDrawable.draw(canvas);
+        } else {
+            final int saveCount = canvas.getSaveCount();
+            canvas.save();
+            canvas.concat(mDrawMatrix);
+            mDrawable.draw(canvas);
+            canvas.restoreToCount(saveCount);
+        }
     }
 
     /** Meant to be called from {@link View#onVisibilityChanged(View,visibility)}. */
@@ -162,6 +191,7 @@
     // OnAttachStateChangeListener implementation.
     @Override
     public void onViewAttachedToWindow(View v) {
+        if (mDrawable == null) return;
         if (mView.isShown() && mView.getWindowVisibility() != View.GONE) {
             mDrawable.setVisible(mView.getVisibility() == View.VISIBLE, false);
         }
@@ -169,6 +199,7 @@
 
     @Override
     public void onViewDetachedFromWindow(View v) {
+        if (mDrawable == null) return;
         if (mView.isShown() && mView.getWindowVisibility() != View.GONE) {
             mDrawable.setVisible(false, false);
         }
@@ -187,4 +218,34 @@
             mOnBoundsChanged = true;
         }
     }
+
+    private void computeBounds() {
+        if (mDrawable == null || !mOnBoundsChanged) return;
+
+        mDrawMatrix.reset();
+
+        int drawableWidth = mDrawable.getIntrinsicWidth();
+        int drawableHeight = mDrawable.getIntrinsicHeight();
+
+        int viewWidth = mView.getWidth();
+        int viewHeight = mView.getHeight();
+
+        mTempSrc.set(0, 0, drawableWidth, drawableHeight);
+        mTempDst.set(0, 0, viewWidth, viewHeight);
+
+        if (mScaleType == ImageView.ScaleType.FIT_START) {
+            mDrawMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.START);
+            mDrawable.setBounds(0, 0, drawableWidth, drawableHeight);
+        } else if (mScaleType == ImageView.ScaleType.FIT_CENTER) {
+            mDrawMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.CENTER);
+            mDrawable.setBounds(0, 0, drawableWidth, drawableHeight);
+        } else if (mScaleType == ImageView.ScaleType.FIT_END) {
+            mDrawMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.END);
+            mDrawable.setBounds(0, 0, drawableWidth, drawableHeight);
+        } else {
+            mDrawable.setBounds(0, 0, viewWidth, viewHeight);
+        }
+
+        mOnBoundsChanged = false;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/ForegroundRoundedCornerImageView.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/ForegroundRoundedCornerImageView.java
new file mode 100644
index 0000000..13b09d6
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/ForegroundRoundedCornerImageView.java
@@ -0,0 +1,76 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.download.home.list.view;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+
+import org.chromium.chrome.download.R;
+import org.chromium.ui.widget.RoundedCornerImageView;
+
+/** Helper class that adds foreground drawable support to {@code RoundedCornerImageView}. */
+public class ForegroundRoundedCornerImageView extends RoundedCornerImageView {
+    private final ForegroundDrawableCompat mForegroundHelper;
+
+    /** Creates an {@link ForegroundRoundedCornerImageView instance. */
+    public ForegroundRoundedCornerImageView(Context context) {
+        this(context, null, 0);
+    }
+
+    /** Creates an {@link ForegroundRoundedCornerImageView instance. */
+    public ForegroundRoundedCornerImageView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    /** Creates an {@link ForegroundRoundedCornerImageView instance. */
+    public ForegroundRoundedCornerImageView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        mForegroundHelper = new ForegroundDrawableCompat(this);
+
+        TypedArray types = attrs == null
+                ? null
+                : context.obtainStyledAttributes(
+                          attrs, R.styleable.ForegroundRoundedCornerImageView, 0, 0);
+
+        mForegroundHelper.setDrawable(AutoAnimatorDrawable.wrap(UiUtils.getDrawable(
+                context, types, R.styleable.ForegroundRoundedCornerImageView_foregroundCompat)));
+
+        if (types != null) types.recycle();
+    }
+
+    /** Sets the foreground drawable of this {@link View} to {@code drawable}. */
+    public void setForegroundDrawableCompat(Drawable drawable) {
+        mForegroundHelper.setDrawable(drawable);
+    }
+
+    // RoundedCornerImageView implementation.
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+        mForegroundHelper.draw(canvas);
+    }
+
+    @Override
+    protected void onVisibilityChanged(View changedView, int visibility) {
+        super.onVisibilityChanged(changedView, visibility);
+        mForegroundHelper.onVisibilityChanged(changedView, visibility);
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        mForegroundHelper.drawableStateChanged();
+    }
+
+    @Override
+    protected boolean verifyDrawable(Drawable dr) {
+        return super.verifyDrawable(dr) || mForegroundHelper.verifyDrawable(dr);
+    }
+}
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/SquareAsyncImageView.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/SquareAsyncImageView.java
new file mode 100644
index 0000000..49595165
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/SquareAsyncImageView.java
@@ -0,0 +1,51 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.download.home.list.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+/**
+ * Helper {@link AsyncImageView} that will force the dimensions of the view to be equal.
+ */
+public class SquareAsyncImageView extends AsyncImageView {
+    /** Creates an instance of {@link SquareAsyncImageView}. */
+    public SquareAsyncImageView(Context context) {
+        this(context, null, 0);
+    }
+
+    /** Creates an instance of {@link SquareAsyncImageView}. */
+    public SquareAsyncImageView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    /** Creates an instance of {@link SquareAsyncImageView}. */
+    public SquareAsyncImageView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    // AsyncImageView implementation.
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
+        int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
+
+        int squareSize;
+
+        // Check if there is a valid explicitly set dimension first (prioritize width).
+        if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY && measureWidth > 0) {
+            squareSize = measureWidth;
+        } else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY
+                && measureHeight > 0) {
+            squareSize = measureHeight;
+        } else {
+            squareSize = Math.min(measureWidth, measureHeight);
+        }
+
+        int squareMeasureSpec = MeasureSpec.makeMeasureSpec(squareSize, MeasureSpec.EXACTLY);
+
+        super.onMeasure(squareMeasureSpec, squareMeasureSpec);
+    }
+}
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/UiUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/UiUtils.java
index 992c0be2..2d35f48 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/UiUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/view/UiUtils.java
@@ -4,24 +4,14 @@
 
 package org.chromium.chrome.browser.download.home.list.view;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.TypedArray;
-import android.graphics.drawable.Animatable;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.DrawableWrapper;
-import android.graphics.drawable.InsetDrawable;
-import android.graphics.drawable.LayerDrawable;
-import android.graphics.drawable.RotateDrawable;
-import android.graphics.drawable.ScaleDrawable;
-import android.os.Build;
 import android.support.annotation.DrawableRes;
 import android.support.annotation.Nullable;
 import android.support.annotation.StyleableRes;
 import android.support.v7.content.res.AppCompatResources;
 
-import org.chromium.base.Callback;
-
 /** A set of helper methods to make interacting with the Android UI easier. */
 public final class UiUtils {
     private UiUtils() {}
@@ -49,68 +39,4 @@
     public static Drawable getDrawable(Context context, @DrawableRes int resId) {
         return AppCompatResources.getDrawable(context, resId);
     }
-
-    /**
-     * Wraps {@code drawable} in a {@link AutoAnimatorDrawable}.
-     * @return A wrapped {@code Drawable} or {@code null} if {@code drawable} is null.
-     */
-    public static @Nullable Drawable autoAnimateDrawable(@Nullable Drawable drawable) {
-        return drawable == null ? null : new AutoAnimatorDrawable(drawable);
-    }
-
-    /**
-     * Recursively searches {@code drawable} for all {@link Animatable} instances and starts them.
-     * @param drawable The {@link Drawable} to start animating.
-     */
-    public static void startAnimatedDrawables(@Nullable Drawable drawable) {
-        animatedDrawableHelper(drawable, animatable -> animatable.start());
-    }
-
-    /**
-     * Recursively searches {@code drawable} for all {@link Animatable} instances and stops them.
-     * @param drawable The {@link Drawable} to stop animating.
-     */
-    public static void stopAnimatedDrawables(@Nullable Drawable drawable) {
-        animatedDrawableHelper(drawable, animatable -> animatable.stop());
-    }
-
-    @TargetApi(Build.VERSION_CODES.KITKAT)
-    private static void animatedDrawableHelper(
-            @Nullable Drawable drawable, Callback<Animatable> consumer) {
-        if (drawable == null) return;
-
-        if (drawable instanceof Animatable) {
-            consumer.onResult((Animatable) drawable);
-
-            // Assume Animatable drawables can handle animating their own internals/sub drawables.
-            return;
-        }
-
-        if (drawable != drawable.getCurrent()) {
-            // Check obvious cases where the current drawable isn't actually being shown.  This
-            // should support all {@link DrawableContainer} instances.
-            UiUtils.animatedDrawableHelper(drawable.getCurrent(), consumer);
-        }
-
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && drawable instanceof DrawableWrapper) {
-            // Support all modern versions of drawables that wrap other ones.  This won't cover old
-            // versions of Android (see below for other if/else blocks).
-            animatedDrawableHelper(((DrawableWrapper) drawable).getDrawable(), consumer);
-        } else if (drawable instanceof LayerDrawable) {
-            LayerDrawable layerDrawable = (LayerDrawable) drawable;
-            for (int i = 0; i < layerDrawable.getNumberOfLayers(); i++) {
-                animatedDrawableHelper(layerDrawable.getDrawable(i), consumer);
-            }
-        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
-                && drawable instanceof InsetDrawable) {
-            // Support legacy versions of InsetDrawable.
-            animatedDrawableHelper(((InsetDrawable) drawable).getDrawable(), consumer);
-        } else if (drawable instanceof RotateDrawable) {
-            // Support legacy versions of RotateDrawable.
-            animatedDrawableHelper(((RotateDrawable) drawable).getDrawable(), consumer);
-        } else if (drawable instanceof ScaleDrawable) {
-            // Support legacy versions of ScaleDrawable.
-            animatedDrawableHelper(((ScaleDrawable) drawable).getDrawable(), consumer);
-        }
-    }
 }
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/view/LoadingBackground.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/view/LoadingBackground.java
deleted file mode 100644
index 69ecf82..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/view/LoadingBackground.java
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.download.home.view;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.support.graphics.drawable.Animatable2Compat;
-import android.support.graphics.drawable.AnimatedVectorDrawableCompat;
-import android.widget.ImageView;
-
-import org.chromium.chrome.download.R;
-
-/**
- * A helper class to display the loading image and animation for image and video items. This class
- * can also be extended and/or modified to include download progress.
- */
-public class LoadingBackground {
-    private AnimatedVectorDrawableCompat mLoadingDrawable;
-
-    public LoadingBackground(Context context) {
-        mLoadingDrawable =
-                AnimatedVectorDrawableCompat.create(context, R.drawable.image_loading_progress);
-        Animatable2Compat.AnimationCallback animationCallback =
-                new Animatable2Compat.AnimationCallback() {
-                    @Override
-                    public void onAnimationEnd(Drawable drawable) {
-                        mLoadingDrawable.start();
-                    }
-                };
-
-        mLoadingDrawable.registerAnimationCallback(animationCallback);
-    }
-
-    /** Show loading animation for the given {@link view}. */
-    public void show(ImageView view) {
-        view.setImageDrawable(mLoadingDrawable);
-        mLoadingDrawable.start();
-    }
-
-    /** Hide the loading animation. */
-    public void hide() {
-        mLoadingDrawable.clearAnimationCallbacks();
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/page_info/PageInfoController.java b/chrome/android/java/src/org/chromium/chrome/browser/page_info/PageInfoController.java
index 7abdb3bb..1ca73d2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/page_info/PageInfoController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/page_info/PageInfoController.java
@@ -159,14 +159,13 @@
      * @param offlinePageUrl           URL that the offline page claims to be generated from.
      * @param offlinePageCreationDate  Date when the offline page was created.
      * @param offlinePageState         State of the tab showing offline page.
-     * @param previewOriginalHost      The domain of the original page of the displayed preview.
      * @param previewPageState         State of the tab showing the preview.
      * @param publisher                The name of the content publisher, if any.
      */
     protected PageInfoController(Activity activity, Tab tab, int securityLevel,
             String offlinePageUrl, String offlinePageCreationDate,
-            @OfflinePageState int offlinePageState, String previewOriginalHost,
-            @PreviewPageState int previewPageState, String publisher) {
+            @OfflinePageState int offlinePageState, @PreviewPageState int previewPageState,
+            String publisher) {
         mContext = activity;
         mTab = tab;
         mSecurityLevel = securityLevel;
@@ -249,7 +248,7 @@
             viewParams.siteSettingsButtonShown = false;
         }
 
-        initPreviewUiParams(viewParams, previewOriginalHost);
+        initPreviewUiParams(viewParams);
 
         if (isShowingOfflinePage()) {
             boolean isConnected = OfflinePageUtils.isConnected();
@@ -332,9 +331,9 @@
      * Initializes the state in viewParams with respect to showing the previews UI.
      *
      * @param viewParams The PageInfoViewParams to set state on.
-     * @param previewOriginalHost The hostname of the displayed preview page.
      */
-    private void initPreviewUiParams(PageInfoViewParams viewParams, String previewOriginalHost) {
+    private void initPreviewUiParams(PageInfoViewParams viewParams) {
+        final PreviewsAndroidBridge bridge = PreviewsAndroidBridge.getInstance();
         viewParams.separatorShown = mPreviewPageState == PreviewPageState.INSECURE_PAGE_PREVIEW;
         viewParams.previewUIShown = isShowingPreview();
         if (isShowingPreview()) {
@@ -342,10 +341,9 @@
             viewParams.connectionMessageShown = false;
 
             viewParams.previewShowOriginalClickCallback = () -> {
-                runAfterDismiss(() -> {
-                    PreviewsAndroidBridge.getInstance().loadOriginal(mTab.getWebContents());
-                });
+                runAfterDismiss(() -> { bridge.loadOriginal(mTab.getWebContents()); });
             };
+            final String previewOriginalHost = bridge.getOriginalHost(mTab.getWebContents());
             final String loadOriginalText = mContext.getString(
                     R.string.page_info_preview_load_original, previewOriginalHost);
             final SpannableString loadOriginalSpan = SpanApplier.applySpans(loadOriginalText,
@@ -355,6 +353,9 @@
                             // because the entire TextView will be clickable.
                             new NoUnderlineClickableSpan((view) -> {})));
             viewParams.previewLoadOriginalMessage = loadOriginalSpan;
+
+            viewParams.previewStaleTimestamp =
+                    bridge.getStalePreviewTimestamp(mTab.getWebContents());
         }
     }
 
@@ -568,15 +569,12 @@
         final int securityLevel =
                 SecurityStateModel.getSecurityLevelForWebContents(tab.getWebContents());
 
-        final PreviewsAndroidBridge previewsBridge = PreviewsAndroidBridge.getInstance();
         @PreviewPageState
         int previewPageState = PreviewPageState.NOT_PREVIEW;
-        String previewOriginalHost = null;
-        if (previewsBridge.shouldShowPreviewUI(tab.getWebContents())) {
+        if (PreviewsAndroidBridge.getInstance().shouldShowPreviewUI(tab.getWebContents())) {
             previewPageState = securityLevel == ConnectionSecurityLevel.SECURE
                     ? PreviewPageState.SECURE_PAGE_PREVIEW
                     : PreviewPageState.INSECURE_PAGE_PREVIEW;
-            previewOriginalHost = previewsBridge.getOriginalHost(tab.getWebContents());
 
             Tracker tracker = TrackerFactory.getTrackerForProfile(Profile.getLastUsedProfile());
             tracker.notifyEvent(EventConstants.PREVIEWS_VERBOSE_STATUS_OPENED);
@@ -607,8 +605,7 @@
         }
 
         new PageInfoController(activity, tab, securityLevel, offlinePageUrl,
-                offlinePageCreationDate, offlinePageState, previewOriginalHost, previewPageState,
-                contentPublisher);
+                offlinePageCreationDate, offlinePageState, previewPageState, contentPublisher);
     }
 
     private static native long nativeInit(PageInfoController controller, WebContents webContents);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/page_info/PageInfoView.java b/chrome/android/java/src/org/chromium/chrome/browser/page_info/PageInfoView.java
index dad888eb..2f681af 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/page_info/PageInfoView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/page_info/PageInfoView.java
@@ -13,6 +13,7 @@
 import android.support.annotation.StringRes;
 import android.support.v7.widget.AppCompatTextView;
 import android.text.Layout;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -170,6 +171,7 @@
 
         public CharSequence url;
         public CharSequence previewLoadOriginalMessage;
+        public CharSequence previewStaleTimestamp;
         public int urlOriginLength;
     }
 
@@ -187,7 +189,6 @@
     public static class ConnectionInfoParams {
         public CharSequence message;
         public CharSequence summary;
-        public CharSequence previewLoadOriginalMessage;
         public Runnable clickCallback;
     }
 
@@ -199,6 +200,7 @@
     private final TextView mConnectionSummary;
     private final TextView mConnectionMessage;
     private final TextView mPreviewMessage;
+    private final TextView mPreviewStaleTimestamp;
     private final TextView mPreviewLoadOriginal;
     private final LinearLayout mPermissionsList;
     private final View mSeparator;
@@ -216,6 +218,7 @@
         mConnectionSummary = (TextView) findViewById(R.id.page_info_connection_summary);
         mConnectionMessage = (TextView) findViewById(R.id.page_info_connection_message);
         mPreviewMessage = (TextView) findViewById(R.id.page_info_preview_message);
+        mPreviewStaleTimestamp = (TextView) findViewById(R.id.page_info_stale_preview_timestamp);
         mPreviewLoadOriginal = (TextView) findViewById(R.id.page_info_preview_load_original);
         mPermissionsList = (LinearLayout) findViewById(R.id.page_info_permissions_list);
         mSeparator = (View) findViewById(R.id.page_info_separator);
@@ -247,8 +250,14 @@
         initializePageInfoViewChild(mPreviewMessage, params.previewUIShown, 0f, null);
         initializePageInfoViewChild(mPreviewLoadOriginal, params.previewUIShown, 0f,
                 params.previewShowOriginalClickCallback);
+        initializePageInfoViewChild(mPreviewStaleTimestamp,
+                params.previewUIShown && !TextUtils.isEmpty(params.previewStaleTimestamp), 0f,
+                null);
         initializePageInfoViewChild(mSeparator, params.separatorShown, 0f, null);
         mPreviewLoadOriginal.setText(params.previewLoadOriginalMessage);
+        if (!TextUtils.isEmpty(params.previewStaleTimestamp)) {
+            mPreviewStaleTimestamp.setText(params.previewStaleTimestamp);
+        }
     }
 
     public void setPermissions(List<PermissionParams> permissionParamsList) {
@@ -365,6 +374,7 @@
         animatableViews.add(mConnectionSummary);
         animatableViews.add(mConnectionMessage);
         animatableViews.add(mPreviewMessage);
+        animatableViews.add(mPreviewStaleTimestamp);
         animatableViews.add(mPreviewLoadOriginal);
         animatableViews.add(mSeparator);
         animatableViews.add(mInstantAppButton);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/previews/PreviewsAndroidBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/previews/PreviewsAndroidBridge.java
index 3dd9627..3340cb8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/previews/PreviewsAndroidBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/previews/PreviewsAndroidBridge.java
@@ -29,11 +29,27 @@
         return nativeShouldShowPreviewUI(mNativePreviewsAndroidBridge, webContents);
     }
 
+    /**
+     * Returns the origin host name of the preview being shown.
+     */
     public String getOriginalHost(WebContents webContents) {
         assert shouldShowPreviewUI(webContents) : "getOriginalHost called on a non-preview page";
         return nativeGetOriginalHost(mNativePreviewsAndroidBridge, webContents);
     }
 
+    /**
+     * If the current preview is a stale preview, this returns the timestamp text to display to the
+     * user. An empty string is returned if the current preview is not a stale preview.
+     */
+    public String getStalePreviewTimestamp(WebContents webContents) {
+        assert shouldShowPreviewUI(webContents)
+            : "getStalePreviewTimestamp called on a non-preview page";
+        return nativeGetStalePreviewTimestamp(mNativePreviewsAndroidBridge, webContents);
+    }
+
+    /**
+     * Requests that the original page be loaded.
+     */
     public void loadOriginal(WebContents webContents) {
         assert shouldShowPreviewUI(webContents) : "loadOriginal called on a non-preview page";
         nativeLoadOriginal(mNativePreviewsAndroidBridge, webContents);
@@ -44,6 +60,8 @@
             long nativePreviewsAndroidBridge, WebContents webContents);
     private native String nativeGetOriginalHost(
             long nativePreviewsAndroidBridge, WebContents webContents);
+    private native String nativeGetStalePreviewTimestamp(
+            long nativePreviewsAndroidBridge, WebContents webContents);
     private native void nativeLoadOriginal(
             long nativePreviewsAndroidBridge, WebContents webContents);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/ScrimView.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/ScrimView.java
index c20a9be9..824824c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/ScrimView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/ScrimView.java
@@ -14,6 +14,7 @@
 import android.view.ViewGroup.MarginLayoutParams;
 
 import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.util.MathUtils;
 import org.chromium.chrome.browser.widget.animation.CancelAwareAnimatorListener;
@@ -134,6 +135,9 @@
     /** The current set of params affecting the scrim. */
     private ScrimParams mActiveParams;
 
+    /** The duration for the fading animation. This can be overridden for testing. */
+    private int mFadeDurationMs;
+
     /**
      * @param context An Android {@link Context} for creating the view.
      * @param scrimDelegate A means of changing the scrim over the status bar.
@@ -146,6 +150,7 @@
         mParent = parent;
         mDefaultBackgroundColor = ApiCompatibilityUtils.getColor(
                 getResources(), R.color.omnibox_focused_fading_background_color);
+        mFadeDurationMs = FADE_DURATION_MS;
 
         setAlpha(0.0f);
         setVisibility(View.GONE);
@@ -245,7 +250,7 @@
         }
         if (mOverlayFadeInAnimator == null) {
             mOverlayFadeInAnimator = ObjectAnimator.ofFloat(this, ALPHA, 1f);
-            mOverlayFadeInAnimator.setDuration(FADE_DURATION_MS);
+            mOverlayFadeInAnimator.setDuration(mFadeDurationMs);
             mOverlayFadeInAnimator.setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE);
         }
 
@@ -258,7 +263,7 @@
     public void hideScrim(boolean fadeOut) {
         if (mOverlayFadeOutAnimator == null) {
             mOverlayFadeOutAnimator = ObjectAnimator.ofFloat(this, ALPHA, 0f);
-            mOverlayFadeOutAnimator.setDuration(FADE_DURATION_MS);
+            mOverlayFadeOutAnimator.setDuration(mFadeDurationMs);
             mOverlayFadeOutAnimator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE);
             mOverlayFadeOutAnimator.addListener(new CancelAwareAnimatorListener() {
                 @Override
@@ -296,4 +301,9 @@
         if (mActiveParams == null || mActiveParams.observer == null) return;
         mActiveParams.observer.onScrimClick();
     }
+
+    @VisibleForTesting
+    public void disableAnimationForTesting(boolean disable) {
+        mFadeDurationMs = disable ? 0 : FADE_DURATION_MS;
+    }
 }
diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd
index 1d8db719..c266b7a 100644
--- a/chrome/android/java/strings/android_chrome_strings.grd
+++ b/chrome/android/java/strings/android_chrome_strings.grd
@@ -1319,7 +1319,7 @@
         Save up to 60% of your data
       </message>
       <message name="IDS_DATA_REDUCTION_PROMO_INFOBAR_TEXT" desc="Text to be displayed in the data reduction promo infobar">
-        Google servers will optimize the pages you visit, except for HTTPS and Incognito.
+        Google servers will optimize the pages you visit.
       </message>
       <message name="IDS_DATA_REDUCTION_PROMO_INFOBAR_BUTTON" desc="Text to be displayed in the data reduction promo infobar confirm button">
         Turn on Data Saver
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 1380b76..3caaf8b 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -523,9 +523,12 @@
   "java/src/org/chromium/chrome/browser/download/home/list/holder/SeparatorViewHolder.java",
   "java/src/org/chromium/chrome/browser/download/home/list/holder/ThumbnailAwareViewHolder.java",
   "java/src/org/chromium/chrome/browser/download/home/list/holder/VideoViewHolder.java",
+  "java/src/org/chromium/chrome/browser/download/home/list/view/AsyncImageView.java",
   "java/src/org/chromium/chrome/browser/download/home/list/view/AutoAnimatorDrawable.java",
   "java/src/org/chromium/chrome/browser/download/home/list/view/CircularProgressView.java",
   "java/src/org/chromium/chrome/browser/download/home/list/view/ForegroundDrawableCompat.java",
+  "java/src/org/chromium/chrome/browser/download/home/list/view/ForegroundRoundedCornerImageView.java",
+  "java/src/org/chromium/chrome/browser/download/home/list/view/SquareAsyncImageView.java",
   "java/src/org/chromium/chrome/browser/download/home/list/view/UiUtils.java",
   "java/src/org/chromium/chrome/browser/download/home/list/ListProperties.java",
   "java/src/org/chromium/chrome/browser/download/home/list/ListPropertyViewBinder.java",
@@ -538,7 +541,6 @@
   "java/src/org/chromium/chrome/browser/download/home/storage/StorageSummaryProvider.java",
   "java/src/org/chromium/chrome/browser/download/home/toolbar/DownloadHomeToolbar.java",
   "java/src/org/chromium/chrome/browser/download/home/toolbar/ToolbarCoordinator.java",
-  "java/src/org/chromium/chrome/browser/download/home/view/LoadingBackground.java",
   "java/src/org/chromium/chrome/browser/download/home/view/SelectionView.java",
   "java/src/org/chromium/chrome/browser/download/items/DownloadBlockedOfflineContentProvider.java",
   "java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorFactory.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/NavigationPopupTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/NavigationPopupTest.java
index b4c53ea..3ff4313 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/NavigationPopupTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/NavigationPopupTest.java
@@ -7,6 +7,7 @@
 import android.graphics.Bitmap;
 import android.support.test.filters.MediumTest;
 import android.support.test.filters.SmallTest;
+import android.view.KeyEvent;
 import android.view.View;
 import android.widget.ListView;
 import android.widget.TextView;
@@ -20,18 +21,21 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
+import org.chromium.base.test.util.Restriction;
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.base.test.util.UrlUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.NavigationController;
 import org.chromium.content_public.browser.NavigationEntry;
 import org.chromium.content_public.browser.NavigationHistory;
 import org.chromium.content_public.browser.test.util.Criteria;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
+import org.chromium.ui.test.util.UiRestriction;
 
 import java.util.concurrent.ExecutionException;
 
@@ -43,8 +47,8 @@
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class NavigationPopupTest {
     @Rule
-    public ChromeActivityTestRule<ChromeActivity> mActivityTestRule =
-            new ChromeActivityTestRule<>(ChromeActivity.class);
+    public ChromeActivityTestRule<ChromeTabbedActivity> mActivityTestRule =
+            new ChromeActivityTestRule<>(ChromeTabbedActivity.class);
 
     private static final int INVALID_NAVIGATION_INDEX = -1;
 
@@ -286,6 +290,47 @@
         });
     }
 
+    @Test
+    @MediumTest
+    @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
+    @EnableFeatures(ChromeFeatureList.LONG_PRESS_BACK_FOR_HISTORY)
+    @Feature({"Navigation"})
+    public void testLongPressBackTriggering() throws ExecutionException {
+        KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> { mActivityTestRule.getActivity().onKeyDown(KeyEvent.KEYCODE_BACK, event); });
+        CriteriaHelper.pollUiThread(
+                () -> mActivityTestRule.getActivity().hasPendingNavigationPopupForTesting());
+
+        // Wait for the long press timeout to trigger and show the navigation popup.
+        CriteriaHelper.pollUiThread(
+                () -> mActivityTestRule.getActivity().getNavigationPopupForTesting() != null);
+    }
+
+    @Test
+    @SmallTest
+    @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
+    @EnableFeatures(ChromeFeatureList.LONG_PRESS_BACK_FOR_HISTORY)
+    @Feature({"Navigation"})
+    public void testLongPressBackTriggering_Cancellation() throws ExecutionException {
+        ThreadUtils.runOnUiThreadBlocking(() -> {
+            KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
+            mActivityTestRule.getActivity().onKeyDown(KeyEvent.KEYCODE_BACK, event);
+        });
+        CriteriaHelper.pollUiThread(
+                () -> mActivityTestRule.getActivity().hasPendingNavigationPopupForTesting());
+        ThreadUtils.runOnUiThreadBlocking(() -> {
+            KeyEvent event = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
+            mActivityTestRule.getActivity().onKeyUp(KeyEvent.KEYCODE_BACK, event);
+        });
+        CriteriaHelper.pollUiThread(
+                () -> !mActivityTestRule.getActivity().hasPendingNavigationPopupForTesting());
+
+        // Ensure no navigation popup is showing.
+        Assert.assertNull(ThreadUtils.runOnUiThreadBlocking(
+                () -> mActivityTestRule.getActivity().getNavigationPopupForTesting()));
+    }
+
     private NavigationPopup showPopup(NavigationController controller) throws ExecutionException {
         return ThreadUtils.runOnUiThreadBlocking(() -> {
             NavigationPopup popup = new NavigationPopup(mProfile, mActivityTestRule.getActivity(),
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java
index fa1d835..366fa11 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java
@@ -61,6 +61,7 @@
 import org.chromium.chrome.browser.suggestions.SiteSuggestion;
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.widget.ScrimView;
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
@@ -207,8 +208,12 @@
     @EnableFeatures({ChromeFeatureList.CONTENT_SUGGESTIONS_SCROLL_TO_LOAD})
     @ParameterAnnotations.UseMethodParameter(InterestFeedParams.class)
     public void testRender_FocusFakeBox(boolean interestFeedEnabled) throws Exception {
+        ScrimView scrimView = mActivityTestRule.getActivity().getScrim();
+        scrimView.disableAnimationForTesting(true);
         onView(withId(R.id.search_box)).perform(click());
+        RenderTestRule.sanitize(mNtp.getView().getRootView());
         mRenderTestRule.render(mNtp.getView().getRootView(), "focus_fake_box");
+        scrimView.disableAnimationForTesting(false);
     }
 
     @Test
@@ -225,10 +230,15 @@
     @Test
     @SmallTest
     @Feature({"NewTabPage", "FeedNewTabPage", "RenderTest"})
+    @EnableFeatures({ChromeFeatureList.CONTENT_SUGGESTIONS_SCROLL_TO_LOAD})
     @ParameterAnnotations.UseMethodParameter(InterestFeedParams.class)
     public void testRender_ArticleSectionHeader(boolean interestFeedEnabled) throws Exception {
         // Scroll to the article section header in case it is not visible.
         onView(instanceOf(RecyclerView.class)).perform(RecyclerViewActions.scrollToPosition(2));
+        if (!interestFeedEnabled) {
+            RecyclerViewTestUtils.waitForStableRecyclerView(
+                    mNtp.getNewTabPageView().getRecyclerView());
+        }
         View view = mNtp.getSectionHeaderViewForTesting();
 
         // Check header is expanded.
@@ -285,7 +295,6 @@
      */
     @Test
     @SmallTest
-    @DisabledTest(message = "https://crbug.com/888129")
     @Feature({"NewTabPage", "FeedNewTabPage"})
     @ParameterAnnotations.UseMethodParameter(InterestFeedParams.class)
     public void testFocusFakebox(boolean interestFeedEnabled) {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoControllerTest.java
index 570c9f66..2d9d6db 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoControllerTest.java
@@ -80,7 +80,7 @@
         ThreadUtils.runOnUiThreadBlocking(() -> {
             PageInfoController pageInfo = new PageInfoController(mActivityTestRule.getActivity(),
                     mActivityTestRule.getActivity().getActivityTab(), ConnectionSecurityLevel.NONE,
-                    null, null, PageInfoController.OfflinePageState.NOT_OFFLINE_PAGE, null,
+                    null, null, PageInfoController.OfflinePageState.NOT_OFFLINE_PAGE,
                     PageInfoController.PreviewPageState.NOT_PREVIEW, null);
             PageInfoView pageInfoView = pageInfo.getPageInfoViewForTesting();
             // Test that the title contains the Unicode hostname rather than strict equality, as
diff --git a/chrome/android/webapk/shell_apk/shell_apk_version.gni b/chrome/android/webapk/shell_apk/shell_apk_version.gni
index 3772b22..728bdd7d 100644
--- a/chrome/android/webapk/shell_apk/shell_apk_version.gni
+++ b/chrome/android/webapk/shell_apk/shell_apk_version.gni
@@ -6,7 +6,7 @@
 # (including AndroidManifest.xml) is updated. This version should be incremented
 # prior to uploading a new ShellAPK to the WebAPK Minting Server.
 # Does not affect Chrome.apk
-template_shell_apk_version = 55
+template_shell_apk_version = 57
 
 # The ShellAPK version expected by Chrome. Chrome will try to update the WebAPK
 # if the WebAPK's ShellAPK version is less than |expected_shell_apk_version|.
diff --git a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/HostBrowserLauncher.java b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/HostBrowserLauncher.java
index e7ac5a7a..aba712f6 100644
--- a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/HostBrowserLauncher.java
+++ b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/HostBrowserLauncher.java
@@ -33,30 +33,31 @@
      * Otherwise, launches the host browser in tabbed mode.
      */
     public static void launch(Context context, HostBrowserLauncherParams params) {
-        Log.v(TAG, "WebAPK Launch URL: " + params.startUrl());
+        Log.v(TAG, "WebAPK Launch URL: " + params.getStartUrl());
 
-        if (HostBrowserUtils.shouldLaunchInTab(params.hostBrowserMajorChromiumVersion())) {
+        if (HostBrowserUtils.shouldLaunchInTab(params.getHostBrowserMajorChromiumVersion())) {
             launchInTab(context, params);
             return;
         }
 
         Intent intent = new Intent();
         intent.setAction(ACTION_START_WEBAPK);
-        intent.setPackage(params.hostBrowserPackageName());
-        Bundle copiedExtras = params.originalIntent().getExtras();
+        intent.setPackage(params.getHostBrowserPackageName());
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        Bundle copiedExtras = params.getOriginalIntent().getExtras();
         if (copiedExtras != null) {
             intent.putExtras(copiedExtras);
         }
 
-        intent.putExtra(WebApkConstants.EXTRA_URL, params.startUrl())
-                .putExtra(WebApkConstants.EXTRA_SOURCE, params.source())
+        intent.putExtra(WebApkConstants.EXTRA_URL, params.getStartUrl())
+                .putExtra(WebApkConstants.EXTRA_SOURCE, params.getSource())
                 .putExtra(WebApkConstants.EXTRA_WEBAPK_PACKAGE_NAME, context.getPackageName())
-                .putExtra(WebApkConstants.EXTRA_FORCE_NAVIGATION, params.forceNavigation());
+                .putExtra(WebApkConstants.EXTRA_FORCE_NAVIGATION, params.getForceNavigation());
 
         // Only pass on the start time if no user action was required between launching the webapk
         // and chrome starting up. See https://crbug.com/842023
-        if (!params.dialogShown()) {
-            intent.putExtra(WebApkConstants.EXTRA_WEBAPK_LAUNCH_TIME, params.launchTimeMs());
+        if (!params.wasDialogShown()) {
+            intent.putExtra(WebApkConstants.EXTRA_WEBAPK_LAUNCH_TIME, params.getLaunchTimeMs());
         }
 
         try {
@@ -69,10 +70,11 @@
 
     /** Launches a WebAPK in its runtime host browser as a tab. */
     private static void launchInTab(Context context, HostBrowserLauncherParams params) {
-        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(params.startUrl()));
-        intent.setPackage(params.hostBrowserPackageName());
+        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(params.getStartUrl()));
+        intent.setPackage(params.getHostBrowserPackageName());
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         intent.putExtra(REUSE_URL_MATCHING_TAB_ELSE_NEW_TAB, true)
-                .putExtra(WebApkConstants.EXTRA_SOURCE, params.source());
+                .putExtra(WebApkConstants.EXTRA_SOURCE, params.getSource());
         try {
             context.startActivity(intent);
         } catch (ActivityNotFoundException e) {
diff --git a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/HostBrowserLauncherActivity.java b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/HostBrowserLauncherActivity.java
index dcfc2f3..bbc75b4 100644
--- a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/HostBrowserLauncherActivity.java
+++ b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/HostBrowserLauncherActivity.java
@@ -38,6 +38,6 @@
                         hostBrowserPackageName, dialogShown, mActivityStartTimeMs);
         if (params == null) return;
 
-        HostBrowserLauncher.launch(this, params);
+        HostBrowserLauncher.launch(getApplicationContext(), params);
     }
 }
diff --git a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/HostBrowserLauncherParams.java b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/HostBrowserLauncherParams.java
index ed04d4d..fdf204b 100644
--- a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/HostBrowserLauncherParams.java
+++ b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/HostBrowserLauncherParams.java
@@ -148,7 +148,7 @@
     }
 
     /** Returns the chosen host browser. */
-    public String hostBrowserPackageName() {
+    public String getHostBrowserPackageName() {
         return mHostBrowserPackageName;
     }
 
@@ -156,27 +156,27 @@
      * Returns the major version of the host browser. Currently, only Chromium host browsers
      * (Chrome Canary, Chrome Dev ...) are supported.
      */
-    public int hostBrowserMajorChromiumVersion() {
+    public int getHostBrowserMajorChromiumVersion() {
         return mHostBrowserMajorChromiumVersion;
     }
 
     /** Returns whether the choose-host-browser dialog was shown. */
-    public boolean dialogShown() {
+    public boolean wasDialogShown() {
         return mDialogShown;
     }
 
     /** Returns intent used to launch WebAPK. */
-    public Intent originalIntent() {
+    public Intent getOriginalIntent() {
         return mOriginalIntent;
     }
 
     /** Returns URL to launch WebAPK at. */
-    public String startUrl() {
+    public String getStartUrl() {
         return mStartUrl;
     }
 
     /** Returns the source which is launching/navigating the WebAPK. */
-    public int source() {
+    public int getSource() {
         return mSource;
     }
 
@@ -184,12 +184,12 @@
      * Returns whether the WebAPK should be navigated to {@link mStartUrl} if it is already
      * running.
      */
-    public boolean forceNavigation() {
+    public boolean getForceNavigation() {
         return mForceNavigation;
     }
 
     /** Returns time in milliseconds that the WebAPK was launched. */
-    public long launchTimeMs() {
+    public long getLaunchTimeMs() {
         return mLaunchTimeMs;
     }
 }
diff --git a/chrome/app/OWNERS b/chrome/app/OWNERS
index 77d5e10..940323c 100644
--- a/chrome/app/OWNERS
+++ b/chrome/app/OWNERS
@@ -27,6 +27,7 @@
 per-file settings*strings*=file://chrome/browser/resources/settings/OWNERS
 
 per-file vr_strings.grdp=file://chrome/browser/vr/OWNERS
+per-file nux_strings.grdp=file://chrome/browser/ui/webui/welcome/nux/OWNERS
 
 # Non-GRD rules.
 
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 20395d4..cab8bd1 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -201,6 +201,11 @@
         <part file="md_extensions_strings.grdp" />
       </if>
 
+      <!-- NUX Welcome onboarding experience strings -->
+      <if expr="not chromeos and not is_android">
+        <part file="nux_strings.grdp" />
+      </if>
+
       <!-- Printing specific strings -->
       <if expr="enable_printing">
         <part file="printing_strings.grdp" />
@@ -9194,7 +9199,7 @@
         Searching...
       </message>
       <message name="IDS_WEBAUTHN_BLE_DEVICE_SELECTION_REMINDER_LABEL" desc="Label text. Reminds the user that their Bluetooth security key (an external physical device for user authentication) will only show up in the list of nearby Bluetooth devices if they put it into pairing mode. This is achieved by holding down the button on the device for 5 seconds, or longer." meaning="Here, `key` refers to a security key, an external physical device for user authentication.">
-        To see your security key, it needs to be in pairing mode. Press the button on your key for at least 5 seconds.
+        If your security key isn't listed, press the key's button for at least 5 seconds.
       </message>
       <message name="IDS_WEBAUTHN_BLE_PIN_ENTRY_TITLE" desc="Title of the dialog shown when instructing the user to enter the PIN code (a 6-digit number) to pair a Bluetooth security key (an external physical device for user authentication) with their computer.">
         Pair with <ph name="DEVICE_NAME">$1<ex>VHGSHSSN</ex></ph>
diff --git a/components/nux_email_strings.grdp b/chrome/app/nux_strings.grdp
similarity index 62%
rename from components/nux_email_strings.grdp
rename to chrome/app/nux_strings.grdp
index 0813071..e33793f 100644
--- a/components/nux_email_strings.grdp
+++ b/chrome/app/nux_strings.grdp
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <grit-part>
+  <!-- NUX email provider selection module -->
   <message name="IDS_NUX_EMAIL_GET_STARTED" desc="Label for a confirmation button to finish adding a bookmark and get the user started with using the browser.">
     Get started
   </message>
@@ -18,4 +19,15 @@
   <message name="IDS_NUX_EMAIL_BOOKMARK_REPLACED" desc="String read for accessibility to inform the user a bookmark was replaced.">
     Bookmark replaced
   </message>
+
+  <!-- NUX Google apps selection module -->
+  <message name="IDS_NUX_GOOGLE_APPS_GET_STARTED" desc="Message for a confirmation button that will add the selected apps from a list.">
+    Get started
+  </message>
+  <message name="IDS_NUX_GOOGLE_APPS_DESCRIPTION" desc="Description of what selecting apps and pressing 'Get started' will do.">
+    Get quick access to your favorite Google Apps
+  </message>
+  <message name="IDS_NUX_GOOGLE_APPS_DESCRIPTION_PROMO_BUBBLE" desc="Text shown when Google Apps have been added to highlight where bookmarks have been placed.">
+    Open apps easily with bookmarks
+  </message>
 </grit-part>
diff --git a/chrome/browser/android/download/video_frame_thumbnail_converter.cc b/chrome/browser/android/download/video_frame_thumbnail_converter.cc
index 208c34e..0b38ed6 100644
--- a/chrome/browser/android/download/video_frame_thumbnail_converter.cc
+++ b/chrome/browser/android/download/video_frame_thumbnail_converter.cc
@@ -38,7 +38,8 @@
     cc::SkiaPaintCanvas canvas(skbitmap);
     renderer.Copy(frame, &canvas,
                   media::Context3D(context_provider_->ContextGL(),
-                                   context_provider_->GrContext()));
+                                   context_provider_->GrContext()),
+                  context_provider_->ContextSupport());
 
     std::move(pixel_callback).Run(true, std::move(skbitmap));
   }
diff --git a/chrome/browser/apps/guest_view/web_view_interactive_browsertest.cc b/chrome/browser/apps/guest_view/web_view_interactive_browsertest.cc
index 86b7f71..7d561e70 100644
--- a/chrome/browser/apps/guest_view/web_view_interactive_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_interactive_browsertest.cc
@@ -879,21 +879,6 @@
     content::SimulateKeyPress(embedder_web_contents, ui::DomKey::TAB,
                               ui::DomCode::TAB, ui::VKEY_TAB, false, false,
                               false, false);
-
-#if defined(OS_MACOSX)
-    bool is_cocoa = true;
-#if BUILDFLAG(MAC_VIEWS_BROWSER)
-    is_cocoa = views_mode_controller::IsViewsBrowserCocoa();
-#endif  //  BUILDFLAG(MAC_VIEWS_BROWSER)
-    // TODO(mcnee): A third Tab key press should not be necessary, but we seem
-    // to need this on Mac when using Cocoa browser UI.
-    if (is_cocoa) {
-      content::SimulateKeyPress(embedder_web_contents, ui::DomKey::TAB,
-                                ui::DomCode::TAB, ui::VKEY_TAB, false, false,
-                                false, false);
-    }
-#endif  //  defined(OS_MACOSX)
-
     ASSERT_TRUE(listener.WaitUntilSatisfied());
   }
 }
diff --git a/chrome/browser/chrome_browser_main_win.cc b/chrome/browser/chrome_browser_main_win.cc
index 6afa2c1..6b40252 100644
--- a/chrome/browser/chrome_browser_main_win.cc
+++ b/chrome/browser/chrome_browser_main_win.cc
@@ -464,8 +464,8 @@
 
 void ChromeBrowserMainPartsWin::ToolkitInitialized() {
   ChromeBrowserMainParts::ToolkitInitialized();
-  gfx::PlatformFontWin::adjust_font_callback = &AdjustUIFont;
-  gfx::PlatformFontWin::get_minimum_font_size_callback = &GetMinimumFontSize;
+  gfx::PlatformFontWin::SetAdjustFontCallback(&AdjustUIFont);
+  gfx::PlatformFontWin::SetGetMinimumFontSizeCallback(&GetMinimumFontSize);
   ui::CursorLoaderWin::SetCursorResourceModule(chrome::kBrowserResourcesDll);
 }
 
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 4618f1d..0a8dc3a 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -1163,6 +1163,9 @@
     "login/screens/marketing_opt_in_screen.h",
     "login/screens/marketing_opt_in_screen_view.h",
     "login/screens/model_view_channel.h",
+    "login/screens/multidevice_setup_screen.cc",
+    "login/screens/multidevice_setup_screen.h",
+    "login/screens/multidevice_setup_screen_view.h",
     "login/screens/network_error.cc",
     "login/screens/network_error.h",
     "login/screens/network_error_view.h",
diff --git a/chrome/browser/chromeos/accessibility/DEPS b/chrome/browser/chromeos/accessibility/DEPS
index 1269720..190a10c 100644
--- a/chrome/browser/chromeos/accessibility/DEPS
+++ b/chrome/browser/chromeos/accessibility/DEPS
@@ -1,12 +1,17 @@
 specific_include_rules = {
   "accessibility_manager\.cc": [
-    # TODO(mash): Fix. https://crbug.com/594887
+    # TODO(mash): Fix. https://crbug.com/594887 https://crbug.com/888145
     "+ash/accessibility/accessibility_controller.h",
     "+ash/root_window_controller.h",
     "+ash/shell.h",
     # TODO(mash): Fix. https://crbug.com/647781
     "+ash/sticky_keys/sticky_keys_controller.h",
   ],
+  "ax_host_service\.cc": [
+    # TODO(mash): Fix. https://crbug.com/888145
+    "+ash/accessibility/accessibility_controller.h",
+    "+ash/shell.h",
+  ],
   "magnification_manager\.cc": [
     # TODO(mash): Fix. https://crbug.com/817157
     "+ash/magnifier/magnification_controller.h",
diff --git a/chrome/browser/chromeos/accessibility/ax_host_service.cc b/chrome/browser/chromeos/accessibility/ax_host_service.cc
index e478265..6b128237 100644
--- a/chrome/browser/chromeos/accessibility/ax_host_service.cc
+++ b/chrome/browser/chromeos/accessibility/ax_host_service.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/chromeos/accessibility/ax_host_service.h"
 
+#include "ash/accessibility/accessibility_controller.h"
+#include "ash/shell.h"
 #include "base/bind.h"
 #include "chrome/browser/extensions/api/automation_internal/automation_event_router.h"
 #include "chrome/common/extensions/chrome_extension_messages.h"
@@ -17,7 +19,17 @@
 
 bool AXHostService::automation_enabled_ = false;
 
-AXHostService::AXHostService() : ui::AXHostDelegate(views::RemoteAXTreeID()) {
+AXHostService::AXHostService() {
+  // AX tree ID is automatically assigned.
+  DCHECK(!tree_id().empty());
+
+  // ash::Shell may not exist in tests.
+  if (ash::Shell::HasInstance()) {
+    // TODO(jamescook): Eliminate this when tree ID assignment is handed in ash.
+    ash::Shell::Get()->accessibility_controller()->set_remote_ax_tree_id(
+        tree_id());
+  }
+
   DCHECK(!instance_);
   instance_ = this;
   registry_.AddInterface<ax::mojom::AXHost>(
@@ -43,22 +55,22 @@
   registry_.BindInterface(interface_name, std::move(interface_pipe));
 }
 
-void AXHostService::SetRemoteHost(ax::mojom::AXRemoteHostPtr remote) {
+void AXHostService::SetRemoteHost(ax::mojom::AXRemoteHostPtr remote,
+                                  SetRemoteHostCallback cb) {
   remote_host_ = std::move(remote);
 
   // Handle both clean and unclean shutdown.
   remote_host_.set_connection_error_handler(base::BindOnce(
       &AXHostService::OnRemoteHostDisconnected, base::Unretained(this)));
 
-  // Ensure remote host knows the initial state.
-  remote_host_->OnAutomationEnabled(automation_enabled_);
+  std::move(cb).Run(tree_id(), automation_enabled_);
 }
 
 void AXHostService::HandleAccessibilityEvent(
     const std::string& tree_id,
     const std::vector<ui::AXTreeUpdate>& updates,
     const ui::AXEvent& event) {
-  DCHECK_EQ(tree_id, views::RemoteAXTreeID());
+  CHECK_EQ(tree_id, this->tree_id());
   ExtensionMsg_AccessibilityEventBundleParams event_bundle;
   event_bundle.tree_id = tree_id;
   for (const ui::AXTreeUpdate& update : updates)
@@ -93,5 +105,5 @@
 
 void AXHostService::OnRemoteHostDisconnected() {
   extensions::AutomationEventRouter::GetInstance()->DispatchTreeDestroyedEvent(
-      views::RemoteAXTreeID(), nullptr /* browser_context */);
+      tree_id(), nullptr /* browser_context */);
 }
diff --git a/chrome/browser/chromeos/accessibility/ax_host_service.h b/chrome/browser/chromeos/accessibility/ax_host_service.h
index d616f23..9d2fa2a 100644
--- a/chrome/browser/chromeos/accessibility/ax_host_service.h
+++ b/chrome/browser/chromeos/accessibility/ax_host_service.h
@@ -38,7 +38,8 @@
                        mojo::ScopedMessagePipeHandle interface_pipe) override;
 
   // ax::mojom::AXHost:
-  void SetRemoteHost(ax::mojom::AXRemoteHostPtr remote) override;
+  void SetRemoteHost(ax::mojom::AXRemoteHostPtr remote,
+                     SetRemoteHostCallback cb) override;
   void HandleAccessibilityEvent(const std::string& tree_id,
                                 const std::vector<ui::AXTreeUpdate>& updates,
                                 const ui::AXEvent& event) override;
@@ -46,6 +47,7 @@
   // ui::AXHostDelegate:
   void PerformAction(const ui::AXActionData& data) override;
 
+  ui::AXTreeID tree_id_for_testing() const { return tree_id(); }
   void FlushForTesting();
 
  private:
diff --git a/chrome/browser/chromeos/accessibility/ax_host_service_unittest.cc b/chrome/browser/chromeos/accessibility/ax_host_service_unittest.cc
index 59fe697..43ac4b4 100644
--- a/chrome/browser/chromeos/accessibility/ax_host_service_unittest.cc
+++ b/chrome/browser/chromeos/accessibility/ax_host_service_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/chromeos/accessibility/ax_host_service.h"
 
+#include "base/bind.h"
 #include "base/macros.h"
 #include "base/test/scoped_task_environment.h"
 #include "mojo/public/cpp/bindings/binding.h"
@@ -23,6 +24,12 @@
     return ptr;
   }
 
+  // Simulates the real AXRemoteHost.
+  void SetRemoteHostCallback(const ui::AXTreeID& tree_id, bool enabled) {
+    tree_id_ = tree_id;
+    OnAutomationEnabled(enabled);
+  }
+
   // ax::mojom::AXRemoteHost:
   void OnAutomationEnabled(bool enabled) override {
     ++automation_enabled_count_;
@@ -34,6 +41,7 @@
   }
 
   mojo::Binding<ax::mojom::AXRemoteHost> binding_;
+  ui::AXTreeID tree_id_;
   int automation_enabled_count_ = 0;
   bool last_automation_enabled_ = false;
   int perform_action_count_ = 0;
@@ -57,12 +65,15 @@
 TEST_F(AXHostServiceTest, AddClientThenEnable) {
   AXHostService service;
   TestAXRemoteHost remote;
-  service.SetRemoteHost(remote.CreateInterfacePtr());
+  service.SetRemoteHost(remote.CreateInterfacePtr(),
+                        base::BindOnce(&TestAXRemoteHost::SetRemoteHostCallback,
+                                       base::Unretained(&remote)));
   service.FlushForTesting();
 
   // Remote received initial state.
   EXPECT_EQ(1, remote.automation_enabled_count_);
   EXPECT_FALSE(remote.last_automation_enabled_);
+  EXPECT_EQ(service.tree_id_for_testing(), remote.tree_id_);
 
   AXHostService::SetAutomationEnabled(true);
   service.FlushForTesting();
@@ -77,12 +88,15 @@
   AXHostService::SetAutomationEnabled(true);
 
   TestAXRemoteHost remote;
-  service.SetRemoteHost(remote.CreateInterfacePtr());
+  service.SetRemoteHost(remote.CreateInterfacePtr(),
+                        base::BindOnce(&TestAXRemoteHost::SetRemoteHostCallback,
+                                       base::Unretained(&remote)));
   service.FlushForTesting();
 
   // Remote received initial state.
   EXPECT_EQ(1, remote.automation_enabled_count_);
   EXPECT_TRUE(remote.last_automation_enabled_);
+  EXPECT_EQ(service.tree_id_for_testing(), remote.tree_id_);
 }
 
 TEST_F(AXHostServiceTest, PerformAction) {
@@ -90,7 +104,9 @@
   AXHostService::SetAutomationEnabled(true);
 
   TestAXRemoteHost remote;
-  service.SetRemoteHost(remote.CreateInterfacePtr());
+  service.SetRemoteHost(remote.CreateInterfacePtr(),
+                        base::BindOnce(&TestAXRemoteHost::SetRemoteHostCallback,
+                                       base::Unretained(&remote)));
   service.FlushForTesting();
 
   ui::AXActionData action;
diff --git a/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc b/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc
index 71c6632..acb7ca39 100644
--- a/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc
+++ b/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc
@@ -276,8 +276,10 @@
   // Wait for the AX tree to be destroyed.
   run_loop.Run();
 
-  // Verify the correct AX tree was destroyed.
-  EXPECT_EQ(views::RemoteAXTreeID(), destroyed_tree_id);
+  // Verify an AX tree was destroyed. It's awkward to get the remote app's
+  // actual tree ID, so just ensure it's a valid ID and not the desktop.
+  EXPECT_NE(ui::AXTreeIDUnknown(), destroyed_tree_id);
+  EXPECT_NE(ui::DesktopAXTreeID(), destroyed_tree_id);
 
   extensions::AutomationEventRouter::GetInstance()
       ->SetTreeDestroyedCallbackForTest(base::DoNothing());
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_drive.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_drive.cc
index 690b3334..f54eb7b 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_drive.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_drive.cc
@@ -48,6 +48,7 @@
 #include "google_apis/drive/auth_service.h"
 #include "google_apis/drive/drive_api_url_generator.h"
 #include "mojo/public/cpp/bindings/callback_helpers.h"
+#include "net/base/network_change_notifier.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "storage/common/fileapi/file_system_info.h"
 #include "storage/common/fileapi/file_system_util.h"
@@ -825,10 +826,19 @@
   drivefs::mojom::SearchQueryPtr search;
   integration_service->GetDriveFsInterface()->StartSearchQuery(
       mojo::MakeRequest(&search), query.Clone());
-  auto* raw_search = search.get();
-  raw_search->GetNextPage(base::BindOnce(&OnSearchDriveFs, std::move(function),
-                                         std::move(search), std::move(query),
-                                         filter_dirs, std::move(callback)));
+  if (net::NetworkChangeNotifier::IsOffline() &&
+      query->query_source !=
+          drivefs::mojom::QueryParameters::QuerySource::kLocalOnly) {
+    // No point trying cloud query if we know we are offline.
+    OnSearchDriveFs(std::move(function), std::move(search), std::move(query),
+                    filter_dirs, std::move(callback),
+                    drive::FILE_ERROR_NO_CONNECTION, {});
+  } else {
+    auto* raw_search = search.get();
+    raw_search->GetNextPage(
+        base::BindOnce(&OnSearchDriveFs, std::move(function), std::move(search),
+                       std::move(query), filter_dirs, std::move(callback)));
+  }
 }
 
 }  // namespace
diff --git a/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc b/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
index 5578cae..9f12a8e 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
@@ -999,11 +999,6 @@
     command_line->AppendSwitch(switches::kIncognito);
   }
 
-  if (!IsZipTest()) {  // Block NaCl use unless needed crbug.com/788671
-    command_line->AppendSwitch(chromeos::switches::kDisableZipArchiverUnpacker);
-    command_line->AppendSwitch(chromeos::switches::kDisableZipArchiverPacker);
-  }
-
   std::vector<base::Feature> enabled_features;
   if (!IsGuestModeTest()) {
     enabled_features.emplace_back(features::kCrostini);
diff --git a/chrome/browser/chromeos/login/demo_mode/demo_setup_controller.cc b/chrome/browser/chromeos/login/demo_mode/demo_setup_controller.cc
index 8448dfe..2b826081 100644
--- a/chrome/browser/chromeos/login/demo_mode/demo_setup_controller.cc
+++ b/chrome/browser/chromeos/login/demo_mode/demo_setup_controller.cc
@@ -137,6 +137,14 @@
 }
 
 // static
+void DemoSetupController::ClearDemoRequisition(
+    policy::DeviceCloudPolicyManagerChromeOS* policy_manager) {
+  if (policy_manager->GetDeviceRequisition() == kDemoRequisition) {
+    policy_manager->SetDeviceRequisition(std::string());
+  }
+}
+
+// static
 bool DemoSetupController::IsDemoModeAllowed() {
   // Demo mode is only allowed on devices that support ARC++.
   return arc::IsArcAvailable();
@@ -230,10 +238,12 @@
     return;
   }
 
-  policy::BrowserPolicyConnectorChromeOS* connector =
-      g_browser_process->platform_part()->browser_policy_connector_chromeos();
-  connector->GetDeviceCloudPolicyManager()->SetDeviceRequisition(
-      kDemoRequisition);
+  policy::DeviceCloudPolicyManagerChromeOS* policy_manager =
+      g_browser_process->platform_part()
+          ->browser_policy_connector_chromeos()
+          ->GetDeviceCloudPolicyManager();
+  DCHECK(policy_manager->GetDeviceRequisition().empty());
+  policy_manager->SetDeviceRequisition(kDemoRequisition);
   policy::EnrollmentConfig config;
   config.mode = policy::EnrollmentConfig::MODE_ATTESTATION;
   config.management_domain = DemoSetupController::kDemoModeDomain;
@@ -422,6 +432,7 @@
   DCHECK_NE(demo_config_, DemoSession::DemoModeConfig::kNone);
   DCHECK_NE(demo_config_ == DemoSession::DemoModeConfig::kOffline,
             policy_dir_.empty());
+
   // |demo_config_| is not reset here, because it is needed for retrying setup.
   enrollment_helper_.reset();
   policy_dir_.clear();
@@ -429,6 +440,11 @@
     device_local_account_policy_store_->RemoveObserver(this);
     device_local_account_policy_store_ = nullptr;
   }
+  policy::DeviceCloudPolicyManagerChromeOS* policy_manager =
+      g_browser_process->platform_part()
+          ->browser_policy_connector_chromeos()
+          ->GetDeviceCloudPolicyManager();
+  ClearDemoRequisition(policy_manager);
 }
 
 void DemoSetupController::OnStoreLoaded(policy::CloudPolicyStore* store) {
diff --git a/chrome/browser/chromeos/login/demo_mode/demo_setup_controller.h b/chrome/browser/chromeos/login/demo_mode/demo_setup_controller.h
index 63b8b00..d999d60 100644
--- a/chrome/browser/chromeos/login/demo_mode/demo_setup_controller.h
+++ b/chrome/browser/chromeos/login/demo_mode/demo_setup_controller.h
@@ -18,6 +18,10 @@
 
 class PrefRegistrySimple;
 
+namespace policy {
+class DeviceCloudPolicyManagerChromeOS;
+}
+
 namespace chromeos {
 
 // Controlls enrollment flow for setting up Demo Mode.
@@ -44,6 +48,11 @@
 
   static void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
 
+  // Clears demo device enrollment requisition on the given |policy_manager| if
+  // it is set.
+  static void ClearDemoRequisition(
+      policy::DeviceCloudPolicyManagerChromeOS* policy_manager);
+
   // Utility method that returns whether demo mode is allowed on the device.
   static bool IsDemoModeAllowed();
 
diff --git a/chrome/browser/chromeos/login/demo_mode/demo_setup_controller_unittest.cc b/chrome/browser/chromeos/login/demo_mode/demo_setup_controller_unittest.cc
index d76a7ce..88c1548 100644
--- a/chrome/browser/chromeos/login/demo_mode/demo_setup_controller_unittest.cc
+++ b/chrome/browser/chromeos/login/demo_mode/demo_setup_controller_unittest.cc
@@ -13,8 +13,11 @@
 #include "base/optional.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
+#include "chrome/browser/browser_process_platform_part.h"
 #include "chrome/browser/chromeos/login/demo_mode/demo_session.h"
 #include "chrome/browser/chromeos/login/demo_mode/demo_setup_test_utils.h"
+#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
+#include "chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h"
 #include "chrome/browser/chromeos/settings/device_settings_service.h"
 #include "chrome/browser/chromeos/settings/stub_install_attributes.h"
 #include "chrome/browser/component_updater/cros_component_installer_chromeos.h"
@@ -95,6 +98,11 @@
     SystemSaltGetter::Initialize();
     DBusThreadManager::Initialize();
     DeviceSettingsService::Initialize();
+    TestingBrowserProcess::GetGlobal()
+        ->platform_part()
+        ->browser_policy_connector_chromeos()
+        ->GetDeviceCloudPolicyManager()
+        ->Initialize(TestingBrowserProcess::GetGlobal()->local_state());
     helper_ = std::make_unique<DemoSetupControllerTestHelper>();
     tested_controller_ = std::make_unique<DemoSetupController>();
   }
@@ -105,6 +113,14 @@
     DeviceSettingsService::Shutdown();
   }
 
+  static std::string GetDeviceRequisition() {
+    return TestingBrowserProcess::GetGlobal()
+        ->platform_part()
+        ->browser_policy_connector_chromeos()
+        ->GetDeviceCloudPolicyManager()
+        ->GetDeviceRequisition();
+  }
+
   std::unique_ptr<DemoSetupControllerTestHelper> helper_;
   std::unique_ptr<DemoSetupController> tested_controller_;
 
@@ -139,6 +155,7 @@
                      base::Unretained(helper_.get())));
 
   EXPECT_TRUE(helper_->WaitResult(true));
+  EXPECT_EQ("", GetDeviceRequisition());
 }
 
 TEST_F(DemoSetupControllerTest, OfflineDeviceLocalAccountPolicyLoadFailure) {
@@ -161,6 +178,7 @@
 
   EXPECT_TRUE(helper_->WaitResult(false));
   EXPECT_FALSE(helper_->IsErrorFatal());
+  EXPECT_EQ("", GetDeviceRequisition());
 }
 
 TEST_F(DemoSetupControllerTest, OfflineDeviceLocalAccountPolicyStoreFailed) {
@@ -186,6 +204,7 @@
 
   EXPECT_TRUE(helper_->WaitResult(false));
   EXPECT_TRUE(helper_->IsErrorFatal());
+  EXPECT_EQ("", GetDeviceRequisition());
 }
 
 TEST_F(DemoSetupControllerTest, OfflineInvalidDeviceLocalAccountPolicyBlob) {
@@ -206,6 +225,7 @@
 
   EXPECT_TRUE(helper_->WaitResult(false));
   EXPECT_TRUE(helper_->IsErrorFatal());
+  EXPECT_EQ("", GetDeviceRequisition());
 }
 
 TEST_F(DemoSetupControllerTest, OfflineError) {
@@ -229,6 +249,7 @@
 
   EXPECT_TRUE(helper_->WaitResult(false));
   EXPECT_FALSE(helper_->IsErrorFatal());
+  EXPECT_EQ("", GetDeviceRequisition());
 }
 
 TEST_F(DemoSetupControllerTest, OnlineSuccess) {
@@ -243,6 +264,7 @@
                      base::Unretained(helper_.get())));
 
   EXPECT_TRUE(helper_->WaitResult(true));
+  EXPECT_EQ("", GetDeviceRequisition());
 }
 
 TEST_F(DemoSetupControllerTest, OnlineError) {
@@ -258,6 +280,7 @@
 
   EXPECT_TRUE(helper_->WaitResult(false));
   EXPECT_FALSE(helper_->IsErrorFatal());
+  EXPECT_EQ("", GetDeviceRequisition());
 }
 
 TEST_F(DemoSetupControllerTest, OnlineComponentError) {
@@ -276,6 +299,7 @@
 
   EXPECT_TRUE(helper_->WaitResult(false));
   EXPECT_FALSE(helper_->IsErrorFatal());
+  EXPECT_EQ("", GetDeviceRequisition());
 }
 
 TEST_F(DemoSetupControllerTest, EnrollTwice) {
@@ -291,6 +315,7 @@
 
   EXPECT_TRUE(helper_->WaitResult(false));
   EXPECT_FALSE(helper_->IsErrorFatal());
+  EXPECT_EQ("", GetDeviceRequisition());
 
   helper_->Reset();
 
@@ -305,6 +330,7 @@
                      base::Unretained(helper_.get())));
 
   EXPECT_TRUE(helper_->WaitResult(true));
+  EXPECT_EQ("", GetDeviceRequisition());
 }
 
 }  //  namespace chromeos
diff --git a/chrome/browser/chromeos/login/oobe_screen.cc b/chrome/browser/chromeos/login/oobe_screen.cc
index 0ffa064..7a56703 100644
--- a/chrome/browser/chromeos/login/oobe_screen.cc
+++ b/chrome/browser/chromeos/login/oobe_screen.cc
@@ -65,6 +65,7 @@
     "app-downloading",               // SCREEN_APP_DOWNLOADING
     "discover",                      // SCREEN_DISCOVER
     "marketing-opt-in",              // SCREEN_MARKETING_OPT_IN
+    "multidevice-setup",             // SCREEN_MULTIDEVICE_SETUP
     "unknown",                       // SCREEN_UNKNOWN
 };
 
diff --git a/chrome/browser/chromeos/login/oobe_screen.h b/chrome/browser/chromeos/login/oobe_screen.h
index c2abc33..b6d263d 100644
--- a/chrome/browser/chromeos/login/oobe_screen.h
+++ b/chrome/browser/chromeos/login/oobe_screen.h
@@ -69,6 +69,7 @@
   SCREEN_DISCOVER,
 
   SCREEN_MARKETING_OPT_IN,
+  SCREEN_MULTIDEVICE_SETUP,
 
   SCREEN_UNKNOWN  // This must always be the last element.
 };
diff --git a/chrome/browser/chromeos/login/screens/multidevice_setup_screen.cc b/chrome/browser/chromeos/login/screens/multidevice_setup_screen.cc
new file mode 100644
index 0000000..69e7de8
--- /dev/null
+++ b/chrome/browser/chromeos/login/screens/multidevice_setup_screen.cc
@@ -0,0 +1,48 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/login/screens/multidevice_setup_screen.h"
+
+#include "base/logging.h"
+#include "chrome/browser/chromeos/login/screens/multidevice_setup_screen_view.h"
+
+namespace chromeos {
+
+namespace {
+
+constexpr const char kFinishedUserAction[] = "setup-finished";
+
+}  // namespace
+
+MultiDeviceSetupScreen::MultiDeviceSetupScreen(
+    BaseScreenDelegate* base_screen_delegate,
+    MultiDeviceSetupScreenView* view)
+    : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_MULTIDEVICE_SETUP),
+      view_(view) {
+  DCHECK(view_);
+  view_->Bind(this);
+}
+
+MultiDeviceSetupScreen::~MultiDeviceSetupScreen() {
+  view_->Bind(nullptr);
+}
+
+void MultiDeviceSetupScreen::Show() {
+  view_->Show();
+}
+
+void MultiDeviceSetupScreen::Hide() {
+  view_->Hide();
+}
+
+void MultiDeviceSetupScreen::OnUserAction(const std::string& action_id) {
+  if (action_id == kFinishedUserAction) {
+    Finish(ScreenExitCode::MULTIDEVICE_SETUP_FINISHED);
+    return;
+  }
+
+  BaseScreen::OnUserAction(action_id);
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/multidevice_setup_screen.h b/chrome/browser/chromeos/login/screens/multidevice_setup_screen.h
new file mode 100644
index 0000000..eff3aa5
--- /dev/null
+++ b/chrome/browser/chromeos/login/screens/multidevice_setup_screen.h
@@ -0,0 +1,37 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_MULTIDEVICE_SETUP_SCREEN_H_
+#define CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_MULTIDEVICE_SETUP_SCREEN_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "chrome/browser/chromeos/login/screens/base_screen.h"
+
+namespace chromeos {
+
+class BaseScreenDelegate;
+class MultiDeviceSetupScreenView;
+
+class MultiDeviceSetupScreen : public BaseScreen {
+ public:
+  MultiDeviceSetupScreen(BaseScreenDelegate* base_screen_delegate,
+                         MultiDeviceSetupScreenView* view);
+  ~MultiDeviceSetupScreen() override;
+
+  // BaseScreen:
+  void Show() override;
+  void Hide() override;
+  void OnUserAction(const std::string& action_id) override;
+
+ private:
+  MultiDeviceSetupScreenView* view_;
+
+  DISALLOW_COPY_AND_ASSIGN(MultiDeviceSetupScreen);
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_MULTIDEVICE_SETUP_SCREEN_H_
diff --git a/chrome/browser/chromeos/login/screens/multidevice_setup_screen_view.h b/chrome/browser/chromeos/login/screens/multidevice_setup_screen_view.h
new file mode 100644
index 0000000..093236e
--- /dev/null
+++ b/chrome/browser/chromeos/login/screens/multidevice_setup_screen_view.h
@@ -0,0 +1,29 @@
+// 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_CHROMEOS_LOGIN_SCREENS_MULTIDEVICE_SETUP_SCREEN_VIEW_H_
+#define CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_MULTIDEVICE_SETUP_SCREEN_VIEW_H_
+
+#include "chrome/browser/chromeos/login/oobe_screen.h"
+
+namespace chromeos {
+
+class MultiDeviceSetupScreen;
+
+// Interface for dependency injection between MultiDeviceSetupScreen and its
+// WebUI representation.
+class MultiDeviceSetupScreenView {
+ public:
+  constexpr static OobeScreen kScreenId = OobeScreen::SCREEN_MULTIDEVICE_SETUP;
+
+  virtual ~MultiDeviceSetupScreenView() = default;
+
+  virtual void Bind(MultiDeviceSetupScreen* screen) = 0;
+  virtual void Show() = 0;
+  virtual void Hide() = 0;
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_MULTIDEVICE_SETUP_SCREEN_VIEW_H_
diff --git a/chrome/browser/chromeos/login/screens/screen_exit_code.cc b/chrome/browser/chromeos/login/screens/screen_exit_code.cc
index a6825a80..e4207ce 100644
--- a/chrome/browser/chromeos/login/screens/screen_exit_code.cc
+++ b/chrome/browser/chromeos/login/screens/screen_exit_code.cc
@@ -100,6 +100,10 @@
       return "FINGERPRINT_SETUP_FINISHED";
     case ScreenExitCode::MARKETING_OPT_IN_FINISHED:
       return "MARKETING_OPT_IN_FINISHED";
+    case ScreenExitCode::ASSISTANT_OPTIN_FLOW_FINISHED:
+      return "ASSISTANT_OPTIN_FLOW_FINISHED";
+    case ScreenExitCode::MULTIDEVICE_SETUP_FINISHED:
+      return "MULTIDEVICE_SETUP_FINISHED";
     case ScreenExitCode::EXIT_CODES_COUNT:
     default:
       NOTREACHED();
diff --git a/chrome/browser/chromeos/login/screens/screen_exit_code.h b/chrome/browser/chromeos/login/screens/screen_exit_code.h
index 52e8364..277e631 100644
--- a/chrome/browser/chromeos/login/screens/screen_exit_code.h
+++ b/chrome/browser/chromeos/login/screens/screen_exit_code.h
@@ -72,6 +72,7 @@
   FINGERPRINT_SETUP_FINISHED = 45,
   MARKETING_OPT_IN_FINISHED = 46,
   ASSISTANT_OPTIN_FLOW_FINISHED = 47,
+  MULTIDEVICE_SETUP_FINISHED = 48,
   EXIT_CODES_COUNT  // not a real code, must be the last
 };
 
diff --git a/chrome/browser/chromeos/login/wizard_controller.cc b/chrome/browser/chromeos/login/wizard_controller.cc
index 7773c32..170bf91 100644
--- a/chrome/browser/chromeos/login/wizard_controller.cc
+++ b/chrome/browser/chromeos/login/wizard_controller.cc
@@ -58,6 +58,7 @@
 #include "chrome/browser/chromeos/login/screens/kiosk_autolaunch_screen.h"
 #include "chrome/browser/chromeos/login/screens/kiosk_enable_screen.h"
 #include "chrome/browser/chromeos/login/screens/marketing_opt_in_screen.h"
+#include "chrome/browser/chromeos/login/screens/multidevice_setup_screen.h"
 #include "chrome/browser/chromeos/login/screens/network_error.h"
 #include "chrome/browser/chromeos/login/screens/network_screen.h"
 #include "chrome/browser/chromeos/login/screens/recommend_apps_screen.h"
@@ -94,6 +95,7 @@
 #include "chrome/common/pref_names.h"
 #include "chromeos/audio/cras_audio_handler.h"
 #include "chromeos/chromeos_constants.h"
+#include "chromeos/chromeos_features.h"
 #include "chromeos/chromeos_switches.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/session_manager_client.h"
@@ -152,6 +154,7 @@
     chromeos::OobeScreen::SCREEN_APP_DOWNLOADING,
     chromeos::OobeScreen::SCREEN_DISCOVER,
     chromeos::OobeScreen::SCREEN_MARKETING_OPT_IN,
+    chromeos::OobeScreen::SCREEN_MULTIDEVICE_SETUP,
 };
 
 // Checks if device is in tablet mode, and that HID-detection screen is not
@@ -524,6 +527,9 @@
   } else if (screen == OobeScreen::SCREEN_ASSISTANT_OPTIN_FLOW) {
     return std::make_unique<AssistantOptInFlowScreen>(
         this, oobe_ui->GetAssistantOptInFlowScreenView());
+  } else if (screen == OobeScreen::SCREEN_MULTIDEVICE_SETUP) {
+    return std::make_unique<MultiDeviceSetupScreen>(
+        this, oobe_ui->GetMultiDeviceSetupScreenView());
   } else if (screen == OobeScreen::SCREEN_DISCOVER) {
     return std::make_unique<DiscoverScreen>(this,
                                             oobe_ui->GetDiscoverScreenView());
@@ -833,6 +839,20 @@
   SetCurrentScreen(GetScreen(OobeScreen::SCREEN_ASSISTANT_OPTIN_FLOW));
 }
 
+void WizardController::ShowMultiDeviceSetupScreen() {
+  if (base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi) &&
+      base::FeatureList::IsEnabled(
+          chromeos::features::kEnableUnifiedMultiDeviceSetup)) {
+    // TODO(khorimoto): Use MultiDeviceSetupClient to determine if any potential
+    // phone(s) on the same GAIA account can be used for multi-device features.
+    // Only show the setup screen if it is possible to use these features.
+    UpdateStatusAreaVisibilityForScreen(OobeScreen::SCREEN_MULTIDEVICE_SETUP);
+    SetCurrentScreen(GetScreen(OobeScreen::SCREEN_MULTIDEVICE_SETUP));
+  } else {
+    OnMultiDeviceSetupFinished();
+  }
+}
+
 void WizardController::ShowDiscoverScreen() {
   VLOG(1) << "Showing Discover screen.";
   UpdateStatusAreaVisibilityForScreen(OobeScreen::SCREEN_DISCOVER);
@@ -1164,6 +1184,10 @@
 }
 
 void WizardController::OnAssistantOptInFlowFinished() {
+  ShowMultiDeviceSetupScreen();
+}
+
+void WizardController::OnMultiDeviceSetupFinished() {
   ShowUserImageScreen();
 }
 
@@ -1509,6 +1533,8 @@
     ShowUpdateRequiredScreen();
   } else if (screen == OobeScreen::SCREEN_ASSISTANT_OPTIN_FLOW) {
     ShowAssistantOptInFlowScreen();
+  } else if (screen == OobeScreen::SCREEN_MULTIDEVICE_SETUP) {
+    ShowMultiDeviceSetupScreen();
   } else if (screen == OobeScreen::SCREEN_DISCOVER) {
     ShowDiscoverScreen();
   } else if (screen == OobeScreen::SCREEN_FINGERPRINT_SETUP) {
@@ -1697,6 +1723,9 @@
     case ScreenExitCode::ASSISTANT_OPTIN_FLOW_FINISHED:
       OnAssistantOptInFlowFinished();
       break;
+    case ScreenExitCode::MULTIDEVICE_SETUP_FINISHED:
+      OnMultiDeviceSetupFinished();
+      break;
     default:
       NOTREACHED();
   }
diff --git a/chrome/browser/chromeos/login/wizard_controller.h b/chrome/browser/chromeos/login/wizard_controller.h
index 474fb71..47362d4 100644
--- a/chrome/browser/chromeos/login/wizard_controller.h
+++ b/chrome/browser/chromeos/login/wizard_controller.h
@@ -197,6 +197,7 @@
   void ShowWaitForContainerReadyScreen();
   void ShowUpdateRequiredScreen();
   void ShowAssistantOptInFlowScreen();
+  void ShowMultiDeviceSetupScreen();
   void ShowDiscoverScreen();
   void ShowMarketingOptInScreen();
 
@@ -246,6 +247,7 @@
   void OnDemoPreferencesCanceled();
   void OnWaitForContainerReadyFinished();
   void OnAssistantOptInFlowFinished();
+  void OnMultiDeviceSetupFinished();
   void OnOobeFlowFinished();
   void OnMarketingOptInFinished();
 
diff --git a/chrome/browser/chromeos/login/wizard_controller_browsertest.cc b/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
index 44384037..d18e5483 100644
--- a/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
+++ b/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
@@ -2642,7 +2642,10 @@
 // TODO(xiaoyinh): Add tests for Fingerprint Setup UI.
 
 // TODO(alemate): Add tests for Marketing Opt-In.
-static_assert(static_cast<int>(ScreenExitCode::EXIT_CODES_COUNT) == 48,
+
+// TODO(khorimoto): Add tests for MultiDevice Setup UI.
+
+static_assert(static_cast<int>(ScreenExitCode::EXIT_CODES_COUNT) == 49,
               "tests for new control flow are missing");
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc b/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc
index 9b6542e..9a77229 100644
--- a/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc
+++ b/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc
@@ -22,6 +22,7 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/attestation/attestation_policy_observer.h"
 #include "chrome/browser/chromeos/attestation/enrollment_policy_observer.h"
+#include "chrome/browser/chromeos/login/demo_mode/demo_setup_controller.h"
 #include "chrome/browser/chromeos/login/enrollment/auto_enrollment_controller.h"
 #include "chrome/browser/chromeos/login/startup_utils.h"
 #include "chrome/browser/chromeos/policy/device_cloud_policy_store_chromeos.h"
@@ -332,6 +333,9 @@
   if (chromeos::StartupUtils::IsOobeCompleted())
     return;
 
+  // Demo requisition may have been set in a prior enrollment attempt that was
+  // interrupted.
+  chromeos::DemoSetupController::ClearDemoRequisition(this);
   const PrefService::Preference* pref = local_state_->FindPreference(
       prefs::kDeviceEnrollmentRequisition);
   if (pref->IsDefaultValue()) {
diff --git a/chrome/browser/data_use_measurement/page_load_capping/page_load_capping_browsertest.cc b/chrome/browser/data_use_measurement/page_load_capping/page_load_capping_browsertest.cc
index cfc25f0..61eba02 100644
--- a/chrome/browser/data_use_measurement/page_load_capping/page_load_capping_browsertest.cc
+++ b/chrome/browser/data_use_measurement/page_load_capping/page_load_capping_browsertest.cc
@@ -10,6 +10,7 @@
 #include "base/run_loop.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
@@ -25,6 +26,7 @@
 #include "components/infobars/core/confirm_infobar_delegate.h"
 #include "components/infobars/core/infobar.h"
 #include "components/infobars/core/infobar_delegate.h"
+#include "components/infobars/core/infobar_manager.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browsing_data_remover.h"
@@ -44,6 +46,26 @@
 const base::FilePath::CharType kDocRoot[] =
     FILE_PATH_LITERAL("chrome/test/data/data_use_measurement");
 const char kImagePrefix[] = "/image";
+
+class TestInfoBarObserver : public infobars::InfoBarManager::Observer {
+ public:
+  explicit TestInfoBarObserver(base::RunLoop* run_loop) : run_loop_(run_loop) {}
+  ~TestInfoBarObserver() override {}
+
+  void OnInfoBarAdded(infobars::InfoBar* infobar) override {}
+  void OnInfoBarRemoved(infobars::InfoBar* infobar, bool animate) override {
+    run_loop_->QuitWhenIdle();
+  }
+  void OnInfoBarReplaced(infobars::InfoBar* old_infobar,
+                         infobars::InfoBar* new_infobar) override {}
+  void OnManagerShuttingDown(infobars::InfoBarManager* manager) override {
+    NOTREACHED();
+  }
+
+ private:
+  base::RunLoop* run_loop_;
+};
+
 }  // namespace
 
 class PageLoadCappingBrowserTest : public InProcessBrowserTest {
@@ -51,8 +73,10 @@
   PageLoadCappingBrowserTest()
       : https_test_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
 
+  ~PageLoadCappingBrowserTest() override {}
+
   void PostToSelf() {
-    EXPECT_FALSE(waiting_);
+    EXPECT_FALSE(waiting_for_infobar_event_ || waiting_for_request_);
     base::RunLoop run_loop;
     base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
                                                   run_loop.QuitClosure());
@@ -60,13 +84,25 @@
   }
 
   void WaitForRequest() {
-    EXPECT_FALSE(waiting_);
-    waiting_ = true;
+    EXPECT_FALSE(waiting_for_infobar_event_ || waiting_for_request_);
+    waiting_for_request_ = true;
     run_loop_ = std::make_unique<base::RunLoop>();
     run_loop_->Run();
     run_loop_.reset();
   }
 
+  void WaitForInfoBarRemoved() {
+    EXPECT_FALSE(waiting_for_infobar_event_ || waiting_for_request_);
+    waiting_for_infobar_event_ = true;
+    run_loop_ = std::make_unique<base::RunLoop>();
+    TestInfoBarObserver test_observer(run_loop_.get());
+    InfoBarService::FromWebContents(contents())->AddObserver(&test_observer);
+    run_loop_->Run();
+    InfoBarService::FromWebContents(contents())->RemoveObserver(&test_observer);
+    waiting_for_infobar_event_ = false;
+    run_loop_.reset();
+  }
+
   GURL GetURL(const std::string& url_string) {
     return https_test_server_.GetURL(url_string);
   }
@@ -115,7 +151,10 @@
     std::map<std::string, std::string> feature_parameters = {
         {"PageCapMiB", "0"},
         {"PageFuzzingKiB", "0"},
-        {"OptOutStoreDisabled", "true"}};
+        {"OptOutStoreDisabled", "true"},
+        {"InfoBarTimeoutInMilliseconds", "500000"}};
+    ChangeParams(&feature_parameters);
+
     base::FieldTrialParamAssociator::GetInstance()->AssociateFieldTrialParams(
         "TrialName1", "GroupName1", feature_parameters);
 
@@ -134,6 +173,8 @@
     InProcessBrowserTest::SetUp();
   }
 
+  virtual void ChangeParams(std::map<std::string, std::string>* params) {}
+
   std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
       const net::test_server::HttpRequest& request) {
     // Check if this matches the image requests from the test suite.
@@ -149,16 +190,17 @@
     std::unique_ptr<net::test_server::BasicHttpResponse> not_found_response =
         std::make_unique<net::test_server::BasicHttpResponse>();
     not_found_response->set_code(net::HTTP_NOT_FOUND);
-    if (waiting_) {
+    if (waiting_for_request_) {
       run_loop_->QuitWhenIdle();
-      waiting_ = false;
+      waiting_for_request_ = false;
     }
     return not_found_response;
   }
 
   net::EmbeddedTestServer https_test_server_;
   size_t images_attempted_ = 0u;
-  bool waiting_ = false;
+  bool waiting_for_request_ = false;
+  bool waiting_for_infobar_event_ = false;
   std::unique_ptr<base::RunLoop> run_loop_;
 
   base::test::ScopedFeatureList scoped_feature_list_;
@@ -429,3 +471,42 @@
 
   EXPECT_EQ(0u, InfoBarCount());
 }
+
+class PageLoadCappingBrowserTestDismissAfterNetworkUse
+    : public PageLoadCappingBrowserTest {
+ public:
+  PageLoadCappingBrowserTestDismissAfterNetworkUse() {}
+  ~PageLoadCappingBrowserTestDismissAfterNetworkUse() override {}
+
+  void ChangeParams(std::map<std::string, std::string>* params) override {
+    (*params)["InfoBarTimeoutInMilliseconds"] = "50";
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(PageLoadCappingBrowserTestDismissAfterNetworkUse,
+                       TestInfoBarDismiss) {
+  // Verifies the InfoBar dismisses shortly (5ms) after the last resource is
+  // loaded.
+  EnableDataSaver(true);
+
+  base::HistogramTester histogram_tester;
+
+  // Load a page and ignore the InfoBar.
+  NavigateToHeavyPage();
+
+  // Verify the InfoBar was shown (it might be dismissed already by the
+  // InfoBarTimeout logic).
+  histogram_tester.ExpectBucketCount("HeavyPageCapping.InfoBarInteraction", 0,
+                                     1);
+  bool is_dismissed = histogram_tester.GetBucketCount(
+                          "HeavyPageCapping.InfoBarInteraction", 3) > 0;
+  if (!is_dismissed) {
+    ASSERT_EQ(1u, InfoBarCount());
+    WaitForInfoBarRemoved();
+  }
+
+  histogram_tester.ExpectBucketCount("HeavyPageCapping.InfoBarInteraction", 3,
+                                     1);
+
+  ASSERT_EQ(0u, InfoBarCount());
+}
diff --git a/chrome/browser/data_use_measurement/page_load_capping/page_load_capping_infobar_delegate.cc b/chrome/browser/data_use_measurement/page_load_capping/page_load_capping_infobar_delegate.cc
index 0169a71..2bff9e8 100644
--- a/chrome/browser/data_use_measurement/page_load_capping/page_load_capping_infobar_delegate.cc
+++ b/chrome/browser/data_use_measurement/page_load_capping/page_load_capping_infobar_delegate.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "base/memory/weak_ptr.h"
 #include "base/metrics/histogram_macros.h"
 #include "build/build_config.h"
 #include "chrome/browser/android/android_theme_resources.h"
@@ -29,7 +30,9 @@
   // |pause_callback| will either pause subresource loading or resume it based
   // on the passed in bool.
   explicit ResumeDelegate(const PauseCallback& pause_callback)
-      : pause_callback_(pause_callback) {}
+      : pause_callback_(pause_callback) {
+    DCHECK(!pause_callback_.is_null());
+  }
   ~ResumeDelegate() override = default;
 
  private:
@@ -42,8 +45,6 @@
   }
   bool LinkClicked(WindowOpenDisposition disposition) override {
     RecordInteractionUMA(InfoBarInteraction::kResumedPage);
-    if (pause_callback_.is_null())
-      return true;
     // Pass false to resume subresource loading.
     pause_callback_.Run(false);
     return true;
@@ -51,7 +52,7 @@
 
   // |pause_callback| will either pause subresource loading or resume it based
   // on the passed in bool.
-  PauseCallback pause_callback_;
+  const PauseCallback pause_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(ResumeDelegate);
 };
@@ -59,14 +60,26 @@
 // The infobar that allows the user to pause resoruce loading on the page.
 class PauseDelegate : public PageLoadCappingInfoBarDelegate {
  public:
-  // This object is destroyed wqhen the page is terminated, and methods related
-  // to functionality of the infobar (E.g., LinkClicked()), are not called from
+  // This object is destroyed when the page is terminated, and methods related
+  // to functionality of the InfoBar (E.g., LinkClicked()), are not called from
   // page destructors. This object is also destroyed on all non-same page
   // navigations.
   // |pause_callback| is a callback that will pause subresource loading on the
   // page.
-  explicit PauseDelegate(const PauseCallback& pause_callback)
-      : pause_callback_(pause_callback) {}
+  // |time_to_expire_callback| is used to get the earliest time at which the
+  // page is considered to have stopped using data.
+  explicit PauseDelegate(const PauseCallback& pause_callback,
+                         const TimeToExpireCallback& time_to_expire_callback)
+      : pause_callback_(pause_callback),
+        time_to_expire_callback_(time_to_expire_callback),
+        weak_factory_(this) {
+    // When creating the InfoBar, it should not already be expired.
+    DCHECK(!time_to_expire_callback_.is_null());
+    DCHECK(!pause_callback_.is_null());
+    base::TimeDelta time_to_expire;
+    time_to_expire_callback_.Run(&time_to_expire);
+    RunDelayedCheck(time_to_expire);
+  }
   ~PauseDelegate() override = default;
 
  private:
@@ -81,10 +94,9 @@
 
   bool LinkClicked(WindowOpenDisposition disposition) override {
     RecordInteractionUMA(InfoBarInteraction::kPausedPage);
-    if (!pause_callback_.is_null()) {
+
       // Pause subresouce loading on the page.
       pause_callback_.Run(true);
-    }
 
     auto* infobar_manager = infobar()->owner();
     // |this| will be gone after this call.
@@ -95,10 +107,41 @@
     return false;
   }
 
- private:
+  void RunDelayedCheck(base::TimeDelta time_to_expire) {
+    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE,
+        base::BindOnce(&PauseDelegate::ExpireIfNecessary,
+                       weak_factory_.GetWeakPtr()),
+        time_to_expire);
+  }
+
+  void ExpireIfNecessary() {
+    base::TimeDelta time_to_expire;
+    time_to_expire_callback_.Run(&time_to_expire);
+
+    // When the owner of |time_to_expire_callback_| is deleted, or it returns a
+    // TimeDelta of 0, the InfoBar should be deleted. Otherwise, re-evaluate
+    // after |time_to_expire|.
+    if (time_to_expire > base::TimeDelta()) {
+      RunDelayedCheck(time_to_expire);
+      return;
+    }
+
+    RecordInteractionUMA(InfoBarInteraction::kDismissedByNetworkStopped);
+    auto* infobar_manager = infobar()->owner();
+    // |this| will be gone after this call.
+    infobar_manager->RemoveInfoBar(infobar());
+  }
+
   // |pause_callback| will either pause subresource loading or resume it based
   // on the passed in bool.
-  PauseCallback pause_callback_;
+  const PauseCallback pause_callback_;
+
+  // Used to get the earliest time at which the page is considered to have
+  // stopped using data.
+  const TimeToExpireCallback time_to_expire_callback_;
+
+  base::WeakPtrFactory<PauseDelegate> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(PauseDelegate);
 };
@@ -108,12 +151,14 @@
 // static
 bool PageLoadCappingInfoBarDelegate::Create(
     content::WebContents* web_contents,
-    const PauseCallback& pause_callback) {
+    const PauseCallback& pause_callback,
+    const TimeToExpireCallback& time_to_expire_callback) {
   auto* infobar_service = InfoBarService::FromWebContents(web_contents);
   RecordInteractionUMA(InfoBarInteraction::kShowedInfoBar);
   // WrapUnique is used to allow for a private constructor.
-  return infobar_service->AddInfoBar(infobar_service->CreateConfirmInfoBar(
-      std::make_unique<PauseDelegate>(pause_callback)));
+  return infobar_service->AddInfoBar(
+      infobar_service->CreateConfirmInfoBar(std::make_unique<PauseDelegate>(
+          pause_callback, time_to_expire_callback)));
 }
 
 PageLoadCappingInfoBarDelegate::~PageLoadCappingInfoBarDelegate() = default;
diff --git a/chrome/browser/data_use_measurement/page_load_capping/page_load_capping_infobar_delegate.h b/chrome/browser/data_use_measurement/page_load_capping/page_load_capping_infobar_delegate.h
index 8b1ca4c..d5a6394 100644
--- a/chrome/browser/data_use_measurement/page_load_capping/page_load_capping_infobar_delegate.h
+++ b/chrome/browser/data_use_measurement/page_load_capping/page_load_capping_infobar_delegate.h
@@ -34,10 +34,21 @@
   // resumed based on |pause|.
   using PauseCallback = base::RepeatingCallback<void(bool pause)>;
 
+  // A callback used to get the earliest possible time (offset from now) that
+  // the InfoBar could be dismissed based on lack of network usage.
+  // |time_to_expire| must be passed in as TimeDelta initialized to 0 to handle
+  // the case of the underlying weak pointer being destroyed.
+  using TimeToExpireCallback =
+      base::RepeatingCallback<void(base::TimeDelta* time_to_expire)>;
+
   // Creates an InfoBar for page load capping. Returns whether the InfoBar was
   // created. |web_contents| is the WebContents that caused the data usage.
+  // |pause_callback| is used to pause and unpause the resource loading of the
+  // page. |time_to_expire_callback| is used to get the earliest time at which
+  // the page is considered to have stopped using data.
   static bool Create(content::WebContents* web_contents,
-                     const PauseCallback& set_handles_callback);
+                     const PauseCallback& pause_callback,
+                     const TimeToExpireCallback& time_to_expire_callback);
 
   ~PageLoadCappingInfoBarDelegate() override;
 
@@ -47,7 +58,8 @@
     kShowedInfoBar = 0,
     kPausedPage = 1,
     kResumedPage = 2,
-    kMaxValue = kResumedPage,
+    kDismissedByNetworkStopped = 3,
+    kMaxValue = kDismissedByNetworkStopped,
   };
 
  protected:
diff --git a/chrome/browser/data_use_measurement/page_load_capping/page_load_capping_infobar_delegate_unittest.cc b/chrome/browser/data_use_measurement/page_load_capping/page_load_capping_infobar_delegate_unittest.cc
index 319c4a2..0da8916 100644
--- a/chrome/browser/data_use_measurement/page_load_capping/page_load_capping_infobar_delegate_unittest.cc
+++ b/chrome/browser/data_use_measurement/page_load_capping/page_load_capping_infobar_delegate_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/data_use_measurement/page_load_capping/page_load_capping_infobar_delegate.h"
 
+#include "base/bind_helpers.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "chrome/browser/infobars/infobar_service.h"
 #include "chrome/browser/infobars/mock_infobar_service.h"
@@ -61,7 +62,8 @@
       web_contents(),
       base::BindRepeating(
           &PageLoadCappingInfoBarDelegateTest::PauseSubresourceLoading,
-          base::Unretained(this))));
+          base::Unretained(this)),
+      base::DoNothing()));
   histogram_tester.ExpectUniqueSample(
       "HeavyPageCapping.InfoBarInteraction",
       PageLoadCappingInfoBarDelegate::InfoBarInteraction::kShowedInfoBar, 1);
diff --git a/chrome/browser/devtools/devtools_eye_dropper.h b/chrome/browser/devtools/devtools_eye_dropper.h
index aaa87d2..5c0c293 100644
--- a/chrome/browser/devtools/devtools_eye_dropper.h
+++ b/chrome/browser/devtools/devtools_eye_dropper.h
@@ -12,7 +12,6 @@
 #include "components/viz/host/client_frame_sink_video_capturer.h"
 #include "content/public/browser/render_widget_host.h"
 #include "content/public/browser/web_contents_observer.h"
-#include "media/renderers/paint_canvas_video_renderer.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 
 namespace blink {
@@ -59,7 +58,6 @@
   content::RenderWidgetHost::MouseEventCallback mouse_event_callback_;
   content::RenderWidgetHost* host_;
   std::unique_ptr<viz::ClientFrameSinkVideoCapturer> video_capturer_;
-  media::PaintCanvasVideoRenderer video_renderer_;
   base::WeakPtrFactory<DevToolsEyeDropper> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(DevToolsEyeDropper);
diff --git a/chrome/browser/download/download_core_service_impl.cc b/chrome/browser/download/download_core_service_impl.cc
index 942024a..230d6b82 100644
--- a/chrome/browser/download/download_core_service_impl.cc
+++ b/chrome/browser/download/download_core_service_impl.cc
@@ -68,6 +68,10 @@
 
   download_provider_.reset(new DownloadOfflineContentProvider(manager));
 
+#if !defined(OS_ANDROID)
+  download_shelf_controller_.reset(new DownloadShelfController(profile_));
+#endif
+
   // Include this download manager in the set monitored by the
   // global status updater.
   DCHECK(g_browser_process->download_status_updater());
diff --git a/chrome/browser/download/download_core_service_impl.h b/chrome/browser/download/download_core_service_impl.h
index 3b029b0..f153ca8 100644
--- a/chrome/browser/download/download_core_service_impl.h
+++ b/chrome/browser/download/download_core_service_impl.h
@@ -13,6 +13,10 @@
 #include "components/keyed_service/core/keyed_service.h"
 #include "extensions/buildflags/buildflags.h"
 
+#if !defined(OS_ANDROID)
+#include "chrome/browser/download/download_shelf_controller.h"
+#endif
+
 class ChromeDownloadManagerDelegate;
 class DownloadHistory;
 class DownloadOfflineContentProvider;
@@ -68,6 +72,10 @@
   // should be destroyed before the latter.
   std::unique_ptr<DownloadUIController> download_ui_;
 
+#if !defined(OS_ANDROID)
+  std::unique_ptr<DownloadShelfController> download_shelf_controller_;
+#endif
+
   // The download provider is the responsible for supplying offline items to the
   // UI.
   std::unique_ptr<DownloadOfflineContentProvider> download_provider_;
diff --git a/chrome/browser/download/download_item_model.cc b/chrome/browser/download/download_item_model.cc
index 91a8eee1..9450df0 100644
--- a/chrome/browser/download/download_item_model.cc
+++ b/chrome/browser/download/download_item_model.cc
@@ -114,6 +114,15 @@
 // -----------------------------------------------------------------------------
 // DownloadItemModel
 
+// static
+DownloadUIModel::DownloadUIModelPtr DownloadItemModel::Wrap(
+    download::DownloadItem* download) {
+  DownloadUIModel::DownloadUIModelPtr model(
+      new DownloadItemModel(download),
+      base::OnTaskRunnerDeleter(base::ThreadTaskRunnerHandle::Get()));
+  return model;
+}
+
 DownloadItemModel::DownloadItemModel(DownloadItem* download)
     : download_(download) {
   download_->AddObserver(this);
diff --git a/chrome/browser/download/download_item_model.h b/chrome/browser/download/download_item_model.h
index 9b8bcc1..624abd3 100644
--- a/chrome/browser/download/download_item_model.h
+++ b/chrome/browser/download/download_item_model.h
@@ -21,6 +21,8 @@
 class DownloadItemModel : public DownloadUIModel,
                           public download::DownloadItem::Observer {
  public:
+  static DownloadUIModelPtr Wrap(download::DownloadItem* download);
+
   // Constructs a DownloadItemModel. The caller must ensure that |download|
   // outlives this object.
   explicit DownloadItemModel(download::DownloadItem* download);
diff --git a/chrome/browser/download/download_offline_content_provider.cc b/chrome/browser/download/download_offline_content_provider.cc
index 392b99e6..31d03b18 100644
--- a/chrome/browser/download/download_offline_content_provider.cc
+++ b/chrome/browser/download/download_offline_content_provider.cc
@@ -94,7 +94,7 @@
     OfflineContentProvider::SingleItemCallback callback) {
   DownloadItem* item = manager_->GetDownloadByGuid(id.id);
   auto offline_item =
-      ShouldShowDownloadItem(item)
+      item && ShouldShowDownloadItem(item)
           ? base::make_optional(OfflineItemUtils::CreateOfflineItem(item))
           : base::nullopt;
 
diff --git a/chrome/browser/download/download_shelf.cc b/chrome/browser/download/download_shelf.cc
index 4f15284..942a590b 100644
--- a/chrome/browser/download/download_shelf.cc
+++ b/chrome/browser/download/download_shelf.cc
@@ -17,12 +17,19 @@
 #include "chrome/browser/download/download_core_service_factory.h"
 #include "chrome/browser/download/download_item_model.h"
 #include "chrome/browser/download/download_started_animation.h"
+#include "chrome/browser/download/offline_item_model.h"
+#include "chrome/browser/download/offline_item_model_manager.h"
+#include "chrome/browser/download/offline_item_model_manager_factory.h"
+#include "chrome/browser/download/offline_item_utils.h"
+#include "chrome/browser/offline_items_collection/offline_content_aggregator_factory.h"
 #include "chrome/browser/platform_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "components/download/public/common/download_item.h"
+#include "components/offline_items_collection/core/offline_content_aggregator.h"
+#include "components/offline_items_collection/core/offline_item.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/download_item_utils.h"
 #include "content/public/browser/download_manager.h"
@@ -54,6 +61,47 @@
   return static_cast<int>(255.0 * temp);
 }
 
+void OnGetDownloadDoneForOfflineItem(
+    Profile* profile,
+    base::OnceCallback<void(DownloadUIModelPtr)> callback,
+    const base::Optional<OfflineItem>& offline_item) {
+  if (!offline_item.has_value())
+    return;
+
+  OfflineItemModelManager* manager =
+      OfflineItemModelManagerFactory::GetForBrowserContext(profile);
+  DownloadUIModelPtr model =
+      OfflineItemModel::Wrap(manager, offline_item.value());
+
+  std::move(callback).Run(std::move(model));
+}
+
+void GetDownload(Profile* profile,
+                 ContentId id,
+                 base::OnceCallback<void(DownloadUIModelPtr)> callback) {
+  if (OfflineItemUtils::IsDownload(id)) {
+    content::DownloadManager* download_manager =
+        content::BrowserContext::GetDownloadManager(profile);
+    if (!download_manager)
+      return;
+
+    DownloadItem* download = download_manager->GetDownloadByGuid(id.id);
+    if (!download)
+      return;
+
+    DownloadUIModelPtr model = DownloadItemModel::Wrap(download);
+    std::move(callback).Run(std::move(model));
+  } else {
+    offline_items_collection::OfflineContentAggregator* aggregator =
+        OfflineContentAggregatorFactory::GetForBrowserContext(profile);
+    if (!aggregator)
+      return;
+
+    aggregator->GetItemById(id, base::BindOnce(&OnGetDownloadDoneForOfflineItem,
+                                               profile, std::move(callback)));
+  }
+}
+
 } // namespace
 
 DownloadShelf::DownloadShelf()
@@ -132,19 +180,19 @@
   PaintDownloadComplete(canvas, theme_provider, 1.0 - animation_progress);
 }
 
-void DownloadShelf::AddDownload(DownloadItem* download) {
-  DCHECK(download);
-  if (DownloadItemModel(download).ShouldRemoveFromShelfWhenComplete()) {
+void DownloadShelf::AddDownload(DownloadUIModelPtr model) {
+  DCHECK(model);
+  if (model->ShouldRemoveFromShelfWhenComplete()) {
     // If we are going to remove the download from the shelf upon completion,
     // wait a few seconds to see if it completes quickly. If it's a small
     // download, then the user won't have time to interact with it.
     base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
         FROM_HERE,
         base::BindOnce(&DownloadShelf::ShowDownloadById,
-                       weak_ptr_factory_.GetWeakPtr(), download->GetId()),
+                       weak_ptr_factory_.GetWeakPtr(), model->GetContentId()),
         GetTransientDownloadShowDelay());
   } else {
-    ShowDownload(download);
+    ShowDownload(std::move(model));
   }
 }
 
@@ -188,23 +236,26 @@
   return base::TimeDelta::FromSeconds(kDownloadShowDelayInSeconds);
 }
 
-content::DownloadManager* DownloadShelf::GetDownloadManager() {
-  return content::BrowserContext::GetDownloadManager(browser()->profile());
+Profile* DownloadShelf::profile() const {
+  return browser() ? browser()->profile() : nullptr;
 }
 
-void DownloadShelf::ShowDownload(DownloadItem* download) {
+void DownloadShelf::ShowDownload(DownloadUIModelPtr download) {
   if (download->GetState() == DownloadItem::COMPLETE &&
-      DownloadItemModel(download).ShouldRemoveFromShelfWhenComplete())
+      download->ShouldRemoveFromShelfWhenComplete())
     return;
-  if (!DownloadCoreServiceFactory::GetForBrowserContext(
-           content::DownloadItemUtils::GetBrowserContext(download))
+
+  if (!DownloadCoreServiceFactory::GetForBrowserContext(download->profile())
            ->IsShelfEnabled())
     return;
 
+  bool should_show_download_started_animation =
+      download->ShouldShowDownloadStartedAnimation();
+
   if (is_hidden_)
     Unhide();
   Open();
-  DoAddDownload(download);
+  DoAddDownload(std::move(download));
 
   // browser() can be NULL for tests.
   if (!browser())
@@ -218,22 +269,15 @@
   // - Rich animations are enabled.
   content::WebContents* shelf_tab =
       browser()->tab_strip_model()->GetActiveWebContents();
-  if (DownloadItemModel(download).ShouldShowDownloadStartedAnimation() &&
-      shelf_tab &&
+  if (should_show_download_started_animation && shelf_tab &&
       platform_util::IsVisible(shelf_tab->GetNativeView()) &&
       gfx::Animation::ShouldRenderRichAnimation()) {
     DownloadStartedAnimation::Show(shelf_tab);
   }
 }
 
-void DownloadShelf::ShowDownloadById(int32_t download_id) {
-  content::DownloadManager* download_manager = GetDownloadManager();
-  if (!download_manager)
-    return;
-
-  DownloadItem* download = download_manager->GetDownload(download_id);
-  if (!download)
-    return;
-
-  ShowDownload(download);
+void DownloadShelf::ShowDownloadById(ContentId id) {
+  GetDownload(profile(), id,
+              base::BindOnce(&DownloadShelf::ShowDownload,
+                             weak_ptr_factory_.GetWeakPtr()));
 }
diff --git a/chrome/browser/download/download_shelf.h b/chrome/browser/download/download_shelf.h
index 4df9e43..33528535 100644
--- a/chrome/browser/download/download_shelf.h
+++ b/chrome/browser/download/download_shelf.h
@@ -10,6 +10,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "chrome/browser/download/download_ui_model.h"
 
 class Browser;
 
@@ -17,18 +18,14 @@
 class Canvas;
 }
 
-namespace content {
-class DownloadManager;
-}
-
-namespace download {
-class DownloadItem;
-}
-
 namespace ui {
 class ThemeProvider;
 }
 
+using offline_items_collection::ContentId;
+using offline_items_collection::OfflineItem;
+using DownloadUIModelPtr = DownloadUIModel::DownloadUIModelPtr;
+
 // This is an abstract base class for platform specific download shelf
 // implementations.
 class DownloadShelf {
@@ -86,7 +83,7 @@
   // DownloadItemModel::ShouldRemoveFromShelfWhenComplete()). These transient
   // downloads are added to the shelf after a delay. If the download completes
   // before the delay duration, it will not be added to the shelf at all.
-  void AddDownload(download::DownloadItem* download);
+  void AddDownload(DownloadUIModelPtr download);
 
   // The browser view needs to know when we are going away to properly return
   // the resize corner size to WebKit so that we don't draw on top of it.
@@ -116,7 +113,7 @@
   bool is_hidden() { return is_hidden_; }
 
  protected:
-  virtual void DoAddDownload(download::DownloadItem* download) = 0;
+  virtual void DoAddDownload(DownloadUIModelPtr download) = 0;
   virtual void DoOpen() = 0;
   virtual void DoClose(CloseReason reason) = 0;
   virtual void DoHide() = 0;
@@ -126,20 +123,16 @@
   // Protected virtual for testing.
   virtual base::TimeDelta GetTransientDownloadShowDelay();
 
-  // Returns the DownloadManager associated with this DownloadShelf. All
-  // downloads that are shown on this shelf is expected to belong to this
-  // DownloadManager. Protected virtual for testing.
-  virtual content::DownloadManager* GetDownloadManager();
+  // Virtual for testing.
+  virtual Profile* profile() const;
 
  private:
   // Show the download on the shelf immediately. Also displayes the download
   // started animation if necessary.
-  void ShowDownload(download::DownloadItem* download);
+  void ShowDownload(DownloadUIModelPtr download);
 
-  // Similar to ShowDownload() but refers to the download using an ID. This
-  // download should belong to the DownloadManager returned by
-  // GetDownloadManager().
-  void ShowDownloadById(int32_t download_id);
+  // Similar to ShowDownload() but refers to the download using an ID.
+  void ShowDownloadById(ContentId id);
 
   bool should_show_on_unhide_;
   bool is_hidden_;
diff --git a/chrome/browser/download/download_shelf_controller.cc b/chrome/browser/download/download_shelf_controller.cc
index 38eea15..1685aaa 100644
--- a/chrome/browser/download/download_shelf_controller.cc
+++ b/chrome/browser/download/download_shelf_controller.cc
@@ -4,18 +4,28 @@
 
 #include "chrome/browser/download/download_shelf_controller.h"
 
+#include "chrome/browser/download/download_shelf.h"
 #include "chrome/browser/download/offline_item_model_manager.h"
 #include "chrome/browser/download/offline_item_model_manager_factory.h"
+#include "chrome/browser/download/offline_item_utils.h"
+#include "chrome/browser/offline_items_collection/offline_content_aggregator_factory.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_window.h"
+#include "components/offline_items_collection/core/offline_content_aggregator.h"
 #include "components/offline_items_collection/core/offline_item.h"
 
 using offline_items_collection::OfflineItemState;
+using offline_items_collection::OfflineContentAggregator;
 
 DownloadShelfController::DownloadShelfController(Profile* profile)
-    : profile_(profile) {}
+    : profile_(profile) {
+  aggregator_ = OfflineContentAggregatorFactory::GetForBrowserContext(profile_);
+  aggregator_->AddObserver(this);
+}
 
-DownloadShelfController::~DownloadShelfController() = default;
+DownloadShelfController::~DownloadShelfController() {
+  aggregator_->RemoveObserver(this);
+}
 
 void DownloadShelfController::OnItemsAdded(
     const OfflineContentProvider::OfflineItemList& items) {
@@ -24,28 +34,38 @@
 }
 
 void DownloadShelfController::OnItemRemoved(const ContentId& id) {
+  if (OfflineItemUtils::IsDownload(id))
+    return;
+
   OfflineItemModelManagerFactory::GetForBrowserContext(profile_)
       ->RemoveOfflineItemModelData(id);
 }
 
 void DownloadShelfController::OnItemUpdated(const OfflineItem& item) {
+  if (OfflineItemUtils::IsDownload(item.id))
+    return;
+
   if (item.state == OfflineItemState::CANCELLED)
     return;
 
   OfflineItemModelManager* manager =
       OfflineItemModelManagerFactory::GetForBrowserContext(profile_);
-  OfflineItemModel model(manager, item);
-  if (!model.WasUINotified()) {
-    model.SetWasUINotified(true);
-    OnNewOfflineItemReady(model);
+
+  DownloadUIModel::DownloadUIModelPtr model =
+      OfflineItemModel::Wrap(manager, item);
+
+  if (!model->WasUINotified()) {
+    model->SetWasUINotified(true);
+    OnNewOfflineItemReady(std::move(model));
   }
 }
 
 void DownloadShelfController::OnNewOfflineItemReady(
-    const OfflineItemModel& item) {
+    DownloadUIModel::DownloadUIModelPtr model) {
   Browser* browser = browser = chrome::FindLastActiveWithProfile(profile_);
 
   if (browser && browser->window()) {
     // Add the offline item to DownloadShelf in the browser window.
+    browser->window()->GetDownloadShelf()->AddDownload(std::move(model));
   }
 }
diff --git a/chrome/browser/download/download_shelf_controller.h b/chrome/browser/download/download_shelf_controller.h
index 6634e42087..502b987 100644
--- a/chrome/browser/download/download_shelf_controller.h
+++ b/chrome/browser/download/download_shelf_controller.h
@@ -9,12 +9,15 @@
 
 #include "base/macros.h"
 #include "chrome/browser/download/offline_item_model.h"
+#include "components/offline_items_collection/core/offline_content_aggregator.h"
 #include "components/offline_items_collection/core/offline_content_provider.h"
 
 class Profile;
 
 using ContentId = offline_items_collection::ContentId;
 using OfflineContentProvider = offline_items_collection::OfflineContentProvider;
+using OfflineContentAggregator =
+    offline_items_collection::OfflineContentAggregator;
 using OfflineItem = offline_items_collection::OfflineItem;
 
 // Class for notifying UI when an OfflineItem should be displayed.
@@ -31,9 +34,10 @@
   void OnItemUpdated(const OfflineItem& item) override;
 
   // Called when a new OfflineItem is to be displayed on UI.
-  void OnNewOfflineItemReady(const OfflineItemModel& item);
+  void OnNewOfflineItemReady(DownloadUIModel::DownloadUIModelPtr model);
 
   Profile* profile_;
+  OfflineContentAggregator* aggregator_;
 
   DISALLOW_COPY_AND_ASSIGN(DownloadShelfController);
 };
diff --git a/chrome/browser/download/download_shelf_unittest.cc b/chrome/browser/download/download_shelf_unittest.cc
index 04b3500..72c22f2 100644
--- a/chrome/browser/download/download_shelf_unittest.cc
+++ b/chrome/browser/download/download_shelf_unittest.cc
@@ -34,13 +34,17 @@
   DownloadShelfTest();
 
  protected:
-  download::MockDownloadItem* download_item() { return download_item_.get(); }
+  DownloadUIModelPtr model() {
+    DownloadUIModelPtr model(
+        new DownloadItemModel(download_item_.get()),
+        base::OnTaskRunnerDeleter(base::ThreadTaskRunnerHandle::Get()));
+    return model;
+  }
+
   content::MockDownloadManager* download_manager() {
     return download_manager_.get();
   }
-  TestDownloadShelf* shelf() {
-    return &shelf_;
-  }
+  TestDownloadShelf* shelf() { return &shelf_; }
   Profile* profile() { return profile_.get(); }
 
   void SetUp() override {
@@ -49,7 +53,7 @@
   void TearDown() override {
   }
 
- private:
+ protected:
   std::unique_ptr<download::MockDownloadItem> GetInProgressMockDownload();
 
   content::TestBrowserThreadBundle test_browser_thread_bundle_;
@@ -61,6 +65,8 @@
 
 DownloadShelfTest::DownloadShelfTest() : profile_(new TestingProfile()) {
   download_item_.reset(new ::testing::NiceMock<download::MockDownloadItem>());
+  ON_CALL(*download_item_, GetGuid())
+      .WillByDefault(ReturnRefOfCopy(std::string("TEST_GUID")));
   ON_CALL(*download_item_, GetAutoOpened()).WillByDefault(Return(false));
   ON_CALL(*download_item_, GetMimeType()).WillByDefault(Return("text/plain"));
   ON_CALL(*download_item_, GetOpenWhenComplete()).WillByDefault(Return(false));
@@ -78,12 +84,14 @@
 
   download_manager_.reset(
       new ::testing::NiceMock<content::MockDownloadManager>());
-  ON_CALL(*download_manager_, GetDownload(_))
+  ON_CALL(*download_manager_, GetDownloadByGuid(_))
       .WillByDefault(Return(download_item_.get()));
   ON_CALL(*download_manager_, GetBrowserContext())
       .WillByDefault(Return(profile()));
 
-  shelf_.set_download_manager(download_manager_.get());
+  content::BrowserContext::SetDownloadManagerForTesting(
+      profile_.get(), std::move(download_manager_));
+  shelf_.set_profile(profile_.get());
 }
 
 } // namespace
@@ -114,20 +122,20 @@
 TEST_F(DownloadShelfTest, AddDownloadWhileHiddenUnhides) {
   shelf()->Open();
   shelf()->Hide();
-  shelf()->AddDownload(download_item());
+  shelf()->AddDownload(model());
   EXPECT_TRUE(shelf()->IsShowing());
 }
 
 TEST_F(DownloadShelfTest, AddDownloadWhileHiddenUnhidesAndShows) {
   shelf()->Hide();
-  shelf()->AddDownload(download_item());
+  shelf()->AddDownload(model());
   EXPECT_TRUE(shelf()->IsShowing());
 }
 
 // Normal downloads should be added synchronously and cause the shelf to show.
 TEST_F(DownloadShelfTest, AddNormalDownload) {
   EXPECT_FALSE(shelf()->IsShowing());
-  shelf()->AddDownload(download_item());
+  shelf()->AddDownload(model());
   EXPECT_TRUE(shelf()->did_add_download());
   EXPECT_TRUE(shelf()->IsShowing());
 }
@@ -136,68 +144,35 @@
 // should be added after a delay. For testing, the delay is set to 0 seconds. So
 // the download should be added once the message loop is flushed.
 TEST_F(DownloadShelfTest, AddDelayedDownload) {
-  EXPECT_CALL(*download_item(), ShouldOpenFileBasedOnExtension())
-      .WillRepeatedly(Return(true));
-  ASSERT_TRUE(DownloadItemModel(download_item())
-              .ShouldRemoveFromShelfWhenComplete());
-  shelf()->AddDownload(download_item());
+  ON_CALL(*download_item_, ShouldOpenFileBasedOnExtension())
+      .WillByDefault(Return(true));
+  ASSERT_TRUE(model()->ShouldRemoveFromShelfWhenComplete());
+  shelf()->AddDownload(model());
 
   EXPECT_FALSE(shelf()->did_add_download());
   EXPECT_FALSE(shelf()->IsShowing());
 
-  base::RunLoop run_loop;
-  run_loop.RunUntilIdle();
-
-  EXPECT_TRUE(shelf()->did_add_download());
-  EXPECT_TRUE(shelf()->IsShowing());
+  base::RunLoop().RunUntilIdle();
 }
 
 // Add a transient download that completes before the delay. It should not be
 // displayed on the shelf.
 TEST_F(DownloadShelfTest, AddDelayedCompletedDownload) {
-  EXPECT_CALL(*download_item(), ShouldOpenFileBasedOnExtension())
-      .WillRepeatedly(Return(true));
-  ASSERT_TRUE(DownloadItemModel(download_item())
-              .ShouldRemoveFromShelfWhenComplete());
-  shelf()->AddDownload(download_item());
+  ON_CALL(*download_item_, ShouldOpenFileBasedOnExtension())
+      .WillByDefault(Return(true));
+  ASSERT_TRUE(model()->ShouldRemoveFromShelfWhenComplete());
+  ON_CALL(*download_item_, IsTemporary()).WillByDefault(Return(true));
+  shelf()->AddDownload(model());
 
   EXPECT_FALSE(shelf()->did_add_download());
   EXPECT_FALSE(shelf()->IsShowing());
 
-  EXPECT_CALL(*download_item(), GetState())
-      .WillRepeatedly(Return(DownloadItem::COMPLETE));
-  EXPECT_CALL(*download_item(), GetAutoOpened())
-      .WillRepeatedly(Return(true));
+  ON_CALL(*download_item_, GetState())
+      .WillByDefault(Return(DownloadItem::COMPLETE));
+  ON_CALL(*download_item_, GetAutoOpened()).WillByDefault(Return(true));
 
-  base::RunLoop run_loop;
-  run_loop.RunUntilIdle();
+  base::RunLoop().RunUntilIdle();
 
   EXPECT_FALSE(shelf()->did_add_download());
   EXPECT_FALSE(shelf()->IsShowing());
 }
-
-// Add a transient download that completes and becomes non-transient before the
-// delay. It should be displayed on the shelf even though it is complete.
-TEST_F(DownloadShelfTest, AddDelayedCompleteNonTransientDownload) {
-  EXPECT_CALL(*download_item(), ShouldOpenFileBasedOnExtension())
-      .WillRepeatedly(Return(true));
-  ASSERT_TRUE(DownloadItemModel(download_item())
-              .ShouldRemoveFromShelfWhenComplete());
-  shelf()->AddDownload(download_item());
-
-  EXPECT_FALSE(shelf()->did_add_download());
-  EXPECT_FALSE(shelf()->IsShowing());
-
-  EXPECT_CALL(*download_item(), GetState())
-      .WillRepeatedly(Return(DownloadItem::COMPLETE));
-  EXPECT_CALL(*download_item(), ShouldOpenFileBasedOnExtension())
-      .WillRepeatedly(Return(false));
-  ASSERT_FALSE(DownloadItemModel(download_item())
-               .ShouldRemoveFromShelfWhenComplete());
-
-  base::RunLoop run_loop;
-  run_loop.RunUntilIdle();
-
-  EXPECT_TRUE(shelf()->did_add_download());
-  EXPECT_TRUE(shelf()->IsShowing());
-}
diff --git a/chrome/browser/download/download_ui_controller.cc b/chrome/browser/download/download_ui_controller.cc
index 15279d7..8150809 100644
--- a/chrome/browser/download/download_ui_controller.cc
+++ b/chrome/browser/download/download_ui_controller.cc
@@ -98,8 +98,10 @@
 
   if (browser && browser->window() &&
       DownloadItemModel(item).ShouldShowInShelf()) {
+    DownloadUIModel::DownloadUIModelPtr model = DownloadItemModel::Wrap(item);
+
     // GetDownloadShelf creates the download shelf if it was not yet created.
-    browser->window()->GetDownloadShelf()->AddDownload(item);
+    browser->window()->GetDownloadShelf()->AddDownload(std::move(model));
   }
 }
 
diff --git a/chrome/browser/download/offline_item_model.cc b/chrome/browser/download/offline_item_model.cc
index c08e04d..25dee33 100644
--- a/chrome/browser/download/offline_item_model.cc
+++ b/chrome/browser/download/offline_item_model.cc
@@ -15,6 +15,16 @@
 using offline_items_collection::OfflineItem;
 using offline_items_collection::OfflineItemState;
 
+// static
+DownloadUIModel::DownloadUIModelPtr OfflineItemModel::Wrap(
+    OfflineItemModelManager* manager,
+    const OfflineItem& offline_item) {
+  DownloadUIModel::DownloadUIModelPtr model(
+      new OfflineItemModel(manager, offline_item),
+      base::OnTaskRunnerDeleter(base::ThreadTaskRunnerHandle::Get()));
+  return model;
+}
+
 OfflineItemModel::OfflineItemModel(OfflineItemModelManager* manager,
                                    const OfflineItem& offline_item)
     : manager_(manager),
@@ -32,6 +42,14 @@
     offline_item_observer_->RemoveObserver(offline_item_->id, this);
 }
 
+Profile* OfflineItemModel::profile() const {
+  return Profile::FromBrowserContext(manager_->browser_context());
+}
+
+ContentId OfflineItemModel::GetContentId() const {
+  return offline_item_ ? offline_item_->id : ContentId();
+}
+
 int64_t OfflineItemModel::GetCompletedBytes() const {
   return offline_item_ ? offline_item_->received_bytes : 0;
 }
@@ -66,6 +84,43 @@
   return offline_item_ ? offline_item_->file_path : base::FilePath();
 }
 
+void OfflineItemModel::OpenDownload() {
+  if (!offline_item_)
+    return;
+
+  GetProvider()->OpenItem(
+      offline_items_collection::LaunchLocation::DOWNLOAD_SHELF,
+      offline_item_->id);
+}
+
+void OfflineItemModel::Pause() {
+  if (!offline_item_)
+    return;
+
+  GetProvider()->PauseDownload(offline_item_->id);
+}
+
+void OfflineItemModel::Resume() {
+  if (!offline_item_)
+    return;
+
+  GetProvider()->ResumeDownload(offline_item_->id, true /* has_user_gesture */);
+}
+
+void OfflineItemModel::Cancel(bool user_cancel) {
+  if (!offline_item_)
+    return;
+
+  GetProvider()->CancelDownload(offline_item_->id);
+}
+
+void OfflineItemModel::Remove() {
+  if (!offline_item_)
+    return;
+
+  GetProvider()->RemoveItem(offline_item_->id);
+}
+
 download::DownloadItem::DownloadState OfflineItemModel::GetState() const {
   if (!offline_item_)
     return download::DownloadItem::CANCELLED;
@@ -91,9 +146,8 @@
 }
 
 bool OfflineItemModel::IsPaused() const {
-  return offline_item_
-             ? offline_item_->state == offline_items_collection::PAUSED
-             : true;
+  return offline_item_ ? offline_item_->state == OfflineItemState::PAUSED
+                       : true;
 }
 
 bool OfflineItemModel::TimeRemaining(base::TimeDelta* remaining) const {
@@ -162,6 +216,18 @@
   return offline_item_ ? offline_item_->page_url : GURL();
 }
 
+bool OfflineItemModel::ShouldRemoveFromShelfWhenComplete() const {
+  // TODO(shaktisahu): Add more appropriate logic.
+  return false;
+}
+
+OfflineContentProvider* OfflineItemModel::GetProvider() const {
+  offline_items_collection::OfflineContentAggregator* aggregator =
+      OfflineContentAggregatorFactory::GetForBrowserContext(
+          manager_->browser_context());
+  return aggregator;
+}
+
 void OfflineItemModel::OnItemRemoved(const ContentId& id) {
   for (auto& obs : observers_)
     obs.OnDownloadDestroyed();
diff --git a/chrome/browser/download/offline_item_model.h b/chrome/browser/download/offline_item_model.h
index 4aa64b9..4a6c3c9 100644
--- a/chrome/browser/download/offline_item_model.h
+++ b/chrome/browser/download/offline_item_model.h
@@ -9,28 +9,42 @@
 
 #include "chrome/browser/download/download_ui_model.h"
 #include "components/offline_items_collection/core/filtered_offline_item_observer.h"
+#include "components/offline_items_collection/core/offline_content_provider.h"
 #include "components/offline_items_collection/core/offline_item.h"
 
 class OfflineItemModelManager;
 
 using offline_items_collection::FilteredOfflineItemObserver;
+using offline_items_collection::OfflineContentProvider;
+using offline_items_collection::OfflineItem;
+using offline_items_collection::ContentId;
 
 // Implementation of DownloadUIModel that wrappers around a |OfflineItem|.
 class OfflineItemModel : public DownloadUIModel,
                          public FilteredOfflineItemObserver::Observer {
  public:
+  static DownloadUIModelPtr Wrap(OfflineItemModelManager* manager,
+                                 const OfflineItem& offline_item);
+
   // Constructs a OfflineItemModel.
   OfflineItemModel(OfflineItemModelManager* manager,
-                   const offline_items_collection::OfflineItem& offline_item);
+                   const OfflineItem& offline_item);
   ~OfflineItemModel() override;
 
   // DownloadUIModel implementation.
+  Profile* profile() const override;
+  ContentId GetContentId() const override;
   int64_t GetCompletedBytes() const override;
   int64_t GetTotalBytes() const override;
   int PercentComplete() const override;
   bool WasUINotified() const override;
   void SetWasUINotified(bool should_notify) override;
   base::FilePath GetTargetFilePath() const override;
+  void OpenDownload() override;
+  void Pause() override;
+  void Resume() override;
+  void Cancel(bool user_cancel) override;
+  void Remove() override;
   download::DownloadItem::DownloadState GetState() const override;
   bool IsPaused() const override;
   bool TimeRemaining(base::TimeDelta* remaining) const override;
@@ -41,6 +55,7 @@
   bool AllDataSaved() const override;
   bool GetFileExternallyRemoved() const override;
   GURL GetURL() const override;
+  bool ShouldRemoveFromShelfWhenComplete() const override;
 
 #if !defined(OS_ANDROID)
   bool IsCommandEnabled(const DownloadCommands* download_commands,
@@ -52,10 +67,11 @@
 #endif
 
  private:
+  OfflineContentProvider* GetProvider() const;
+
   // FilteredOfflineItemObserver::Observer overrides.
-  void OnItemRemoved(const offline_items_collection::ContentId& id) override;
-  void OnItemUpdated(
-      const offline_items_collection::OfflineItem& item) override;
+  void OnItemRemoved(const ContentId& id) override;
+  void OnItemUpdated(const OfflineItem& item) override;
 
   // DownloadUIModel implementation.
   std::string GetMimeType() const override;
@@ -63,7 +79,7 @@
   OfflineItemModelManager* manager_;
 
   std::unique_ptr<FilteredOfflineItemObserver> offline_item_observer_;
-  std::unique_ptr<offline_items_collection::OfflineItem> offline_item_;
+  std::unique_ptr<OfflineItem> offline_item_;
 
   DISALLOW_COPY_AND_ASSIGN(OfflineItemModel);
 };
diff --git a/chrome/browser/download/offline_item_model_manager_factory.cc b/chrome/browser/download/offline_item_model_manager_factory.cc
index 0c70fce..b84e313 100644
--- a/chrome/browser/download/offline_item_model_manager_factory.cc
+++ b/chrome/browser/download/offline_item_model_manager_factory.cc
@@ -6,6 +6,7 @@
 
 #include "base/memory/singleton.h"
 #include "chrome/browser/download/offline_item_model_manager.h"
+#include "chrome/browser/profiles/incognito_helpers.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "content/public/browser/browser_context.h"
 
@@ -32,3 +33,8 @@
     content::BrowserContext* context) const {
   return new OfflineItemModelManager(context);
 }
+
+content::BrowserContext* OfflineItemModelManagerFactory::GetBrowserContextToUse(
+    content::BrowserContext* context) const {
+  return chrome::GetBrowserContextOwnInstanceInIncognito(context);
+}
diff --git a/chrome/browser/download/offline_item_model_manager_factory.h b/chrome/browser/download/offline_item_model_manager_factory.h
index a77b95c..9bddef1 100644
--- a/chrome/browser/download/offline_item_model_manager_factory.h
+++ b/chrome/browser/download/offline_item_model_manager_factory.h
@@ -42,6 +42,8 @@
   // BrowserContextKeyedServiceFactory implementation.
   KeyedService* BuildServiceInstanceFor(
       content::BrowserContext* context) const override;
+  content::BrowserContext* GetBrowserContextToUse(
+      content::BrowserContext* context) const override;
 
   DISALLOW_COPY_AND_ASSIGN(OfflineItemModelManagerFactory);
 };
diff --git a/chrome/browser/download/offline_item_utils.cc b/chrome/browser/download/offline_item_utils.cc
index f9222148..5aadb2a 100644
--- a/chrome/browser/download/offline_item_utils.cc
+++ b/chrome/browser/download/offline_item_utils.cc
@@ -25,6 +25,9 @@
 // The namespace for incognito downloads.
 const char kDownloadIncognitoNamespace[] = "LEGACY_DOWNLOAD_INCOGNITO";
 
+// Prefix that all download namespaces should start with.
+const char kDownloadNamespacePrefix[] = "LEGACY_DOWNLOAD";
+
 // The remaining time for a download item if it cannot be calculated.
 constexpr int64_t kUnknownRemainingTime = -1;
 
@@ -121,3 +124,7 @@
 std::string OfflineItemUtils::GetDownloadNamespace(bool is_off_the_record) {
   return is_off_the_record ? kDownloadIncognitoNamespace : kDownloadNamespace;
 }
+
+bool OfflineItemUtils::IsDownload(const ContentId& id) {
+  return id.name_space.find(kDownloadNamespacePrefix) != std::string::npos;
+}
diff --git a/chrome/browser/download/offline_item_utils.h b/chrome/browser/download/offline_item_utils.h
index 5821192..a72f857 100644
--- a/chrome/browser/download/offline_item_utils.h
+++ b/chrome/browser/download/offline_item_utils.h
@@ -21,6 +21,8 @@
 
   static std::string GetDownloadNamespace(bool is_off_the_record);
 
+  static bool IsDownload(const offline_items_collection::ContentId& id);
+
  private:
   DISALLOW_COPY_AND_ASSIGN(OfflineItemUtils);
 };
diff --git a/chrome/browser/download/test_download_shelf.cc b/chrome/browser/download/test_download_shelf.cc
index 077acaf..48302c2 100644
--- a/chrome/browser/download/test_download_shelf.cc
+++ b/chrome/browser/download/test_download_shelf.cc
@@ -7,14 +7,9 @@
 #include "content/public/browser/download_manager.h"
 
 TestDownloadShelf::TestDownloadShelf()
-    : is_showing_(false),
-      did_add_download_(false),
-      download_manager_(NULL) {
-}
+    : is_showing_(false), did_add_download_(false), profile_(nullptr) {}
 
 TestDownloadShelf::~TestDownloadShelf() {
-  if (download_manager_)
-    download_manager_->RemoveObserver(this);
 }
 
 bool TestDownloadShelf::IsShowing() const {
@@ -29,21 +24,7 @@
   return NULL;
 }
 
-void TestDownloadShelf::set_download_manager(
-    content::DownloadManager* download_manager) {
-  if (download_manager_)
-    download_manager_->RemoveObserver(this);
-  download_manager_ = download_manager;
-  if (download_manager_)
-    download_manager_->AddObserver(this);
-}
-
-void TestDownloadShelf::ManagerGoingDown(content::DownloadManager* manager) {
-  DCHECK_EQ(manager, download_manager_);
-  download_manager_ = NULL;
-}
-
-void TestDownloadShelf::DoAddDownload(download::DownloadItem* download) {
+void TestDownloadShelf::DoAddDownload(DownloadUIModelPtr download) {
   did_add_download_ = true;
 }
 
@@ -67,6 +48,6 @@
   return base::TimeDelta();
 }
 
-content::DownloadManager* TestDownloadShelf::GetDownloadManager() {
-  return download_manager_;
+Profile* TestDownloadShelf::profile() const {
+  return profile_;
 }
diff --git a/chrome/browser/download/test_download_shelf.h b/chrome/browser/download/test_download_shelf.h
index 5f81c25..4c877eae 100644
--- a/chrome/browser/download/test_download_shelf.h
+++ b/chrome/browser/download/test_download_shelf.h
@@ -12,8 +12,7 @@
 #include "content/public/browser/download_manager.h"
 
 // An implementation of DownloadShelf for testing.
-class TestDownloadShelf : public DownloadShelf,
-                          public content::DownloadManager::Observer {
+class TestDownloadShelf : public DownloadShelf {
  public:
   TestDownloadShelf();
   ~TestDownloadShelf() override;
@@ -26,25 +25,22 @@
   // Return |true| if a download was added to this shelf.
   bool did_add_download() const { return did_add_download_; }
 
-  // Set download_manager_ (and the result of calling GetDownloadManager())
-  void set_download_manager(content::DownloadManager* download_manager);
-
-  // DownloadManager::Observer implementation.
-  void ManagerGoingDown(content::DownloadManager* manager) override;
+  // Set a profile.
+  void set_profile(Profile* profile) { profile_ = profile; }
 
  protected:
-  void DoAddDownload(download::DownloadItem* download) override;
+  void DoAddDownload(DownloadUIModelPtr download) override;
   void DoOpen() override;
   void DoClose(CloseReason reason) override;
   void DoHide() override;
   void DoUnhide() override;
   base::TimeDelta GetTransientDownloadShowDelay() override;
-  content::DownloadManager* GetDownloadManager() override;
+  Profile* profile() const override;
 
  private:
   bool is_showing_;
   bool did_add_download_;
-  content::DownloadManager* download_manager_;
+  Profile* profile_;
 
   DISALLOW_COPY_AND_ASSIGN(TestDownloadShelf);
 };
diff --git a/chrome/browser/extensions/api/declarative_net_request/ruleset_manager_unittest.cc b/chrome/browser/extensions/api/declarative_net_request/ruleset_manager_unittest.cc
index 84aa3eb..740ea3e 100644
--- a/chrome/browser/extensions/api/declarative_net_request/ruleset_manager_unittest.cc
+++ b/chrome/browser/extensions/api/declarative_net_request/ruleset_manager_unittest.cc
@@ -519,6 +519,68 @@
   }
 }
 
+TEST_P(RulesetManagerTest, HostPermissionForInitiator) {
+  RulesetManager* manager = info_map()->GetRulesetManager();
+  ASSERT_TRUE(manager);
+
+  // Add an extension which blocks all sub-resource and sub-frame requests to
+  // example.com. By default, the "main_frame" type is excluded if no
+  // "resource_types" are specified.
+  {
+    std::unique_ptr<RulesetMatcher> matcher;
+
+    TestRule rule = CreateGenericRule();
+    rule.id = kMinValidID;
+    rule.condition->url_filter = std::string("example.com");
+
+    std::vector<std::string> host_permissions = {"*://yahoo.com/*",
+                                                 "*://example.com/*"};
+
+    ASSERT_NO_FATAL_FAILURE(CreateMatcherForRules(
+        {rule}, "test extension", &matcher, host_permissions,
+        false /* has_background_script */));
+
+    manager->AddRuleset(last_loaded_extension()->id(), std::move(matcher),
+                        URLPatternSet());
+  }
+
+  struct {
+    std::string url;
+    base::Optional<url::Origin> initiator;
+    bool expect_blocked;
+  } cases[] = {
+      // empty initiator. Has access.
+      {"https://example.com", base::nullopt, true},
+      // Opaque origin as initiator. Has access.
+      {"https://example.com", url::Origin(), true},
+      // yahoo.com as initiator. Has access.
+      {"https://example.com", url::Origin::Create(GURL("http://yahoo.com")),
+       true},
+      // No matching rule.
+      {"https://yahoo.com", url::Origin::Create(GURL("http://example.com")),
+       false},
+      // Doesn't have access to initiator.
+      {"https://example.com", url::Origin::Create(GURL("http://google.com")),
+       false},
+  };
+
+  for (const auto& test : cases) {
+    SCOPED_TRACE(base::StringPrintf(
+        "Url-%s initiator-%s", test.url.c_str(),
+        test.initiator ? test.initiator->Serialize().c_str() : "empty"));
+
+    WebRequestInfo request = GetRequestForURL(test.url);
+    request.initiator = test.initiator;
+    GURL redirect_url;
+
+    RulesetManager::Action action = manager->EvaluateRequest(
+        request, false /* is_incognito_context */, &redirect_url);
+    EXPECT_EQ(test.expect_blocked ? RulesetManager::Action::BLOCK
+                                  : RulesetManager::Action::NONE,
+              action);
+  }
+}
+
 INSTANTIATE_TEST_CASE_P(,
                         RulesetManagerTest,
                         ::testing::Values(ExtensionLoadType::PACKED,
diff --git a/chrome/browser/extensions/api/permissions/permissions_api_unittest.cc b/chrome/browser/extensions/api/permissions/permissions_api_unittest.cc
index a365158..22a7f14 100644
--- a/chrome/browser/extensions/api/permissions/permissions_api_unittest.cc
+++ b/chrome/browser/extensions/api/permissions/permissions_api_unittest.cc
@@ -2,16 +2,22 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "base/json/json_reader.h"
 #include "chrome/browser/extensions/api/permissions/permissions_api.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/extensions/extension_api_unittest.h"
 #include "chrome/browser/extensions/extension_function_test_utils.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_service_test_with_install.h"
 #include "chrome/browser/extensions/extension_util.h"
+#include "chrome/browser/extensions/permissions_updater.h"
+#include "chrome/browser/extensions/scripting_permissions_modifier.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/crx_file/id_util.h"
 #include "extensions/common/extension_builder.h"
+#include "extensions/common/extension_features.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace extensions {
@@ -120,4 +126,82 @@
   EXPECT_EQ(expected_has_permission, has_permission);
 }
 
+TEST_F(PermissionsAPIUnitTest, ContainsAndGetAllWithRuntimeHostPermissions) {
+  // This test relies on the click-to-script feature.
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      extensions_features::kRuntimeHostPermissions);
+
+  scoped_refptr<const Extension> extension =
+      ExtensionBuilder("extension")
+          .AddPermissions({"https://example.com/"})
+          .Build();
+  PermissionsUpdater updater(profile());
+  updater.InitializePermissions(extension.get());
+  updater.GrantActivePermissions(extension.get());
+  service()->AddExtension(extension.get());
+
+  auto contains_origin = [this, &extension](const char* origin) {
+    auto function = base::MakeRefCounted<PermissionsContainsFunction>();
+    function->set_extension(extension.get());
+    if (!extension_function_test_utils::RunFunction(
+            function.get(),
+            base::StringPrintf(R"([{"origins": ["%s"]}])", origin), browser(),
+            api_test_utils::NONE)) {
+      ADD_FAILURE() << "Running function failed: " << function->GetError();
+    }
+
+    return function->GetResultList()->GetList()[0].GetBool();
+  };
+
+  auto get_all = [this, &extension]() {
+    auto function = base::MakeRefCounted<PermissionsGetAllFunction>();
+    function->set_extension(extension.get());
+
+    std::vector<std::string> origins;
+    if (!extension_function_test_utils::RunFunction(
+            function.get(), "[]", browser(), api_test_utils::NONE)) {
+      ADD_FAILURE() << "Running function failed: " << function->GetError();
+      return origins;
+    }
+
+    const base::Value* results = function->GetResultList();
+    if (results->GetList().size() != 1u || !results->GetList()[0].is_dict()) {
+      ADD_FAILURE() << "Invalid result value";
+      return origins;
+    }
+
+    const base::Value* origins_value =
+        results->GetList()[0].FindKeyOfType("origins", base::Value::Type::LIST);
+    for (const auto& value : origins_value->GetList())
+      origins.push_back(value.GetString());
+
+    return origins;
+  };
+
+  // Currently, the extension should have access to example.com (since
+  // permissions are not withheld).
+  constexpr char kExampleCom[] = "https://example.com/*";
+  EXPECT_TRUE(contains_origin(kExampleCom));
+  EXPECT_THAT(get_all(), testing::ElementsAre(kExampleCom));
+
+  ScriptingPermissionsModifier modifier(profile(), extension);
+  modifier.SetWithholdHostPermissions(true);
+
+  // Once we withhold the permission, the contains function should correctly
+  // report the value.
+  EXPECT_FALSE(contains_origin(kExampleCom));
+  EXPECT_THAT(get_all(), testing::IsEmpty());
+
+  constexpr char kChromiumOrg[] = "https://chromium.org/";
+  modifier.GrantHostPermission(GURL(kChromiumOrg));
+
+  // The permissions API only reports active permissions, rather than granted
+  // permissions. This means it will not report values for permissions that
+  // aren't requested. This is probably good, because the extension wouldn't be
+  // able to use them anyway (since they aren't active).
+  EXPECT_FALSE(contains_origin(kChromiumOrg));
+  EXPECT_THAT(get_all(), testing::IsEmpty());
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/infobars/infobars_browsertest.cc b/chrome/browser/infobars/infobars_browsertest.cc
index a5c18a0..5993012 100644
--- a/chrome/browser/infobars/infobars_browsertest.cc
+++ b/chrome/browser/infobars/infobars_browsertest.cc
@@ -414,7 +414,7 @@
 
     case IBD::PAGE_LOAD_CAPPING_INFOBAR_DELEGATE:
       PageLoadCappingInfoBarDelegate::Create(
-          GetWebContents(), PageLoadCappingInfoBarDelegate::PauseCallback());
+          GetWebContents(), base::DoNothing(), base::DoNothing());
       break;
 
     case IBD::BLOATED_RENDERER_INFOBAR_DELEGATE:
diff --git a/chrome/browser/metrics/chrome_stability_metrics_provider.cc b/chrome/browser/metrics/chrome_stability_metrics_provider.cc
index f1717960..dc48f4e 100644
--- a/chrome/browser/metrics/chrome_stability_metrics_provider.cc
+++ b/chrome/browser/metrics/chrome_stability_metrics_provider.cc
@@ -160,12 +160,8 @@
     int rph_id,
     const crash_reporter::CrashMetricsReporter::ReportedCrashTypeSet&
         reported_counts) {
-  if (reported_counts.count(
-          crash_reporter::CrashMetricsReporter::ProcessedCrashCounts::
-              kRendererForegroundVisibleCrash) ||
-      reported_counts.count(
-          crash_reporter::CrashMetricsReporter::ProcessedCrashCounts::
-              kRendererForegroundVisibleSubframeCrash)) {
+  if (reported_counts.count(crash_reporter::CrashMetricsReporter::
+                                ProcessedCrashCounts::kRendererCrashAll)) {
     helper_.IncreaseRendererCrashCount();
   }
   if (reported_counts.count(crash_reporter::CrashMetricsReporter::
diff --git a/chrome/browser/notifications/win/notification_image_retainer.cc b/chrome/browser/notifications/win/notification_image_retainer.cc
index a8f3abb..98fecee4 100644
--- a/chrome/browser/notifications/win/notification_image_retainer.cc
+++ b/chrome/browser/notifications/win/notification_image_retainer.cc
@@ -7,6 +7,7 @@
 #include "base/files/file_util.h"
 #include "base/hash.h"
 #include "base/memory/ref_counted_memory.h"
+#include "base/metrics/histogram_macros.h"
 #include "base/path_service.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/task/post_task.h"
@@ -67,8 +68,11 @@
     : task_runner_(task_runner) {}
 
 NotificationImageRetainer::~NotificationImageRetainer() {
-  if (!image_directory_.empty())
+  if (!image_directory_.empty()) {
+    SCOPED_UMA_HISTOGRAM_TIMER(
+        "Notifications.Windows.ImageRetainerDestructionTime");
     base::DeleteFile(image_directory_, true);
+  }
 }
 
 base::FilePath NotificationImageRetainer::RegisterTemporaryImage(
@@ -82,6 +86,8 @@
     return base::FilePath();
 
   if (!initialized_) {
+    SCOPED_UMA_HISTOGRAM_TIMER(
+        "Notifications.Windows.ImageRetainerInitializationTime");
     image_directory_ = DetermineImageDirectory();
     // Delete the old image directory.
     DeleteFile(image_directory_, /*recursive=*/true);
diff --git a/chrome/browser/offline_pages/android/offline_page_bridge.cc b/chrome/browser/offline_pages/android/offline_page_bridge.cc
index 1ec5bd2..ce5b2d00 100644
--- a/chrome/browser/offline_pages/android/offline_page_bridge.cc
+++ b/chrome/browser/offline_pages/android/offline_page_bridge.cc
@@ -221,6 +221,9 @@
       offline_header.reason =
           offline_pages::OfflinePageHeader::Reason::NET_ERROR_SUGGESTION;
       break;
+    case offline_items_collection::LaunchLocation::DOWNLOAD_SHELF:
+      NOTREACHED();
+      break;
   }
   offline_header.need_to_persist = true;
   offline_header.id = base::Int64ToString(offline_id);
diff --git a/chrome/browser/page_load_metrics/observers/page_capping_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/page_capping_page_load_metrics_observer.cc
index 9672666..e82a8c2 100644
--- a/chrome/browser/page_load_metrics/observers/page_capping_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/page_capping_page_load_metrics_observer.cc
@@ -12,6 +12,7 @@
 #include "base/optional.h"
 #include "base/rand_util.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/time/default_tick_clock.h"
 #include "chrome/browser/data_use_measurement/page_load_capping/chrome_page_load_capping_features.h"
 #include "chrome/browser/data_use_measurement/page_load_capping/page_load_capping_blacklist.h"
 #include "chrome/browser/data_use_measurement/page_load_capping/page_load_capping_infobar_delegate.h"
@@ -39,6 +40,8 @@
 
 const char kPageFuzzing[] = "PageFuzzingKiB";
 
+const char kInfoBarTimeoutInMilliseconds[] = "InfoBarTimeoutInMilliseconds";
+
 // The page load capping bytes threshold for the page. There are seperate
 // thresholds for media and non-media pages. Returns empty optional if the
 // page should not be capped.
@@ -77,10 +80,18 @@
   return std::max<int64_t>((typical_size - network_bytes), 0);
 }
 
+base::TimeDelta GetPageLoadCappingTimeout() {
+  return base::TimeDelta::FromMilliseconds(
+      base::GetFieldTrialParamByFeatureAsInt(
+          data_use_measurement::page_load_capping::features::
+              kDetectingHeavyPages,
+          kInfoBarTimeoutInMilliseconds, 8000));
+}
+
 }  // namespace
 
 PageCappingPageLoadMetricsObserver::PageCappingPageLoadMetricsObserver()
-    : weak_factory_(this) {}
+    : clock_(base::DefaultTickClock::GetInstance()), weak_factory_(this) {}
 PageCappingPageLoadMetricsObserver::~PageCappingPageLoadMetricsObserver() {}
 
 page_load_metrics::PageLoadMetricsObserver::ObservePolicy
@@ -101,6 +112,7 @@
 void PageCappingPageLoadMetricsObserver::OnLoadedResource(
     const page_load_metrics::ExtraRequestCompleteInfo&
         extra_request_complete_info) {
+  last_request_complete_time_ = clock_->NowTicks();
   if (extra_request_complete_info.was_cached)
     return;
   network_bytes_ += extra_request_complete_info.raw_body_bytes;
@@ -116,21 +128,25 @@
   if (!web_contents_)
     return;
 
-  // If there is no capping threshold, the threshold or the threshold is not
-  // met, do not show an InfoBar. Use the fuzzing offset to increase the number
-  // of bytes needed.
+  // If there is no capping threshold or the threshold is not met, do not show
+  // an InfoBar. Use the fuzzing offset to increase the number of bytes needed.
   if (!page_cap_ || (network_bytes_ - fuzzing_offset_) < page_cap_.value())
     return;
 
   if (IsBlacklisted())
     return;
 
-  if (PageLoadCappingInfoBarDelegate::Create(
+  // Set the state preemptively in case one of the callbacks is called
+  // synchronously, if the InfoBar is not created, set it back.
+  page_capping_state_ = PageCappingState::kInfoBarShown;
+  if (!PageLoadCappingInfoBarDelegate::Create(
           web_contents_,
           base::BindRepeating(
               &PageCappingPageLoadMetricsObserver::PauseSubresourceLoading,
-              weak_factory_.GetWeakPtr()))) {
-    page_capping_state_ = PageCappingState::kInfoBarShown;
+              weak_factory_.GetWeakPtr()),
+          base::BindRepeating(&PageCappingPageLoadMetricsObserver::TimeToExpire,
+                              weak_factory_.GetWeakPtr()))) {
+    page_capping_state_ = PageCappingState::kInfoBarNotShown;
   }
 }
 
@@ -333,3 +349,22 @@
 
   return page_capping_service->page_load_capping_blacklist();
 }
+
+void PageCappingPageLoadMetricsObserver::TimeToExpire(
+    base::TimeDelta* time_to_expire) const {
+  DCHECK(time_to_expire);
+  DCHECK_EQ(*time_to_expire, base::TimeDelta());
+  DCHECK_EQ(PageCappingState::kInfoBarShown, page_capping_state_);
+  DCHECK(last_request_complete_time_);
+  auto expiration_time =
+      (last_request_complete_time_.value() + GetPageLoadCappingTimeout());
+  if (expiration_time < clock_->NowTicks())
+    return;
+
+  *time_to_expire = expiration_time - clock_->NowTicks();
+}
+
+void PageCappingPageLoadMetricsObserver::SetTickClockForTesting(
+    base::TickClock* clock) {
+  clock_ = clock;
+}
diff --git a/chrome/browser/page_load_metrics/observers/page_capping_page_load_metrics_observer.h b/chrome/browser/page_load_metrics/observers/page_capping_page_load_metrics_observer.h
index 85023fb..e5940b2 100644
--- a/chrome/browser/page_load_metrics/observers/page_capping_page_load_metrics_observer.h
+++ b/chrome/browser/page_load_metrics/observers/page_capping_page_load_metrics_observer.h
@@ -5,12 +5,15 @@
 #ifndef CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_PAGE_CAPPING_PAGE_LOAD_METRICS_OBSERVER_H_
 #define CHROME_BROWSER_PAGE_LOAD_METRICS_OBSERVERS_PAGE_CAPPING_PAGE_LOAD_METRICS_OBSERVER_H_
 
+#include <memory>
 #include <vector>
 
 #include <stdint.h>
 
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
 #include "chrome/browser/page_load_metrics/page_load_metrics_observer.h"
 #include "third_party/blink/public/mojom/loader/pause_subresource_loading_handle.mojom.h"
 
@@ -39,6 +42,10 @@
   // Returns whether the page's subresource loading is currently paused.
   bool IsPausedForTesting() const;
 
+  // Tests can change the behavior of clock for testing time between resource
+  // loads.
+  void SetTickClockForTesting(base::TickClock* clock);
+
   // The current state of the page.
   // This class operates as a state machine going from each of the below states
   // in order. This is recorded to UKM, so the enum should not be changed.
@@ -112,6 +119,15 @@
   // load resources. https://crbug.com/835895
   void PauseSubresourceLoading(bool paused);
 
+  // Sets |time_to_expire| to the earliest time duration that the page load is
+  // considered not to be using data anymore. |time_to_expire| must be passed in
+  // as TimeDelta initialized to 0 to handle the case of the underlying weak
+  // pointer being destroyed.
+  // If |time_to_expire| is returned as 0, the consumer should treat the page as
+  // not using data anymore, and does not need to wait any longer to consider
+  // the page stopped with respect to data use..
+  void TimeToExpire(base::TimeDelta* time_to_expire) const;
+
   // The current bytes threshold of the capping page triggering.
   base::Optional<int64_t> page_cap_;
 
@@ -149,6 +165,11 @@
   // render frames of this page.
   std::vector<blink::mojom::PauseSubresourceLoadingHandlePtr> handles_;
 
+  base::Optional<base::TimeTicks> last_request_complete_time_;
+
+  // Default clock unless SetClockForTesting is called.
+  const base::TickClock* clock_;
+
   base::WeakPtrFactory<PageCappingPageLoadMetricsObserver> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(PageCappingPageLoadMetricsObserver);
diff --git a/chrome/browser/page_load_metrics/observers/page_capping_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/page_capping_page_load_metrics_observer_unittest.cc
index b4d9441a..b212021 100644
--- a/chrome/browser/page_load_metrics/observers/page_capping_page_load_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/page_capping_page_load_metrics_observer_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/callback.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/test/simple_test_tick_clock.h"
 #include "base/time/default_clock.h"
 #include "chrome/browser/data_use_measurement/page_load_capping/chrome_page_load_capping_features.h"
 #include "chrome/browser/data_use_measurement/page_load_capping/page_load_capping_blacklist.h"
@@ -52,12 +53,17 @@
     : public PageCappingPageLoadMetricsObserver {
  public:
   using SizeUpdateCallback = base::RepeatingCallback<void(int64_t)>;
-  TestPageCappingPageLoadMetricsObserver(int64_t fuzzing_offset,
-                                         PageLoadCappingBlacklist* blacklist,
-                                         const SizeUpdateCallback& callback)
+  TestPageCappingPageLoadMetricsObserver(
+      int64_t fuzzing_offset,
+      PageLoadCappingBlacklist* blacklist,
+      std::unique_ptr<base::SimpleTestTickClock> simple_test_tick_clock,
+      const SizeUpdateCallback& callback)
       : fuzzing_offset_(fuzzing_offset),
         blacklist_(blacklist),
-        size_callback_(callback) {}
+        simple_test_tick_clock_(std::move(simple_test_tick_clock)),
+        size_callback_(callback) {
+    SetTickClockForTesting(simple_test_tick_clock_.get());
+  }
   ~TestPageCappingPageLoadMetricsObserver() override {}
 
   void WriteToSavings(int64_t bytes_saved) override {
@@ -73,6 +79,7 @@
  private:
   int64_t fuzzing_offset_;
   PageLoadCappingBlacklist* blacklist_;
+  std::unique_ptr<base::SimpleTestTickClock> simple_test_tick_clock_;
   SizeUpdateCallback size_callback_;
 };
 
@@ -126,9 +133,11 @@
   void RegisterObservers(page_load_metrics::PageLoadTracker* tracker) override {
     auto observer = std::make_unique<TestPageCappingPageLoadMetricsObserver>(
         fuzzing_offset_, test_blacklist_.get(),
+        std::make_unique<base::SimpleTestTickClock>(),
         base::BindRepeating(&PageCappingObserverTest::UpdateSavings,
                             base::Unretained(this)));
     observer_ = observer.get();
+    // Keep the clock frozen.
     tracker->AddObserver(std::move(observer));
   }
 
diff --git a/chrome/browser/previews/android/previews_android_bridge.cc b/chrome/browser/previews/android/previews_android_bridge.cc
index 9f6c4e36..b5630e9 100644
--- a/chrome/browser/previews/android/previews_android_bridge.cc
+++ b/chrome/browser/previews/android/previews_android_bridge.cc
@@ -55,6 +55,26 @@
           env, web_contents->GetVisibleURL().host()));
 }
 
+base::android::ScopedJavaLocalRef<jstring>
+PreviewsAndroidBridge::GetStalePreviewTimestamp(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& obj,
+    const base::android::JavaParamRef<jobject>& j_web_contents) {
+  content::WebContents* web_contents =
+      content::WebContents::FromJavaWebContents(j_web_contents);
+  if (!web_contents)
+    return base::android::ScopedJavaLocalRef<jstring>();
+
+  PreviewsUITabHelper* tab_helper =
+      PreviewsUITabHelper::FromWebContents(web_contents);
+  if (!tab_helper)
+    return base::android::ScopedJavaLocalRef<jstring>();
+
+  return base::android::ScopedJavaLocalRef<jstring>(
+      base::android::ConvertUTF16ToJavaString(
+          env, tab_helper->GetStalePreviewTimestampText()));
+}
+
 void PreviewsAndroidBridge::LoadOriginal(
     JNIEnv* env,
     const base::android::JavaParamRef<jobject>& obj,
diff --git a/chrome/browser/previews/android/previews_android_bridge.h b/chrome/browser/previews/android/previews_android_bridge.h
index d4893f6..b7dc027a 100644
--- a/chrome/browser/previews/android/previews_android_bridge.h
+++ b/chrome/browser/previews/android/previews_android_bridge.h
@@ -28,6 +28,11 @@
       const base::android::JavaParamRef<jobject>& obj,
       const base::android::JavaParamRef<jobject>& j_web_contents);
 
+  base::android::ScopedJavaLocalRef<jstring> GetStalePreviewTimestamp(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj,
+      const base::android::JavaParamRef<jobject>& j_web_contents);
+
   void LoadOriginal(JNIEnv* env,
                     const base::android::JavaParamRef<jobject>& obj,
                     const base::android::JavaParamRef<jobject>& j_web_contents);
diff --git a/chrome/browser/resources/chromeos/assistant_optin/assistant_loading.js b/chrome/browser/resources/chromeos/assistant_optin/assistant_loading.js
index 2b75a78..5b539bc 100644
--- a/chrome/browser/resources/chromeos/assistant_optin/assistant_loading.js
+++ b/chrome/browser/resources/chromeos/assistant_optin/assistant_loading.js
@@ -98,8 +98,8 @@
       this.addClass_('loading-animation');
     }.bind(this), 500);
     this.loadingTimeout_ = window.setTimeout(function() {
-      this.onErrorOccurred();
-    }.bind(this), 10000);
+      this.onLoadingTimeout();
+    }.bind(this), 15000);
   },
 
   /**
@@ -131,6 +131,14 @@
   },
 
   /**
+   * Called when the loading timeout is triggered.
+   */
+  onLoadingTimeout: function() {
+    chrome.send('login.AssistantOptInFlowScreen.LoadingScreen.timeout');
+    this.onErrorOccurred();
+  },
+
+  /**
    * Signal from host to show the screen.
    */
   onShow: function() {
diff --git a/chrome/browser/resources/chromeos/login/custom_elements_login.html b/chrome/browser/resources/chromeos/login/custom_elements_login.html
index dd85949..36d289b 100644
--- a/chrome/browser/resources/chromeos/login/custom_elements_login.html
+++ b/chrome/browser/resources/chromeos/login/custom_elements_login.html
@@ -35,5 +35,6 @@
 <include src="discover/discover_components.html">
 <include src="marketing_opt_in.html">
 <include src="../assistant_optin/assistant_optin_flow.html">
+<include src="multidevice_setup_first_run.html">
 
 <script src="chrome://oobe/custom_elements.js"></script>
diff --git a/chrome/browser/resources/chromeos/login/custom_elements_login.js b/chrome/browser/resources/chromeos/login/custom_elements_login.js
index feda281c..cd6bba4 100644
--- a/chrome/browser/resources/chromeos/login/custom_elements_login.js
+++ b/chrome/browser/resources/chromeos/login/custom_elements_login.js
@@ -39,3 +39,4 @@
 // <include src="discover/discover_components.js">
 // <include src="marketing_opt_in.js">
 // <include src="../assistant_optin/assistant_optin_flow.js">
+// <include src="multidevice_setup_first_run.js">
diff --git a/chrome/browser/resources/chromeos/login/custom_elements_oobe.html b/chrome/browser/resources/chromeos/login/custom_elements_oobe.html
index 825134c1..b58dcf2d 100644
--- a/chrome/browser/resources/chromeos/login/custom_elements_oobe.html
+++ b/chrome/browser/resources/chromeos/login/custom_elements_oobe.html
@@ -46,5 +46,6 @@
 <include src="discover/discover_components.html">
 <include src="marketing_opt_in.html">
 <include src="../assistant_optin/assistant_optin_flow.html">
+<include src="multidevice_setup_first_run.html">
 
 <script src="chrome://oobe/custom_elements.js"></script>
diff --git a/chrome/browser/resources/chromeos/login/custom_elements_oobe.js b/chrome/browser/resources/chromeos/login/custom_elements_oobe.js
index 742a2e26..fffa637 100644
--- a/chrome/browser/resources/chromeos/login/custom_elements_oobe.js
+++ b/chrome/browser/resources/chromeos/login/custom_elements_oobe.js
@@ -55,3 +55,4 @@
 // <include src="discover/discover_components.js">
 // <include src="marketing_opt_in.js">
 // <include src="../assistant_optin/assistant_optin_flow.js">
+// <include src="multidevice_setup_first_run.js">
diff --git a/chrome/browser/resources/chromeos/login/login.js b/chrome/browser/resources/chromeos/login/login.js
index b509202..8fddfea 100644
--- a/chrome/browser/resources/chromeos/login/login.js
+++ b/chrome/browser/resources/chromeos/login/login.js
@@ -62,6 +62,7 @@
       login.DiscoverScreen.register();
       login.MarketingOptInScreen.register();
       login.AssistantOptInFlowScreen.register();
+      login.MultiDeviceSetupScreen.register();
 
       cr.ui.Bubble.decorate($('bubble'));
       login.HeaderBar.decorate($('login-header-bar'));
diff --git a/chrome/browser/resources/chromeos/login/login_non_lock_shared.js b/chrome/browser/resources/chromeos/login/login_non_lock_shared.js
index b284885..5d7e8d7 100644
--- a/chrome/browser/resources/chromeos/login/login_non_lock_shared.js
+++ b/chrome/browser/resources/chromeos/login/login_non_lock_shared.js
@@ -39,6 +39,7 @@
 // <include src="screen_app_downloading.js">
 // <include src="screen_discover.js">
 // <include src="screen_marketing_opt_in.js">
+// <include src="screen_multidevice_setup.js">
 
 // <include src="../../gaia_auth_host/authenticator.js">
 
diff --git a/chrome/browser/resources/chromeos/login/login_screens.html b/chrome/browser/resources/chromeos/login/login_screens.html
index c4c4e69..ca1ca5cb 100644
--- a/chrome/browser/resources/chromeos/login/login_screens.html
+++ b/chrome/browser/resources/chromeos/login/login_screens.html
@@ -28,3 +28,4 @@
 <include src="screen_app_downloading.html">
 <include src="screen_discover.html">
 <include src="screen_marketing_opt_in.html">
+<include src="screen_multidevice_setup.html">
diff --git a/chrome/browser/resources/chromeos/login/md_login.js b/chrome/browser/resources/chromeos/login/md_login.js
index ffc87f7..e5afcaa 100644
--- a/chrome/browser/resources/chromeos/login/md_login.js
+++ b/chrome/browser/resources/chromeos/login/md_login.js
@@ -61,6 +61,7 @@
       login.DiscoverScreen.register();
       login.MarketingOptInScreen.register();
       login.AssistantOptInFlowScreen.register();
+      login.MultiDeviceSetupScreen.register();
 
       cr.ui.Bubble.decorate($('bubble-persistent'));
       $('bubble-persistent').persistent = true;
diff --git a/chrome/browser/resources/chromeos/login/md_login_screens.html b/chrome/browser/resources/chromeos/login/md_login_screens.html
index 92270b9..8239a4c 100644
--- a/chrome/browser/resources/chromeos/login/md_login_screens.html
+++ b/chrome/browser/resources/chromeos/login/md_login_screens.html
@@ -28,3 +28,4 @@
 <include src="screen_app_downloading.html">
 <include src="screen_discover.html">
 <include src="screen_marketing_opt_in.html">
+<include src="screen_multidevice_setup.html">
diff --git a/chrome/browser/resources/chromeos/login/multidevice_setup_first_run.html b/chrome/browser/resources/chromeos/login/multidevice_setup_first_run.html
new file mode 100644
index 0000000..e7776aa6
--- /dev/null
+++ b/chrome/browser/resources/chromeos/login/multidevice_setup_first_run.html
@@ -0,0 +1,35 @@
+<!-- Copyright 2018 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<link rel="import" href="chrome://resources/html/polymer.html">
+
+<link rel="import" href="chrome://oobe/custom_elements.html">
+<!--
+  UI for the MultiDevice setup flow when displayed after OOBE or during the
+  user's first login on this Chromebook. Note that this flow is
+  slightly different from the post-login flow in 3 ways:
+    (1) In the first run, the user has just entered their password, so we do not
+        prompt the user to enter a password before continuing.
+    (2) In the first run, once the user selects a host device, we continue to
+        the next OOBE/login task; in the post-login mode, there is a "success"
+        screen.
+    (3) In the first run, buttons are styled with custom OOBE buttons.
+
+  TODO(khorimoto): Implement real flow. For now, this page simply displays
+                   placeholder buttons. See https://crbug.com/884065.
+
+  Example:
+      <multidevice-setup-first-run></multidevice-setup-first-run>
+-->
+<dom-module id="multidevice-setup-first-run">
+  <template>
+    <div>Placeholder MultiDevice setup flow</div>
+    <oobe-text-button inverse on-click="onSkipSetupClicked_">
+      <div>Skip setup</div>
+    </oobe-text-button>
+    <oobe-next-button on-click="onAcceptClicked_">
+      <div>Next</div>
+    </oobe-next-button>
+  </template>
+</dom-module>
diff --git a/chrome/browser/resources/chromeos/login/multidevice_setup_first_run.js b/chrome/browser/resources/chromeos/login/multidevice_setup_first_run.js
new file mode 100644
index 0000000..93633b4
--- /dev/null
+++ b/chrome/browser/resources/chromeos/login/multidevice_setup_first_run.js
@@ -0,0 +1,27 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview MultiDevice setup flow Polymer element to be used in the first
+ *     run (i.e., after OOBE or during the user's first login on this
+ *     Chromebook).
+ */
+Polymer({
+  is: 'multidevice-setup-first-run',
+
+  /** @private */
+  onSkipSetupClicked_: function() {
+    this.exitSetupFlow_();
+  },
+
+  /** @private */
+  onAcceptClicked_: function() {
+    this.exitSetupFlow_();
+  },
+
+  /** @private */
+  exitSetupFlow_: function() {
+    chrome.send('login.MultiDeviceSetupScreen.userActed', ['setup-finished']);
+  }
+});
diff --git a/chrome/browser/resources/chromeos/login/oobe.js b/chrome/browser/resources/chromeos/login/oobe.js
index fdb6bfa..78de013 100644
--- a/chrome/browser/resources/chromeos/login/oobe.js
+++ b/chrome/browser/resources/chromeos/login/oobe.js
@@ -71,6 +71,7 @@
       login.DiscoverScreen.register();
       login.MarketingOptInScreen.register();
       login.AssistantOptInFlowScreen.register();
+      login.MultiDeviceSetupScreen.register();
 
       cr.ui.Bubble.decorate($('bubble-persistent'));
       $('bubble-persistent').persistent = true;
diff --git a/chrome/browser/resources/chromeos/login/oobe_screens.html b/chrome/browser/resources/chromeos/login/oobe_screens.html
index 84174e8..153304f 100644
--- a/chrome/browser/resources/chromeos/login/oobe_screens.html
+++ b/chrome/browser/resources/chromeos/login/oobe_screens.html
@@ -37,3 +37,4 @@
 <include src="screen_app_downloading.html">
 <include src="screen_discover.html">
 <include src="screen_marketing_opt_in.html">
+<include src="screen_multidevice_setup.html">
diff --git a/chrome/browser/resources/chromeos/login/screen_multidevice_setup.html b/chrome/browser/resources/chromeos/login/screen_multidevice_setup.html
new file mode 100644
index 0000000..7293931
--- /dev/null
+++ b/chrome/browser/resources/chromeos/login/screen_multidevice_setup.html
@@ -0,0 +1,9 @@
+<!-- Copyright 2018 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+<link rel="import" href="chrome://oobe/custom_elements.html">
+
+<div class="step right hidden" id="multidevice-setup" hidden>
+  <multidevice-setup-first-run id="multidevice-setup-impl">
+  </multidevice-setup-first-run>
+</div>
diff --git a/chrome/browser/resources/chromeos/login/screen_multidevice_setup.js b/chrome/browser/resources/chromeos/login/screen_multidevice_setup.js
new file mode 100644
index 0000000..626a1bf
--- /dev/null
+++ b/chrome/browser/resources/chromeos/login/screen_multidevice_setup.js
@@ -0,0 +1,14 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview MultiDevice setup screen for login/OOBE.
+ */
+login.createScreen('MultiDeviceSetupScreen', 'multidevice-setup', function() {
+  return {
+    get defaultControl() {
+      return $('multidevice-setup-impl');
+    },
+  };
+});
diff --git a/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js b/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js
index b04fdf1d..660e1f5 100644
--- a/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js
+++ b/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js
@@ -113,8 +113,14 @@
 /** @const {number} */
 SelectToSpeak.READ_SELECTION_KEY_CODE = 83;
 
-/** @const {number} */
-SelectToSpeak.NODE_STATE_TEST_INTERVAL_MS = 1000;
+/**
+ * How often (in ms) to check that the currently spoken node is
+ * still valid and in the same position. Decreasing this will make
+ * STS seem more reactive to page changes but decreasing it too much
+ * could cause performance issues.
+ * @const {number}
+ */
+SelectToSpeak.NODE_STATE_TEST_INTERVAL_MS = 500;
 
 SelectToSpeak.prototype = {
   /**
diff --git a/chrome/browser/resources/chromeos/wallpaper_manager/css/wallpaper_manager.css b/chrome/browser/resources/chromeos/wallpaper_manager/css/wallpaper_manager.css
index a342db1..a90080e 100644
--- a/chrome/browser/resources/chromeos/wallpaper_manager/css/wallpaper_manager.css
+++ b/chrome/browser/resources/chromeos/wallpaper_manager/css/wallpaper_manager.css
@@ -435,8 +435,7 @@
 
 .v2 #categories-list > li {
   border-top: unset;
-  height: 32px;
-  margin-bottom: 8px;
+  height: 31px;
   margin-top: 8px;
   pointer-events: auto;
 }
diff --git a/chrome/browser/resources/chromeos/zip_archiver/cpp/compressor.cc b/chrome/browser/resources/chromeos/zip_archiver/cpp/compressor.cc
index f71ecf7..14630495 100644
--- a/chrome/browser/resources/chromeos/zip_archiver/cpp/compressor.cc
+++ b/chrome/browser/resources/chromeos/zip_archiver/cpp/compressor.cc
@@ -8,6 +8,7 @@
 #include <ctime>
 #include <sstream>
 #include <string>
+#include <utility>
 
 #include "chrome/browser/resources/chromeos/zip_archiver/cpp/compressor_archive_minizip.h"
 #include "chrome/browser/resources/chromeos/zip_archiver/cpp/compressor_io_javascript_stream.h"
@@ -49,17 +50,15 @@
     : compressor_id_(compressor_id),
       message_sender_(message_sender),
       worker_(instance_handle),
-      callback_factory_(this) {
-  requestor_ = new JavaScriptCompressorRequestor(this);
-  compressor_stream_ = new CompressorIOJavaScriptStream(requestor_);
-  compressor_archive_ = new CompressorArchiveMinizip(compressor_stream_);
-}
+      callback_factory_(this),
+      requestor_(std::make_unique<JavaScriptCompressorRequestor>(this)),
+      compressor_stream_(
+          std::make_unique<CompressorIOJavaScriptStream>(requestor_.get())),
+      compressor_archive_(std::make_unique<CompressorArchiveMinizip>(
+          compressor_stream_.get())) {}
 
 Compressor::~Compressor() {
   worker_.Join();
-  delete compressor_archive_;
-  delete compressor_stream_;
-  delete requestor_;
 }
 
 bool Compressor::Init() {
diff --git a/chrome/browser/resources/chromeos/zip_archiver/cpp/compressor.h b/chrome/browser/resources/chromeos/zip_archiver/cpp/compressor.h
index d2774aae..63e229c 100644
--- a/chrome/browser/resources/chromeos/zip_archiver/cpp/compressor.h
+++ b/chrome/browser/resources/chromeos/zip_archiver/cpp/compressor.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_RESOURCES_CHROMEOS_ZIP_ARCHIVER_CPP_COMPRESSOR_H_
 
 #include <ctime>
+#include <memory>
 
 #include "ppapi/cpp/instance_handle.h"
 #include "ppapi/cpp/var_array_buffer.h"
@@ -53,7 +54,9 @@
   JavaScriptMessageSenderInterface* message_sender() { return message_sender_; }
 
   // A getter function for the requestor.
-  JavaScriptCompressorRequestorInterface* requestor() { return requestor_; }
+  JavaScriptCompressorRequestorInterface* requestor() {
+    return requestor_.get();
+  }
 
   // A getter function for the compressor id.
   int compressor_id() { return compressor_id_; }
@@ -81,13 +84,13 @@
   pp::CompletionCallbackFactory<Compressor> callback_factory_;
 
   // A requestor for making calls to JavaScript.
-  JavaScriptCompressorRequestorInterface* requestor_;
-
-  // Minizip wrapper instance per compressor, shared across all operations.
-  CompressorArchive* compressor_archive_;
+  std::unique_ptr<JavaScriptCompressorRequestorInterface> requestor_;
 
   // An instance that takes care of all IO operations.
-  CompressorStream* compressor_stream_;
+  std::unique_ptr<CompressorStream> compressor_stream_;
+
+  // Minizip wrapper instance per compressor, shared across all operations.
+  std::unique_ptr<CompressorArchive> compressor_archive_;
 };
 
 #endif  // CHROME_BROWSER_RESOURCES_CHROMEOS_ZIP_ARCHIVER_CPP_COMPRESSOR_H_
diff --git a/chrome/browser/resources/chromeos/zip_archiver/cpp/compressor_io_javascript_stream.cc b/chrome/browser/resources/chromeos/zip_archiver/cpp/compressor_io_javascript_stream.cc
index 879671e9..8a0a13c 100644
--- a/chrome/browser/resources/chromeos/zip_archiver/cpp/compressor_io_javascript_stream.cc
+++ b/chrome/browser/resources/chromeos/zip_archiver/cpp/compressor_io_javascript_stream.cc
@@ -7,6 +7,7 @@
 #include <algorithm>
 #include <cstring>
 #include <limits>
+#include <utility>
 
 #include "chrome/browser/resources/chromeos/zip_archiver/cpp/javascript_compressor_requestor_interface.h"
 #include "ppapi/cpp/logging.h"
@@ -20,20 +21,21 @@
 
 CompressorIOJavaScriptStream::CompressorIOJavaScriptStream(
     JavaScriptCompressorRequestorInterface* requestor)
-    : requestor_(requestor), buffer_offset_(-1), buffer_data_length_(0) {
+    : requestor_(requestor),
+      buffer_offset_(-1),
+      buffer_data_length_(0),
+      buffer_(std::make_unique<char[]>(kMaximumDataChunkSize)) {
   pthread_mutex_init(&shared_state_lock_, nullptr);
   pthread_cond_init(&available_data_cond_, nullptr);
   pthread_cond_init(&data_written_cond_, nullptr);
 
   pthread_mutex_lock(&shared_state_lock_);
   available_data_ = false;
-  buffer_ = new char[kMaximumDataChunkSize];
   pthread_mutex_unlock(&shared_state_lock_);
 }
 
 CompressorIOJavaScriptStream::~CompressorIOJavaScriptStream() {
   pthread_mutex_lock(&shared_state_lock_);
-  delete buffer_;
   pthread_mutex_unlock(&shared_state_lock_);
   pthread_cond_destroy(&data_written_cond_);
   pthread_cond_destroy(&available_data_cond_);
@@ -51,7 +53,7 @@
   // Copy the data in buffer_ to array_buffer.
   pp::VarArrayBuffer array_buffer(buffer_data_length_);
   char* array_buffer_data = static_cast<char*>(array_buffer.Map());
-  memcpy(array_buffer_data, buffer_, buffer_data_length_);
+  memcpy(array_buffer_data, buffer_.get(), buffer_data_length_);
   array_buffer.Unmap();
 
   requestor_->WriteChunkRequest(buffer_offset_, buffer_data_length_,
@@ -118,7 +120,7 @@
     int64_t offset_in_buffer = current_offset - buffer_offset_;
     // Copy data from zip_buffer, which is pointed by buffer_pointer, to
     // buffer_.
-    memcpy(buffer_ + offset_in_buffer, buffer_pointer, copy_length);
+    memcpy(buffer_.get() + offset_in_buffer, buffer_pointer, copy_length);
 
     buffer_pointer += copy_length;
     buffer_data_length_ =
diff --git a/chrome/browser/resources/chromeos/zip_archiver/cpp/compressor_io_javascript_stream.h b/chrome/browser/resources/chromeos/zip_archiver/cpp/compressor_io_javascript_stream.h
index 1605cc9..b967c4c 100644
--- a/chrome/browser/resources/chromeos/zip_archiver/cpp/compressor_io_javascript_stream.h
+++ b/chrome/browser/resources/chromeos/zip_archiver/cpp/compressor_io_javascript_stream.h
@@ -7,6 +7,7 @@
 
 #include <pthread.h>
 #include <cstdint>
+#include <memory>
 #include <string>
 
 #include "chrome/browser/resources/chromeos/zip_archiver/cpp/compressor_stream.h"
@@ -72,7 +73,7 @@
   int64_t buffer_data_length_;
 
   // The buffer that contains cached data.
-  char* buffer_;
+  std::unique_ptr<char[]> buffer_;
 };
 
 #endif  // CHROME_BROWSER_RESOURCES_CHROMEOS_ZIP_ARCHIVER_CPP_COMPRESSOR_IO_JAVASCRIPT_STREAM_H_
diff --git a/chrome/browser/resources/chromeos/zip_archiver/cpp/module.cc b/chrome/browser/resources/chromeos/zip_archiver/cpp/module.cc
index df572a9..5735afe 100644
--- a/chrome/browser/resources/chromeos/zip_archiver/cpp/module.cc
+++ b/chrome/browser/resources/chromeos/zip_archiver/cpp/module.cc
@@ -3,7 +3,9 @@
 // found in the LICENSE file.
 
 #include <clocale>
+#include <memory>
 #include <sstream>
+#include <utility>
 
 #include "chrome/browser/resources/chromeos/zip_archiver/cpp/compressor.h"
 #include "chrome/browser/resources/chromeos/zip_archiver/cpp/javascript_message_sender_interface.h"
@@ -19,7 +21,8 @@
 
 namespace {
 
-typedef std::map<std::string, Volume*>::const_iterator volume_iterator;
+typedef std::map<std::string, std::unique_ptr<Volume>>::const_iterator
+    volume_iterator;
 typedef std::map<int, std::unique_ptr<Compressor>>::const_iterator
     compressor_iterator;
 
@@ -154,12 +157,7 @@
         instance_handle_(instance),
         message_sender_(this) {}
 
-  virtual ~NaclArchiveInstance() {
-    for (volume_iterator iterator = volumes_.begin();
-         iterator != volumes_.end(); ++iterator) {
-      delete iterator->second;
-    }
-  }
+  virtual ~NaclArchiveInstance() = default;
 
   // Handler for messages coming in from JS via postMessage().
   virtual void HandleMessage(const pp::Var& var_message) {
@@ -222,9 +220,7 @@
         break;
 
       case request::CLOSE_VOLUME: {
-        volume_iterator iterator = volumes_.find(file_system_id);
-        PP_DCHECK(iterator != volumes_.end());
-        delete iterator->second;
+        PP_DCHECK(volumes_.find(file_system_id) != volumes_.end());
         volumes_.erase(file_system_id);
         break;
       }
@@ -295,21 +291,21 @@
     // Should not call ReadMetadata for a Volume already present in NaCl.
     PP_DCHECK(volumes_.find(file_system_id) == volumes_.end());
 
-    Volume* volume =
-        new Volume(instance_handle_, file_system_id, &message_sender_);
+    std::unique_ptr<Volume> volume = std::make_unique<Volume>(
+        instance_handle_, file_system_id, &message_sender_);
     if (!volume->Init()) {
       message_sender_.SendFileSystemError(
           file_system_id, request_id,
           "Could not create a volume for: " + file_system_id + ".");
-      delete volume;
       return;
     }
-    volumes_[file_system_id] = volume;
+    Volume* raw_volume = volume.get();
+    volumes_[file_system_id] = std::move(volume);
 
     PP_DCHECK(var_dict.Get(request::key::kEncoding).is_string());
     PP_DCHECK(var_dict.Get(request::key::kArchiveSize).is_string());
 
-    volume->ReadMetadata(
+    raw_volume->ReadMetadata(
         request_id, var_dict.Get(request::key::kEncoding).AsString(),
         request::GetInt64FromString(var_dict, request::key::kArchiveSize));
   }
@@ -473,7 +469,7 @@
 
   // A map that holds for every opened archive its instance. The key is the file
   // system id of the archive.
-  std::map<std::string, Volume*> volumes_;
+  std::map<std::string, std::unique_ptr<Volume>> volumes_;
 
   // A map from compressor ids to compressors.
   std::map<int, std::unique_ptr<Compressor>> compressors_;
diff --git a/chrome/browser/resources/chromeos/zip_archiver/cpp/volume.cc b/chrome/browser/resources/chromeos/zip_archiver/cpp/volume.cc
index ff5d7b2..a2b1662 100644
--- a/chrome/browser/resources/chromeos/zip_archiver/cpp/volume.cc
+++ b/chrome/browser/resources/chromeos/zip_archiver/cpp/volume.cc
@@ -6,6 +6,7 @@
 
 #include <cstring>
 #include <sstream>
+#include <utility>
 
 #include "chrome/browser/resources/chromeos/zip_archiver/cpp/char_coding.h"
 #include "chrome/browser/resources/chromeos/zip_archiver/cpp/javascript_message_sender_interface.h"
@@ -16,13 +17,6 @@
 
 namespace {
 
-#define LOG(x)                                                            \
-  do {                                                                    \
-    std::stringstream fmt;                                                \
-    fmt << x;                                                             \
-    message_sender_->CONSOLE_LOG(file_system_id_, request_id, fmt.str()); \
-  } while (0)
-
 typedef std::map<std::string, VolumeArchive*>::const_iterator
     volume_archive_iterator;
 
@@ -142,8 +136,9 @@
 // Volume constructor.
 class VolumeArchiveFactory : public VolumeArchiveFactoryInterface {
  public:
-  virtual VolumeArchive* Create(VolumeReader* reader) {
-    return new VolumeArchiveMinizip(reader);
+  virtual std::unique_ptr<VolumeArchive> Create(
+      std::unique_ptr<VolumeReader> reader) {
+    return std::make_unique<VolumeArchiveMinizip>(std::move(reader));
   }
 };
 
@@ -154,8 +149,9 @@
   // VolumeReaderFactory does not own the volume pointer.
   explicit VolumeReaderFactory(Volume* volume) : volume_(volume) {}
 
-  virtual VolumeReader* Create(int64_t archive_size) {
-    return new VolumeReaderJavaScriptStream(archive_size, volume_->requestor());
+  virtual std::unique_ptr<VolumeReader> Create(int64_t archive_size) {
+    return std::make_unique<VolumeReaderJavaScriptStream>(archive_size,
+                                                          volume_->requestor());
   }
 
  private:
@@ -182,43 +178,36 @@
 Volume::Volume(const pp::InstanceHandle& instance_handle,
                const std::string& file_system_id,
                JavaScriptMessageSenderInterface* message_sender)
-    : volume_archive_(nullptr),
-      file_system_id_(file_system_id),
-      message_sender_(message_sender),
-      worker_(instance_handle),
-      callback_factory_(this) {
-  requestor_ = new JavaScriptRequestor(this);
-  volume_archive_factory_ = new VolumeArchiveFactory();
-  volume_reader_factory_ = new VolumeReaderFactory(this);
-  // Delegating constructors only from c++11.
-}
-
-Volume::Volume(const pp::InstanceHandle& instance_handle,
-               const std::string& file_system_id,
-               JavaScriptMessageSenderInterface* message_sender,
-               VolumeArchiveFactoryInterface* volume_archive_factory,
-               VolumeReaderFactoryInterface* volume_reader_factory)
-    : volume_archive_(nullptr),
-      file_system_id_(file_system_id),
+    : file_system_id_(file_system_id),
       message_sender_(message_sender),
       worker_(instance_handle),
       callback_factory_(this),
-      volume_archive_factory_(volume_archive_factory),
-      volume_reader_factory_(volume_reader_factory) {
-  requestor_ = new JavaScriptRequestor(this);
+      requestor_(std::make_unique<JavaScriptRequestor>(this)),
+      volume_archive_factory_(std::make_unique<VolumeArchiveFactory>()),
+      volume_reader_factory_(std::make_unique<VolumeReaderFactory>(this)) {
+  // Delegating constructors only from c++11.
 }
 
+Volume::Volume(
+    const pp::InstanceHandle& instance_handle,
+    const std::string& file_system_id,
+    JavaScriptMessageSenderInterface* message_sender,
+    std::unique_ptr<VolumeArchiveFactoryInterface> volume_archive_factory,
+    std::unique_ptr<VolumeReaderFactoryInterface> volume_reader_factory)
+    : file_system_id_(file_system_id),
+      message_sender_(message_sender),
+      worker_(instance_handle),
+      callback_factory_(this),
+      requestor_(std::make_unique<JavaScriptRequestor>(this)),
+      volume_archive_factory_(std::move(volume_archive_factory)),
+      volume_reader_factory_(std::move(volume_reader_factory)) {}
+
 Volume::~Volume() {
   worker_.Join();
 
   if (volume_archive_) {
     volume_archive_->Cleanup();
-    delete volume_archive_;
   }
-
-  delete requestor_;
-  delete volume_archive_factory_;
-  delete volume_reader_factory_;
 }
 
 bool Volume::Init() {
@@ -315,8 +304,7 @@
     message_sender_->SendFileSystemError(file_system_id_, request_id,
                                          volume_archive_->error_message());
     ClearJob();
-    delete volume_archive_;
-    volume_archive_ = nullptr;
+    volume_archive_.reset();
     return;
   }
 
@@ -338,8 +326,7 @@
       message_sender_->SendFileSystemError(file_system_id_, request_id,
                                            volume_archive_->error_message());
       ClearJob();
-      delete volume_archive_;
-      volume_archive_ = nullptr;
+      volume_archive_.reset();
       return;
     }
 
@@ -365,8 +352,7 @@
       message_sender_->SendFileSystemError(file_system_id_, request_id,
                                            volume_archive_->error_message());
       ClearJob();
-      delete volume_archive_;
-      volume_archive_ = nullptr;
+      volume_archive_.reset();
       return;
     }
     if (return_value == VolumeArchive::RESULT_EOF)
diff --git a/chrome/browser/resources/chromeos/zip_archiver/cpp/volume.h b/chrome/browser/resources/chromeos/zip_archiver/cpp/volume.h
index 0b82a87e..4ec197e 100644
--- a/chrome/browser/resources/chromeos/zip_archiver/cpp/volume.h
+++ b/chrome/browser/resources/chromeos/zip_archiver/cpp/volume.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_RESOURCES_CHROMEOS_ZIP_ARCHIVER_CPP_VOLUME_H_
 
 #include <map>
+#include <memory>
 #include <string>
 
 #include "chrome/browser/resources/chromeos/zip_archiver/cpp/volume_archive.h"
@@ -25,7 +26,8 @@
   virtual ~VolumeArchiveFactoryInterface() {}
 
   // Creates a new VolumeArchive.
-  virtual VolumeArchive* Create(VolumeReader* reader) = 0;
+  virtual std::unique_ptr<VolumeArchive> Create(
+      std::unique_ptr<VolumeReader> reader) = 0;
 };
 
 // A factory that creates VolumeReader(s). Useful for testing.
@@ -34,9 +36,7 @@
   virtual ~VolumeReaderFactoryInterface() {}
 
   // Creates a new VolumeReader. Returns nullptr if failed.
-  // Passes VolumeReader ownership to the implementation of
-  // VolumeArchiveInterfaceInterface.
-  virtual VolumeReader* Create(int64_t archive_size) = 0;
+  virtual std::unique_ptr<VolumeReader> Create(int64_t archive_size) = 0;
 };
 
 // Handles all operations like reading metadata and reading files from a single
@@ -53,8 +53,8 @@
   Volume(const pp::InstanceHandle& instance_handle /* Used for workers. */,
          const std::string& file_system_id,
          JavaScriptMessageSenderInterface* message_sender,
-         VolumeArchiveFactoryInterface* volume_archive_factory,
-         VolumeReaderFactoryInterface* volume_reader_factory);
+         std::unique_ptr<VolumeArchiveFactoryInterface> volume_archive_factory,
+         std::unique_ptr<VolumeReaderFactoryInterface> volume_reader_factory);
 
   virtual ~Volume();
 
@@ -102,7 +102,7 @@
                 const pp::VarDictionary& dictionary);
 
   JavaScriptMessageSenderInterface* message_sender() { return message_sender_; }
-  JavaScriptRequestorInterface* requestor() { return requestor_; }
+  JavaScriptRequestorInterface* requestor() { return requestor_.get(); }
   std::string file_system_id() { return file_system_id_; }
 
  private:
@@ -138,12 +138,12 @@
   void ClearJob();
 
   // Minizip wrapper instance per volume, shared across all operations.
-  VolumeArchive* volume_archive_;
+  std::unique_ptr<VolumeArchive> volume_archive_;
 
   // The file system id for this volume.
-  std::string file_system_id_;
+  const std::string file_system_id_;
 
-  // An object that sends messages to JavaScript.
+  // An object that sends messages to JavaScript. Not owned.
   JavaScriptMessageSenderInterface* message_sender_;
 
   // A worker for jobs that require blocking operations or a lot of processing
@@ -176,13 +176,13 @@
   pp::Lock job_lock_;  // A lock for guarding members related to jobs.
 
   // A requestor for making calls to JavaScript.
-  JavaScriptRequestorInterface* requestor_;
+  std::unique_ptr<JavaScriptRequestorInterface> requestor_;
 
   // A factory for creating VolumeArchive.
-  VolumeArchiveFactoryInterface* volume_archive_factory_;
+  std::unique_ptr<VolumeArchiveFactoryInterface> volume_archive_factory_;
 
   // A factory for creating VolumeReader.
-  VolumeReaderFactoryInterface* volume_reader_factory_;
+  std::unique_ptr<VolumeReaderFactoryInterface> volume_reader_factory_;
 
   // A map that converts index of file in the volume to pathname.
   std::map<int, std::string> index_to_pathname_;
diff --git a/chrome/browser/resources/chromeos/zip_archiver/cpp/volume_archive.h b/chrome/browser/resources/chromeos/zip_archiver/cpp/volume_archive.h
index 3acf374..ed835d0 100644
--- a/chrome/browser/resources/chromeos/zip_archiver/cpp/volume_archive.h
+++ b/chrome/browser/resources/chromeos/zip_archiver/cpp/volume_archive.h
@@ -7,7 +7,9 @@
 
 #include <time.h>
 #include <cstdint>
+#include <memory>
 #include <string>
+#include <utility>
 
 #include "chrome/browser/resources/chromeos/zip_archiver/cpp/volume_reader.h"
 
@@ -15,7 +17,8 @@
 // to be thread safe and its methods shouldn't be called in parallel.
 class VolumeArchive {
  public:
-  explicit VolumeArchive(VolumeReader* reader) : reader_(reader) {}
+  explicit VolumeArchive(std::unique_ptr<VolumeReader> reader)
+      : reader_(std::move(reader)) {}
 
   virtual ~VolumeArchive() {}
 
@@ -85,23 +88,17 @@
   // VolumeArchive::error_message().
   virtual bool Cleanup() = 0;
 
-  VolumeReader* reader() const { return reader_; }
+  VolumeReader* reader() const { return reader_.get(); }
   std::string error_message() const { return error_message_; }
 
  protected:
-  // Cleans up the reader. Can be called multiple times, but once called reader
-  // cannot be reinitialized.
-  void CleanupReader() {
-    delete reader_;
-    reader_ = nullptr;
-  }
-
   void set_error_message(const std::string& error_message) {
     error_message_ = error_message;
   }
 
  private:
-  VolumeReader* reader_;  // The reader that actually reads the archive data.
+  // The reader that actually reads the archive data.
+  std::unique_ptr<VolumeReader> reader_;
   std::string error_message_;  // An error message set in case of any errors.
 };
 
diff --git a/chrome/browser/resources/chromeos/zip_archiver/cpp/volume_archive_minizip.cc b/chrome/browser/resources/chromeos/zip_archiver/cpp/volume_archive_minizip.cc
index b41cfeeb..804ee0ed 100644
--- a/chrome/browser/resources/chromeos/zip_archiver/cpp/volume_archive_minizip.cc
+++ b/chrome/browser/resources/chromeos/zip_archiver/cpp/volume_archive_minizip.cc
@@ -9,6 +9,7 @@
 #include <cerrno>
 #include <cstring>
 #include <limits>
+#include <utility>
 
 #include "base/strings/string_util.h"
 #include "base/time/time.h"
@@ -204,8 +205,8 @@
 
 }  // namespace volume_archive_functions
 
-VolumeArchiveMinizip::VolumeArchiveMinizip(VolumeReader* reader)
-    : VolumeArchive(reader),
+VolumeArchiveMinizip::VolumeArchiveMinizip(std::unique_ptr<VolumeReader> reader)
+    : VolumeArchive(std::move(reader)),
       reader_data_size_(kMinimumDataChunkSize),
       zip_file_(nullptr),
       dynamic_cache_(std::make_unique<char[]>(kMaximumDataChunkSize)),
@@ -499,8 +500,6 @@
   zip_file_ = nullptr;
   password_cache_.reset();
 
-  CleanupReader();
-
   return returnValue;
 }
 
diff --git a/chrome/browser/resources/chromeos/zip_archiver/cpp/volume_archive_minizip.h b/chrome/browser/resources/chromeos/zip_archiver/cpp/volume_archive_minizip.h
index 784d4e7..dd114b05 100644
--- a/chrome/browser/resources/chromeos/zip_archiver/cpp/volume_archive_minizip.h
+++ b/chrome/browser/resources/chromeos/zip_archiver/cpp/volume_archive_minizip.h
@@ -43,7 +43,7 @@
 // operations.
 class VolumeArchiveMinizip : public VolumeArchive {
  public:
-  explicit VolumeArchiveMinizip(VolumeReader* reader);
+  explicit VolumeArchiveMinizip(std::unique_ptr<VolumeReader> reader);
 
   virtual ~VolumeArchiveMinizip();
 
diff --git a/chrome/browser/resources/chromeos/zip_archiver/js/background.js b/chrome/browser/resources/chromeos/zip_archiver/js/background.js
index 787ac85..eaa80c4 100644
--- a/chrome/browser/resources/chromeos/zip_archiver/js/background.js
+++ b/chrome/browser/resources/chromeos/zip_archiver/js/background.js
@@ -18,9 +18,6 @@
   chrome.fileSystemProvider.onReadFileRequested.addListener(
       unpacker.app.onReadFileRequested);
 
-  // Load the PNaCl module.
-  unpacker.app.loadNaclModule('module.nmf', 'application/x-pnacl');
-
   // Load translations
   unpacker.app.loadStringData();
 
diff --git a/chrome/browser/resources/local_ntp/custom_backgrounds.css b/chrome/browser/resources/local_ntp/custom_backgrounds.css
index 2158e49..e2c07cd 100644
--- a/chrome/browser/resources/local_ntp/custom_backgrounds.css
+++ b/chrome/browser/resources/local_ntp/custom_backgrounds.css
@@ -258,6 +258,7 @@
   background-size: 20px 20px;
   display: none;
   height: 20px;
+  outline: none;
   width: 20px;
 }
 
@@ -266,10 +267,12 @@
   display: none;
   height: 36px;
   margin: 8px 8px 0 16px;
+  outline: none;
   width: 36px;
 }
 
-#bg-sel-back-circle:active {
+#bg-sel-back-circle:active,
+#bg-sel-back-circle:focus {
   background: rgb(218, 220, 224);
   background-position: center;
   background-size: 36px 36px;
@@ -634,4 +637,4 @@
 html[dir=rtl] #link-icon {
   margin-left: 8px;
   margin-right: auto;
-}
\ No newline at end of file
+}
diff --git a/chrome/browser/resources/local_ntp/custom_backgrounds.js b/chrome/browser/resources/local_ntp/custom_backgrounds.js
index 5c97d5c..07b8187 100644
--- a/chrome/browser/resources/local_ntp/custom_backgrounds.js
+++ b/chrome/browser/resources/local_ntp/custom_backgrounds.js
@@ -71,6 +71,7 @@
 customBackgrounds.IDS = {
   ATTRIBUTIONS: 'custom-bg-attr',
   BACK: 'bg-sel-back',
+  BACK_CIRCLE: 'bg-sel-back-circle',
   CANCEL: 'bg-sel-footer-cancel',
   CUSTOM_LINKS_RESTORE_DEFAULT: 'custom-links-restore-default',
   CUSTOM_LINKS_RESTORE_DEFAULT_TEXT: 'custom-links-restore-default-text',
@@ -1070,7 +1071,7 @@
     customBackgrounds.networkStateChanged(false);
   }
 
-  $(customBackgrounds.IDS.BACK)
+  $(customBackgrounds.IDS.BACK_CIRCLE)
       .setAttribute('aria-label', configData.translatedStrings.backLabel);
   $(customBackgrounds.IDS.CANCEL)
       .setAttribute('aria-label', configData.translatedStrings.selectionCancel);
@@ -1222,7 +1223,7 @@
         customBackgrounds.dialogCollectionsSource);
   };
   $(customBackgrounds.IDS.BACK).onclick = backInteraction;
-  $(customBackgrounds.IDS.BACK).onkeyup = function(event) {
+  $(customBackgrounds.IDS.BACK_CIRCLE).onkeyup = function(event) {
     if (event.keyCode === customBackgrounds.KEYCODES.ENTER) {
       backInteraction(event);
     }
diff --git a/chrome/browser/resources/local_ntp/local_ntp.html b/chrome/browser/resources/local_ntp/local_ntp.html
index ed5aeb1..8ed5bf6 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.html
+++ b/chrome/browser/resources/local_ntp/local_ntp.html
@@ -121,8 +121,8 @@
 
   <dialog id="bg-sel-menu">
     <div id="bg-sel-title-bar">
-    <div id="bg-sel-back-circle">
-      <button id="bg-sel-back" tabindex="0"></button>
+    <div id="bg-sel-back-circle" tabindex="0" role="button">
+      <div id="bg-sel-back"></div>
     </div>
     <div id="bg-sel-title"></div>
     </div>
diff --git a/chrome/browser/resources/omnibox/omnibox.js b/chrome/browser/resources/omnibox/omnibox.js
index 6efa513..0e97d18 100644
--- a/chrome/browser/resources/omnibox/omnibox.js
+++ b/chrome/browser/resources/omnibox/omnibox.js
@@ -178,103 +178,6 @@
   ];
 
   /**
-   * Returns an HTML Element of type table row that contains the
-   * headers we'll use for labeling the columns.  If we're in
-   * detailedMode, we use all the headers.  If not, we only use ones
-   * marked displayAlways.
-   */
-  function createAutocompleteResultTableHeader() {
-    let row = document.createElement('tr');
-    let inDetailedMode = $('show-details').checked;
-    PROPERTY_OUTPUT_ORDER.forEach(property => {
-      if (inDetailedMode || property.displayAlways) {
-        let headerCell = document.createElement('th');
-        if (property.urlLabelForHeader !== '') {
-          // Wrap header text in URL.
-          let linkNode = document.createElement('a');
-          linkNode.href = property.urlLabelForHeader;
-          linkNode.textContent = property.header;
-          headerCell.appendChild(linkNode);
-        } else {
-          // Output header text without a URL.
-          headerCell.textContent = property.header;
-          headerCell.className = 'table-header';
-          headerCell.title = property.tooltip;
-        }
-        row.appendChild(headerCell);
-      }
-    });
-    return row;
-  }
-
-  /**
-   * @param {!Object} autocompleteSuggestion the particular
-   *     autocomplete suggestion we're in the process of displaying.
-   * @param {string} propertyName the particular property of the autocomplete
-   *     suggestion that should go in this cell.
-   * @return {Element} that contains the value within this
-   *     autocompleteSuggestion associated with propertyName.
-   */
-  function createCellForPropertyAndRemoveProperty(autocompleteSuggestion, propertyName) {
-    let cell = document.createElement('td');
-    if (propertyName in autocompleteSuggestion) {
-      if (propertyName === 'additionalInfo') {
-        // |additionalInfo| embeds a two-column table of provider-specific data
-        // within this cell. |additionalInfo| is an array of
-        // AutocompleteAdditionalInfo.
-        let additionalInfoTable = document.createElement('table');
-        autocompleteSuggestion[propertyName].forEach(additionalInfo => {
-          let additionalInfoRow = document.createElement('tr');
-
-          // Set the title (name of property) cell text.
-          let propertyCell = document.createElement('td');
-          propertyCell.textContent = additionalInfo.key + ':';
-          propertyCell.className = 'additional-info-property';
-          additionalInfoRow.appendChild(propertyCell);
-
-          // Set the value of the property cell text.
-          let valueCell = document.createElement('td');
-          valueCell.textContent = additionalInfo.value;
-          valueCell.className = 'additional-info-value';
-          additionalInfoRow.appendChild(valueCell);
-
-          additionalInfoTable.appendChild(additionalInfoRow);
-        });
-        cell.appendChild(additionalInfoTable);
-      } else if (typeof autocompleteSuggestion[propertyName] === 'boolean') {
-        // If this is a boolean, display a checkmark or an X instead of
-        // the strings true or false.
-        cell.className =
-            autocompleteSuggestion[propertyName] ? 'check-mark' : 'x-mark';
-      } else {
-        let text = String(autocompleteSuggestion[propertyName]);
-        // If it's a URL wrap it in an href.
-        let re = /^(http|https|ftp|chrome|file):\/\//;
-        if (re.test(text)) {
-          let aCell = document.createElement('a');
-          aCell.textContent = text;
-          aCell.href = text;
-          cell.appendChild(aCell);
-        } else {
-          // All other data types (integer, strings, etc.) display their
-          // normal toString() output.
-          cell.textContent = autocompleteSuggestion[propertyName];
-        }
-      }
-    }  // else: if propertyName is undefined, we leave the cell blank
-    return cell;
-  }
-
-  /**
-   * Appends a paragraph node containing text to the parent node.
-   */
-  function addParagraph(parent, text) {
-    let p = document.createElement('p');
-    p.textContent = text;
-    parent.appendChild(p);
-  }
-
-  /**
    * Appends some human-readable information about the provided
    * autocomplete result to the HTML node with id omnibox-debug-text.
    * The current human-readable form is a few lines about general
@@ -391,7 +294,104 @@
     return table;
   }
 
-  /** Repaints the page based on the contents of the array
+  /**
+   * Returns an HTML Element of type table row that contains the
+   * headers we'll use for labeling the columns.  If we're in
+   * detailedMode, we use all the headers.  If not, we only use ones
+   * marked displayAlways.
+   */
+  function createAutocompleteResultTableHeader() {
+    let row = document.createElement('tr');
+    let inDetailedMode = $('show-details').checked;
+    PROPERTY_OUTPUT_ORDER.forEach(property => {
+      if (inDetailedMode || property.displayAlways) {
+        let headerCell = document.createElement('th');
+        if (property.urlLabelForHeader !== '') {
+          // Wrap header text in URL.
+          let linkNode = document.createElement('a');
+          linkNode.href = property.urlLabelForHeader;
+          linkNode.textContent = property.header;
+          headerCell.appendChild(linkNode);
+        } else {
+          // Output header text without a URL.
+          headerCell.textContent = property.header;
+          headerCell.className = 'table-header';
+          headerCell.title = property.tooltip;
+        }
+        row.appendChild(headerCell);
+      }
+    });
+    return row;
+  }
+
+  /**
+   * @param {!Object} autocompleteSuggestion the particular
+   *     autocomplete suggestion we're in the process of displaying.
+   * @param {string} propertyName the particular property of the autocomplete
+   *     suggestion that should go in this cell.
+   * @return {Element} that contains the value within this
+   *     autocompleteSuggestion associated with propertyName.
+   */
+  function createCellForPropertyAndRemoveProperty(autocompleteSuggestion, propertyName) {
+    let cell = document.createElement('td');
+    if (propertyName in autocompleteSuggestion) {
+      if (propertyName === 'additionalInfo') {
+        // |additionalInfo| embeds a two-column table of provider-specific data
+        // within this cell. |additionalInfo| is an array of
+        // AutocompleteAdditionalInfo.
+        let additionalInfoTable = document.createElement('table');
+        autocompleteSuggestion[propertyName].forEach(additionalInfo => {
+          let additionalInfoRow = document.createElement('tr');
+
+          // Set the title (name of property) cell text.
+          let propertyCell = document.createElement('td');
+          propertyCell.textContent = additionalInfo.key + ':';
+          propertyCell.className = 'additional-info-property';
+          additionalInfoRow.appendChild(propertyCell);
+
+          // Set the value of the property cell text.
+          let valueCell = document.createElement('td');
+          valueCell.textContent = additionalInfo.value;
+          valueCell.className = 'additional-info-value';
+          additionalInfoRow.appendChild(valueCell);
+
+          additionalInfoTable.appendChild(additionalInfoRow);
+        });
+        cell.appendChild(additionalInfoTable);
+      } else if (typeof autocompleteSuggestion[propertyName] === 'boolean') {
+        // If this is a boolean, display a checkmark or an X instead of
+        // the strings true or false.
+        cell.className =
+            autocompleteSuggestion[propertyName] ? 'check-mark' : 'x-mark';
+      } else {
+        let text = String(autocompleteSuggestion[propertyName]);
+        // If it's a URL wrap it in an href.
+        let re = /^(http|https|ftp|chrome|file):\/\//;
+        if (re.test(text)) {
+          let aCell = document.createElement('a');
+          aCell.textContent = text;
+          aCell.href = text;
+          cell.appendChild(aCell);
+        } else {
+          // All other data types (integer, strings, etc.) display their
+          // normal toString() output.
+          cell.textContent = autocompleteSuggestion[propertyName];
+        }
+      }
+    }  // else: if propertyName is undefined, we leave the cell blank
+    return cell;
+  }
+
+  /**
+   * Appends a paragraph node containing text to the parent node.
+   */
+  function addParagraph(parent, text) {
+    let p = document.createElement('p');
+    p.textContent = text;
+    parent.appendChild(p);
+  }
+
+  /* Repaints the page based on the contents of the array
    * progressiveAutocompleteResults, which represents consecutive
    * autocomplete results.  We only display the last (most recent)
    * entry unless we're asked to display incomplete results.  For an
diff --git a/chrome/browser/resources/settings/about_page/about_page.js b/chrome/browser/resources/settings/about_page/about_page.js
index 0274720..e03fda0 100644
--- a/chrome/browser/resources/settings/about_page/about_page.js
+++ b/chrome/browser/resources/settings/about_page/about_page.js
@@ -41,7 +41,10 @@
     regulatoryInfo_: Object,
 
     /** @private */
-    hasEndOfLife_: Boolean,
+    hasEndOfLife_: {
+      type: Boolean,
+      value: false,
+    },
 
     /** @private */
     showCrostini: Boolean,
@@ -72,18 +75,25 @@
     // </if>
 
     /** @private */
-    showUpdateStatus_: Boolean,
+    showUpdateStatus_: {
+      type: Boolean,
+      value: false,
+    },
 
     /** @private */
     showButtonContainer_: Boolean,
 
     /** @private */
-    showRelaunch_: Boolean,
+    showRelaunch_: {
+      type: Boolean,
+      value: false,
+    },
 
     // <if expr="chromeos">
     /** @private */
     showRelaunchAndPowerwash_: {
       type: Boolean,
+      value: false,
       computed: 'computeShowRelaunchAndPowerwash_(' +
           'currentUpdateStatusEvent_, targetChannel_, currentChannel_)',
     },
diff --git a/chrome/browser/safe_browsing/safe_browsing_service_browsertest.cc b/chrome/browser/safe_browsing/safe_browsing_service_browsertest.cc
index aab1e0b..a44e796 100644
--- a/chrome/browser/safe_browsing/safe_browsing_service_browsertest.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_service_browsertest.cc
@@ -72,6 +72,7 @@
 #include "components/safe_browsing/db/v4_get_hash_protocol_manager.h"
 #include "components/safe_browsing/db/v4_protocol_manager_util.h"
 #include "components/safe_browsing/db/v4_test_util.h"
+#include "components/safe_browsing/features.h"
 #include "components/security_interstitials/core/controller_client.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
@@ -133,6 +134,7 @@
 const char kMalwareWebSocketPath[] = "/safe_browsing/malware-ws";
 const char kNeverCompletesPath[] = "/never_completes";
 const char kPrefetchMalwarePage[] = "/safe_browsing/prefetch_malware.html";
+const char kBillingInterstitialPage[] = "/safe_browsing/billing.html";
 
 // TODO(ricea): Use net::test_server::HungResponse instead.
 class NeverCompletingHttpResponse : public net::test_server::HttpResponse {
@@ -387,15 +389,20 @@
   }
 
   void CheckBrowseUrlOnIOThread(const GURL& url) {
+    SBThreatTypeSet threat_types = CreateSBThreatTypeSet(
+        {SB_THREAT_TYPE_URL_PHISHING, SB_THREAT_TYPE_URL_MALWARE,
+         SB_THREAT_TYPE_URL_UNWANTED});
+    if (base::FeatureList::IsEnabled(kBillingInterstitial)) {
+      SBThreatTypeSet billing =
+          CreateSBThreatTypeSet({safe_browsing::SB_THREAT_TYPE_BILLING});
+      threat_types.insert(billing.begin(), billing.end());
+    }
+
     // The async CheckDone() hook will not be called when we have a synchronous
     // safe signal, handle it right away.
     bool synchronous_safe_signal =
         safe_browsing_service_->database_manager()->CheckBrowseUrl(
-            url,
-            CreateSBThreatTypeSet({SB_THREAT_TYPE_URL_PHISHING,
-                                   SB_THREAT_TYPE_URL_MALWARE,
-                                   SB_THREAT_TYPE_URL_UNWANTED}),
-            this);
+            url, threat_types, this);
     if (synchronous_safe_signal) {
       threat_type_ = SB_THREAT_TYPE_SAFE;
       base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI},
@@ -533,6 +540,13 @@
                               ThreatPatternType::NONE);
   }
 
+  // Sets up the prefix database and the full hash cache to match one of the
+  // prefixes for the given URL in the Billing store.
+  void MarkUrlForBillingUnexpired(const GURL& bad_url) {
+    MarkUrlForListIdUnexpired(bad_url, GetUrlBillingId(),
+                              ThreatPatternType::NONE);
+  }
+
   void SetUpCommandLine(base::CommandLine* command_line) override {
 #if defined(OS_CHROMEOS)
     command_line->AppendSwitch(
@@ -994,6 +1008,32 @@
   }
 }
 
+IN_PROC_BROWSER_TEST_F(V4SafeBrowsingServiceTest, CheckBrowseUrlForBilling) {
+  const GURL bad_url = embedded_test_server()->GetURL(kBillingInterstitialPage);
+  {
+    scoped_refptr<TestSBClient> client(new TestSBClient);
+
+    // Since the feature isn't enabled and the URL isn't in the database, it is
+    // considered to be safe.
+    client->CheckBrowseUrl(bad_url);
+    EXPECT_EQ(SB_THREAT_TYPE_SAFE, client->GetThreatType());
+
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitAndEnableFeature(kBillingInterstitial);
+
+    // Since bad_url is not in database, it is considered to be
+    // safe.
+    client->CheckBrowseUrl(bad_url);
+    EXPECT_EQ(SB_THREAT_TYPE_SAFE, client->GetThreatType());
+
+    MarkUrlForBillingUnexpired(bad_url);
+
+    // Now, the bad_url is not safe since it is added to the database.
+    client->CheckBrowseUrl(bad_url);
+    EXPECT_EQ(SB_THREAT_TYPE_BILLING, client->GetThreatType());
+  }
+}
+
 // Parameterised fixture to permit running the same test for Window and Worker
 // scopes.
 class V4SafeBrowsingServiceJsRequestTest
diff --git a/chrome/browser/service_process/service_process_control.cc b/chrome/browser/service_process/service_process_control.cc
index c19fb088..d24e8b78 100644
--- a/chrome/browser/service_process/service_process_control.cc
+++ b/chrome/browser/service_process/service_process_control.cc
@@ -206,6 +206,7 @@
 void ServiceProcessControl::OnProcessLaunched() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (launcher_->launched()) {
+    saved_pid_ = launcher_->saved_pid();
     UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceEvents",
                               SERVICE_EVENT_LAUNCHED, SERVICE_EVENT_MAX);
     // After we have successfully created the service process we try to connect
@@ -382,6 +383,7 @@
 #endif
   process_ = base::LaunchProcess(*cmd_line_, options);
   if (process_.IsValid()) {
+    saved_pid_ = process_.Pid();
     base::PostTaskWithTraits(FROM_HERE, {BrowserThread::IO},
                              base::Bind(&Launcher::DoDetectLaunched, this));
   } else {
diff --git a/chrome/browser/service_process/service_process_control.h b/chrome/browser/service_process/service_process_control.h
index acc95310..c622ed3 100644
--- a/chrome/browser/service_process/service_process_control.h
+++ b/chrome/browser/service_process/service_process_control.h
@@ -113,6 +113,8 @@
     return remote_interfaces_;
   }
 
+  base::ProcessId GetLaunchedPidForTesting() const { return saved_pid_; }
+
  private:
   // This class is responsible for launching the service process on the
   // PROCESS_LAUNCHER thread.
@@ -126,6 +128,7 @@
     void Run(const base::Closure& task);
 
     bool launched() const { return launched_; }
+    base::ProcessId saved_pid() const { return saved_pid_; }
 
    private:
     friend class base::RefCountedThreadSafe<ServiceProcessControl::Launcher>;
@@ -142,6 +145,10 @@
     bool launched_;
     uint32_t retry_count_;
     base::Process process_;
+
+    // Used to save the process id for |process_| upon successful launch.
+    // Only used for testing.
+    base::ProcessId saved_pid_;
   };
 
   friend class MockServiceProcessControl;
@@ -204,6 +211,9 @@
   // If true changes to UpgradeObserver are applied, if false they are ignored.
   bool apply_changes_from_upgrade_observer_;
 
+  // Same as |Launcher::saved_pid_|.
+  base::ProcessId saved_pid_;
+
   base::WeakPtrFactory<ServiceProcessControl> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(ServiceProcessControl);
diff --git a/chrome/browser/service_process/service_process_control_browsertest.cc b/chrome/browser/service_process/service_process_control_browsertest.cc
index 7daefb2..fcc085f 100644
--- a/chrome/browser/service_process/service_process_control_browsertest.cc
+++ b/chrome/browser/service_process/service_process_control_browsertest.cc
@@ -32,6 +32,13 @@
 #include "content/public/test/test_utils.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
+#if defined(OS_WIN)
+#include <Tlhelp32.h>
+#include <windows.h>
+
+#include "base/threading/platform_thread.h"
+#endif
+
 class ServiceProcessControlBrowserTest
     : public InProcessBrowserTest {
  public:
@@ -439,3 +446,66 @@
   EXPECT_CALL(*this, MockHistogramsCallback()).Times(1);
   run_loop.Run();
 }
+
+#if defined(OS_WIN)
+// Test for https://crbug.com/860827 to make sure it is possible to stop the
+// Cloud Print service with WM_QUIT.
+IN_PROC_BROWSER_TEST_F(ServiceProcessControlBrowserTest, StopViaWmQuit) {
+  LaunchServiceProcessControlAndWait();
+
+  // Make sure we are connected to the service process.
+  ASSERT_TRUE(ServiceProcessControl::GetInstance()->IsConnected());
+  cloud_print::mojom::CloudPrintPtr cloud_print_proxy;
+  ServiceProcessControl::GetInstance()->remote_interfaces().GetInterface(
+      &cloud_print_proxy);
+  base::RunLoop run_loop;
+  cloud_print_proxy->GetCloudPrintProxyInfo(
+      base::BindOnce([](base::OnceClosure done, bool, const std::string&,
+                        const std::string&) { std::move(done).Run(); },
+                     run_loop.QuitClosure()));
+  run_loop.Run();
+
+  base::ProcessId pid =
+      ServiceProcessControl::GetInstance()->GetLaunchedPidForTesting();
+  base::Process process = base::Process::Open(pid);
+  ASSERT_TRUE(process.IsValid());
+
+  // Find the first thread associated with |pid|.
+  base::PlatformThreadId tid = 0;
+  {
+    HANDLE snapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, NULL);
+    ASSERT_NE(INVALID_HANDLE_VALUE, snapshot);
+
+    THREADENTRY32 thread_entry = {0};
+    thread_entry.dwSize = sizeof(THREADENTRY32);
+
+    BOOL result = ::Thread32First(snapshot, &thread_entry);
+    while (result) {
+      if (thread_entry.th32OwnerProcessID == pid) {
+        tid = thread_entry.th32ThreadID;
+        break;
+      }
+      result = Thread32Next(snapshot, &thread_entry);
+    }
+  }
+  ASSERT_NE(base::kInvalidThreadId, tid);
+
+  // And then shutdown the service process via WM_QUIT.
+  ASSERT_TRUE(::PostThreadMessage(tid, WM_QUIT, 0, 0));
+
+  // And wait for it to stop running.
+  constexpr int kRetries = 5;
+  for (int retry = 0; retry < kRetries; ++retry) {
+    if (!process.IsRunning()) {
+      // |process| stopped running. Test is done.
+      return;
+    }
+
+    // |process| did not stop running. Wait.
+    base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1));
+  }
+
+  // |process| still did not stop running after |kRetries|.
+  FAIL();
+}
+#endif  // defined(OS_WIN)
diff --git a/chrome/browser/sync/test/integration/two_client_printers_sync_test.cc b/chrome/browser/sync/test/integration/two_client_printers_sync_test.cc
index e827dcd..d631943 100644
--- a/chrome/browser/sync/test/integration/two_client_printers_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_printers_sync_test.cc
@@ -8,6 +8,7 @@
 #include "base/run_loop.h"
 #include "build/build_config.h"
 #include "chrome/browser/sync/test/integration/printers_helper.h"
+#include "chrome/browser/sync/test/integration/profile_sync_service_harness.h"
 #include "chrome/browser/sync/test/integration/sync_test.h"
 #include "content/public/test/test_utils.h"
 
@@ -119,31 +120,79 @@
   // Store 0 and 1 have 3 printers now.
   ASSERT_TRUE(PrintersMatchChecker().Wait());
 
+  // Client 1 makes a local change.
   ASSERT_TRUE(
       EditPrinterDescription(GetPrinterStore(1), 0, kOverwrittenDescription));
 
-  // Wait for a non-zero period (200ms).
+  // Wait for a non-zero period (200ms) for modification timestamps to differ.
   base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
 
-  // Run all tasks so the first description change is applied.
-  // TODO(crbug.com/810408): This is a temporary fix to prevent flakiness. Tasks
-  // shouldn't run between description changes, because it prevents a real
-  // conflict from happening.
-  content::RunAllTasksUntilIdle();
+  // Client 0 goes offline, to make this test deterministic (client 1 commits
+  // first).
+  GetClient(0)->StopSyncService(syncer::SyncService::KEEP_DATA);
 
+  // Client 0 makes a change while offline.
+  ASSERT_TRUE(
+      EditPrinterDescription(GetPrinterStore(0), 0, kLatestDescription));
+
+  // We must wait until the sync cycle is completed before client 0 goes online
+  // in order to make the outcome of conflict resolution deterministic (needed
+  // due to lack of a strong consistency model on the server).
+  ProfileSyncServiceHarness::AwaitQuiescence({GetClient(1)});
+
+  ASSERT_EQ(GetPrinterStore(0)->GetConfiguredPrinters()[0].description(),
+            kLatestDescription);
+  ASSERT_EQ(GetPrinterStore(1)->GetConfiguredPrinters()[0].description(),
+            kOverwrittenDescription);
+
+  // Client 0 goes online, which results in a conflict (local wins).
+  GetClient(0)->StartSyncService();
+
+  // Run tasks until the most recent update has been applied to all stores.
+  ASSERT_TRUE(PrintersMatchChecker().Wait());
+
+  EXPECT_EQ(GetPrinterStore(0)->GetConfiguredPrinters()[0].description(),
+            kLatestDescription);
+  EXPECT_EQ(GetPrinterStore(1)->GetConfiguredPrinters()[0].description(),
+            kLatestDescription);
+}
+
+IN_PROC_BROWSER_TEST_F(TwoClientPrintersSyncTest,
+                       ConflictResolutionWithStrongConsistency) {
+  ASSERT_TRUE(SetupSync());
+  GetFakeServer()->EnableStrongConsistencyWithConflictDetectionModel();
+
+  AddPrinter(GetPrinterStore(0), CreateTestPrinter(0));
+  AddPrinter(GetPrinterStore(0), CreateTestPrinter(2));
+  AddPrinter(GetPrinterStore(0), CreateTestPrinter(3));
+
+  // Store 0 and 1 have 3 printers now.
+  ASSERT_TRUE(PrintersMatchChecker().Wait());
+
+  // Client 1 makes a local change.
+  ASSERT_TRUE(
+      EditPrinterDescription(GetPrinterStore(1), 0, kOverwrittenDescription));
+
+  // Wait for a non-zero period (200ms) for modification timestamps to differ.
+  base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
+
+  // Client 0 makes a change to the same printer.
   ASSERT_TRUE(
       EditPrinterDescription(GetPrinterStore(0), 0, kLatestDescription));
 
   // Run tasks until the most recent update has been applied to all stores.
-  AwaitMatchStatusChangeChecker wait_latest_description_propagated(
-      base::BindRepeating([]() {
-        return GetPrinterStore(0)->GetConfiguredPrinters()[0].description() ==
-                   kLatestDescription &&
-               GetPrinterStore(1)->GetConfiguredPrinters()[0].description() ==
-                   kLatestDescription;
-      }),
-      "Description not propagated");
-  ASSERT_TRUE(wait_latest_description_propagated.Wait());
+  // One of the two clients (the second one committing) will be requested by the
+  // server to resolve the conflict and recommit. The custom conflict resolution
+  // as implemented in PrintersSyncBridge::ResolveConflict() should guarantee
+  // that the one with latest modification timestamp (kLatestDescription) wins,
+  // which can mean local wins or remote wins, depending on which client is
+  // involved.
+  ASSERT_TRUE(PrintersMatchChecker().Wait());
+
+  EXPECT_EQ(GetPrinterStore(0)->GetConfiguredPrinters()[0].description(),
+            kLatestDescription);
+  EXPECT_EQ(GetPrinterStore(1)->GetConfiguredPrinters()[0].description(),
+            kLatestDescription);
 }
 
 IN_PROC_BROWSER_TEST_F(TwoClientPrintersSyncTest, SimpleMerge) {
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 8443b6b6..d0db65d24 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1670,6 +1670,8 @@
       "webui/chromeos/login/l10n_util.h",
       "webui/chromeos/login/marketing_opt_in_screen_handler.cc",
       "webui/chromeos/login/marketing_opt_in_screen_handler.h",
+      "webui/chromeos/login/multidevice_setup_screen_handler.cc",
+      "webui/chromeos/login/multidevice_setup_screen_handler.h",
       "webui/chromeos/login/native_window_delegate.h",
       "webui/chromeos/login/network_dropdown_handler.cc",
       "webui/chromeos/login/network_dropdown_handler.h",
@@ -1946,6 +1948,17 @@
       "webui/welcome/welcome_ui.h",
     ]
 
+    # TODO(scottchen): Remove if-check once nux files are non-exclusive
+    #     to windows.
+    if (is_win && is_chrome_branded) {
+      deps += [
+        "//chrome/browser/ui/webui/welcome/nux:constants",
+        "//chrome/browser/ui/webui/welcome/nux:email_feature",
+        "//chrome/browser/ui/webui/welcome/nux:google_apps_feature",
+        "//chrome/browser/ui/webui/welcome/nux:set_as_default_feature",
+      ]
+    }
+
     if (enable_dice_support) {
       sources += [
         "webui/signin/dice_turn_sync_on_helper.cc",
@@ -3056,8 +3069,6 @@
     sources += [
       "aura/accessibility/automation_manager_aura.cc",
       "aura/accessibility/automation_manager_aura.h",
-      "aura/accessibility/ax_root_obj_wrapper.cc",
-      "aura/accessibility/ax_root_obj_wrapper.h",
       "aura/accessibility/ax_tree_source_aura.cc",
       "aura/accessibility/ax_tree_source_aura.h",
       "aura/native_window_tracker_aura.cc",
@@ -3480,15 +3491,6 @@
     deps += [ "//components/nacl/browser" ]
   }
 
-  if (is_win && is_chrome_branded) {
-    deps += [
-      "//components/nux:constants",
-      "//components/nux:email_feature",
-      "//components/nux:google_apps_feature",
-      "//components/nux:set_as_default_feature",
-    ]
-  }
-
   if (enable_plugins) {
     sources += [
       "hung_plugin_tab_helper.cc",
diff --git a/chrome/browser/ui/aura/accessibility/automation_manager_aura.h b/chrome/browser/ui/aura/accessibility/automation_manager_aura.h
index c7a4695..f40e8a8105 100644
--- a/chrome/browser/ui/aura/accessibility/automation_manager_aura.h
+++ b/chrome/browser/ui/aura/accessibility/automation_manager_aura.h
@@ -29,16 +29,14 @@
 class View;
 }  // namespace views
 
-using AuraAXTreeSerializer =
-    ui::AXTreeSerializer<views::AXAuraObjWrapper*,
-                         ui::AXNodeData,
-                         ui::AXTreeData>;
+using AuraAXTreeSerializer = ui::
+    AXTreeSerializer<views::AXAuraObjWrapper*, ui::AXNodeData, ui::AXTreeData>;
 
 struct ExtensionMsg_AccessibilityEventBundleParams;
 
 // Manages a tree of automation nodes.
 class AutomationManagerAura : public ui::AXHostDelegate,
-                              views::AXAuraObjCache::Delegate {
+                              public views::AXAuraObjCache::Delegate {
  public:
   // Get the single instance of this class.
   static AutomationManagerAura* GetInstance();
diff --git a/chrome/browser/ui/aura/accessibility/ax_tree_source_aura.cc b/chrome/browser/ui/aura/accessibility/ax_tree_source_aura.cc
index 1df4d3c7..a8ff294 100644
--- a/chrome/browser/ui/aura/accessibility/ax_tree_source_aura.cc
+++ b/chrome/browser/ui/aura/accessibility/ax_tree_source_aura.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/aura/accessibility/ax_tree_source_aura.h"
 
+#include "chrome/browser/ui/aura/accessibility/automation_manager_aura.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "ui/accessibility/ax_tree_id.h"
@@ -12,7 +13,8 @@
 #include "ui/views/controls/webview/webview.h"
 
 AXTreeSourceAura::AXTreeSourceAura()
-    : desktop_root_(std::make_unique<AXRootObjWrapper>()) {}
+    : desktop_root_(std::make_unique<AXRootObjWrapper>(
+          AutomationManagerAura::GetInstance())) {}
 
 AXTreeSourceAura::~AXTreeSourceAura() = default;
 
diff --git a/chrome/browser/ui/aura/accessibility/ax_tree_source_aura.h b/chrome/browser/ui/aura/accessibility/ax_tree_source_aura.h
index fdd4488..3031c84 100644
--- a/chrome/browser/ui/aura/accessibility/ax_tree_source_aura.h
+++ b/chrome/browser/ui/aura/accessibility/ax_tree_source_aura.h
@@ -8,7 +8,7 @@
 #include <memory>
 
 #include "base/macros.h"
-#include "chrome/browser/ui/aura/accessibility/ax_root_obj_wrapper.h"
+#include "ui/views/accessibility/ax_root_obj_wrapper.h"
 #include "ui/views/accessibility/ax_tree_source_views.h"
 
 // This class exposes the views hierarchy as an accessibility tree permitting
diff --git a/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc b/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc
index ebaca986..7ab10e5 100644
--- a/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc
+++ b/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc
@@ -494,13 +494,11 @@
   // Check that the new popup has (roughly) the requested size.
   gfx::Size window_size = popup->GetContainerBounds().size();
   EXPECT_TRUE(349 <= window_size.width() && window_size.width() <= 351);
-#if defined(OS_MACOSX)
+#if !defined(OS_MACOSX)
   // Window height computation is off in MacViews: https://crbug.com/846329
-  if (!views_mode_controller::IsViewsBrowserCocoa())
-    return;
-#endif
   EXPECT_GE(window_size.height(), 249);
   EXPECT_LE(window_size.height(), 253);
+#endif
 }
 
 IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest, CorrectReferrer) {
diff --git a/chrome/browser/ui/cocoa/touchbar/web_textfield_touch_bar_controller.mm b/chrome/browser/ui/cocoa/touchbar/web_textfield_touch_bar_controller.mm
index d6a311f..76a9101 100644
--- a/chrome/browser/ui/cocoa/touchbar/web_textfield_touch_bar_controller.mm
+++ b/chrome/browser/ui/cocoa/touchbar/web_textfield_touch_bar_controller.mm
@@ -23,12 +23,6 @@
 @implementation WebTextfieldTouchBarController
 
 + (WebTextfieldTouchBarController*)controllerForWindow:(NSWindow*)window {
-  if (features::IsViewsBrowserCocoa()) {
-    BrowserWindowController* bwc =
-        [BrowserWindowController browserWindowControllerForWindow:window];
-    return [[bwc browserWindowTouchBarController] webTextfieldTouchBar];
-  }
-
   BrowserView* browser_view =
       BrowserView::GetBrowserViewForNativeWindow(window);
   if (!browser_view)
diff --git a/chrome/browser/ui/startup/startup_tab_provider.cc b/chrome/browser/ui/startup/startup_tab_provider.cc
index 07969a7..d0e80b8 100644
--- a/chrome/browser/ui/startup/startup_tab_provider.cc
+++ b/chrome/browser/ui/startup/startup_tab_provider.cc
@@ -28,7 +28,7 @@
 #endif  // defined(OS_WIN)
 
 #if defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)
-#include "components/nux/constants.h"
+#include "chrome/browser/ui/webui/welcome/nux/constants.h"
 #endif  // defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)
 
 namespace {
diff --git a/chrome/browser/ui/startup/startup_tab_provider_unittest.cc b/chrome/browser/ui/startup/startup_tab_provider_unittest.cc
index bc8b9c7b..ff4bf48 100644
--- a/chrome/browser/ui/startup/startup_tab_provider_unittest.cc
+++ b/chrome/browser/ui/startup/startup_tab_provider_unittest.cc
@@ -15,7 +15,7 @@
 #if defined(OS_WIN)
 #include "base/win/windows_version.h"
 #if defined(GOOGLE_CHROME_BUILD)
-#include "components/nux/constants.h"
+#include "chrome/browser/ui/webui/welcome/nux/constants.h"
 #endif  // defined(GOOGLE_CHROME_BUILD)
 #endif  // defined(OS_WIN)
 
diff --git a/chrome/browser/ui/views/download/download_shelf_view.cc b/chrome/browser/ui/views/download/download_shelf_view.cc
index 4fbb794..5aa4ba7 100644
--- a/chrome/browser/ui/views/download/download_shelf_view.cc
+++ b/chrome/browser/ui/views/download/download_shelf_view.cc
@@ -119,12 +119,10 @@
   new_item_animation_.Show();
 }
 
-void DownloadShelfView::DoAddDownload(DownloadItem* download) {
-  DownloadUIModel::DownloadUIModelPtr model(
-      new DownloadItemModel(download),
-      base::OnTaskRunnerDeleter(base::ThreadTaskRunnerHandle::Get()));
+void DownloadShelfView::DoAddDownload(
+    DownloadUIModel::DownloadUIModelPtr download) {
   AddDownloadView(
-      new DownloadItemView(std::move(model), this, accessible_alert_));
+      new DownloadItemView(std::move(download), this, accessible_alert_));
 }
 
 void DownloadShelfView::MouseMovedOutOfHost() {
diff --git a/chrome/browser/ui/views/download/download_shelf_view.h b/chrome/browser/ui/views/download/download_shelf_view.h
index 0a84c66..412e3ab 100644
--- a/chrome/browser/ui/views/download/download_shelf_view.h
+++ b/chrome/browser/ui/views/download/download_shelf_view.h
@@ -27,10 +27,6 @@
 class PageNavigator;
 }
 
-namespace download {
-class DownloadItem;
-}
-
 namespace views {
 class ImageButton;
 class MdTextButton;
@@ -96,7 +92,7 @@
 
  protected:
   // DownloadShelf:
-  void DoAddDownload(download::DownloadItem* download) override;
+  void DoAddDownload(DownloadUIModel::DownloadUIModelPtr download) override;
   void DoOpen() override;
   void DoClose(CloseReason reason) override;
   void DoHide() override;
diff --git a/chrome/browser/ui/views/extensions/extension_installed_bubble_view.cc b/chrome/browser/ui/views/extensions/extension_installed_bubble_view.cc
index 3a5036c..8f58e665 100644
--- a/chrome/browser/ui/views/extensions/extension_installed_bubble_view.cc
+++ b/chrome/browser/ui/views/extensions/extension_installed_bubble_view.cc
@@ -25,8 +25,12 @@
 #include "chrome/browser/ui/sync/bubble_sync_promo_delegate.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/frame/app_menu_button.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
+#include "chrome/browser/ui/views/location_bar/location_icon_view.h"
 #include "chrome/browser/ui/views/sync/bubble_sync_promo_view.h"
-#include "chrome/browser/ui/views_mode_controller.h"
+#include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
+#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
@@ -44,14 +48,6 @@
 #include "ui/views/controls/link_listener.h"
 #include "ui/views/layout/box_layout.h"
 
-#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER)
-#include "chrome/browser/ui/views/frame/browser_view.h"
-#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
-#include "chrome/browser/ui/views/location_bar/location_icon_view.h"
-#include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
-#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
-#endif
-
 #if BUILDFLAG(ENABLE_DICE_SUPPORT)
 #include "chrome/browser/signin/account_consistency_mode_manager.h"
 #include "chrome/browser/ui/views/sync/dice_bubble_sync_promo_view.h"
@@ -73,15 +69,8 @@
   return label;
 }
 
-#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER)
 views::View* AnchorViewForBrowser(ExtensionInstalledBubble* controller,
                                   Browser* browser) {
-// The Cocoa browser always needs to use an anchor point.
-#if BUILDFLAG(MAC_VIEWS_BROWSER)
-  if (views_mode_controller::IsViewsBrowserCocoa())
-    return nullptr;
-#endif
-
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
   views::View* reference_view = nullptr;
 
@@ -110,14 +99,6 @@
     return browser_view->toolbar_button_provider()->GetAppMenuButton();
   return reference_view;
 }
-#else  // OS_MACOSX && !MAC_VIEWS_BROWSER
-// Always use an anchor point in non-Views Mac builds. This needs a separate
-// implementation because non-Views Mac builds can't even reference BrowserView.
-views::View* AnchorViewForBrowser(ExtensionInstalledBubble* controller,
-                                  Browser* browser) {
-  return nullptr;
-}
-#endif
 
 }  // namespace
 
diff --git a/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_bubble_view.h b/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_bubble_view.h
index 4f61263..1246f4d 100644
--- a/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_bubble_view.h
+++ b/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_bubble_view.h
@@ -6,7 +6,7 @@
 #define CHROME_BROWSER_UI_VIEWS_FEATURE_PROMOS_BOOKMARK_BAR_PROMO_BUBBLE_VIEW_H_
 
 #include "base/macros.h"
-#include "components/nux/show_promo_delegate.h"
+#include "chrome/browser/ui/webui/welcome/nux/show_promo_delegate.h"
 
 namespace bookmarks {
 class BookmarkNode;
diff --git a/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_dialog_browsertest.cc b/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_dialog_browsertest.cc
index a4d4a0f..26e44b0 100644
--- a/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_dialog_browsertest.cc
+++ b/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_dialog_browsertest.cc
@@ -7,6 +7,7 @@
 #include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h"
 #include "chrome/browser/ui/views/feature_promos/bookmark_bar_promo_bubble_view.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/grit/generated_resources.h"
 #include "components/bookmarks/browser/bookmark_model.h"
 #include "components/bookmarks/common/bookmark_pref_names.h"
 #include "components/bookmarks/test/bookmark_test_helpers.h"
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc
index 6980e2a..ae43dac 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc
@@ -20,9 +20,7 @@
 #include "ash/public/cpp/window_properties.h"
 #include "ash/public/interfaces/constants.mojom.h"
 #include "ash/public/interfaces/window_state_type.mojom.h"
-#include "ash/shell.h"
-#include "ash/wm/overview/window_selector_controller.h"
-#include "ash/wm/window_util.h"
+#include "ash/wm/window_util.h"  // mash-ok
 #include "base/command_line.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/app/chrome_command_ids.h"
@@ -125,7 +123,6 @@
   } else {
     ash::wm::InstallResizeHandleWindowTargeterForWindow(
         frame->GetNativeWindow());
-    ash::Shell::Get()->AddShellObserver(this);
   }
 
   // The ServiceManagerConnection may be nullptr in tests.
@@ -150,9 +147,6 @@
       browser_view()->immersive_mode_controller();
   if (immersive_controller)
     immersive_controller->RemoveObserver(this);
-
-  if (!features::IsUsingWindowService())
-    ash::Shell::Get()->RemoveShellObserver(this);
 }
 
 void BrowserNonClientFrameViewAsh::Init() {
@@ -197,8 +191,7 @@
           ? false
           : true);
 
-  window_observer_.Add(
-      features::IsUsingWindowService() ? window->GetRootWindow() : window);
+  window_observer_.Add(GetFrameWindow());
 
   // To preserve privacy, tag incognito windows so that they won't be included
   // in screenshot sent to assistant server.
@@ -262,7 +255,7 @@
     // The header isn't painted for restored popup/app windows in overview mode,
     // but the inset is still calculated below, so the overview code can align
     // the window content with a fake header.
-    if (!in_overview_mode_ || frame()->IsFullscreen() ||
+    if (!IsInOverviewMode() || frame()->IsFullscreen() ||
         browser_view()->IsTabStripVisible()) {
       return 0;
     }
@@ -490,19 +483,6 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-// ash::ShellObserver:
-
-void BrowserNonClientFrameViewAsh::OnOverviewModeStarting() {
-  in_overview_mode_ = true;
-  OnOverviewOrSplitviewModeChanged();
-}
-
-void BrowserNonClientFrameViewAsh::OnOverviewModeEnded() {
-  in_overview_mode_ = false;
-  OnOverviewOrSplitviewModeChanged();
-}
-
-///////////////////////////////////////////////////////////////////////////////
 // ash::mojom::TabletModeClient:
 
 void BrowserNonClientFrameViewAsh::OnTabletModeToggled(bool enabled) {
@@ -612,11 +592,12 @@
 void BrowserNonClientFrameViewAsh::OnWindowPropertyChanged(aura::Window* window,
                                                            const void* key,
                                                            intptr_t old) {
-  if (key != aura::client::kShowStateKey)
-    return;
-
-  frame_header_->OnShowStateChanged(
-      window->GetProperty(aura::client::kShowStateKey));
+  if (key == aura::client::kShowStateKey) {
+    frame_header_->OnShowStateChanged(
+        window->GetProperty(aura::client::kShowStateKey));
+  } else if (key == ash::kIsShowingInOverviewKey) {
+    OnOverviewOrSplitviewModeChanged();
+  }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -692,7 +673,7 @@
     return false;
   }
 
-  return !in_overview_mode_ ||
+  return !IsInOverviewMode() ||
          IsSnappedInSplitView(frame()->GetNativeWindow(), split_view_state_);
 }
 
@@ -721,7 +702,7 @@
     return false;
 
   // Do not paint for V1 apps in overview mode.
-  return browser_view()->IsBrowserTypeNormal() || !in_overview_mode_;
+  return browser_view()->IsBrowserTypeNormal() || !IsInOverviewMode();
 }
 
 void BrowserNonClientFrameViewAsh::OnOverviewOrSplitviewModeChanged() {
@@ -825,6 +806,18 @@
 
 ws::Id BrowserNonClientFrameViewAsh::GetServerWindowId() const {
   DCHECK(features::IsUsingWindowService());
-  return aura::WindowMus::Get(GetWidget()->GetNativeWindow()->GetRootWindow())
-      ->server_id();
+  return aura::WindowMus::Get(GetFrameWindow())->server_id();
+}
+
+bool BrowserNonClientFrameViewAsh::IsInOverviewMode() const {
+  return GetFrameWindow()->GetProperty(ash::kIsShowingInOverviewKey);
+}
+
+const aura::Window* BrowserNonClientFrameViewAsh::GetFrameWindow() const {
+  return const_cast<BrowserNonClientFrameViewAsh*>(this)->GetFrameWindow();
+}
+
+aura::Window* BrowserNonClientFrameViewAsh::GetFrameWindow() {
+  aura::Window* window = frame()->GetNativeWindow();
+  return features::IsUsingWindowService() ? window->GetRootWindow() : window;
 }
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h
index 1a114d1..306bcbe 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h
@@ -9,7 +9,6 @@
 
 #include "ash/public/interfaces/ash_window_manager.mojom.h"
 #include "ash/public/interfaces/split_view.mojom.h"
-#include "ash/shell_observer.h"
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/scoped_observer.h"
@@ -43,7 +42,6 @@
 class BrowserNonClientFrameViewAsh
     : public BrowserNonClientFrameView,
       public BrowserFrameHeaderAsh::AppearanceProvider,
-      public ash::ShellObserver,
       public TabletModeClientObserver,
       public TabIconViewModel,
       public CommandObserver,
@@ -96,10 +94,6 @@
   int GetFrameHeaderImageYInset() override;
   gfx::ImageSkia GetFrameHeaderOverlayImage(bool active) override;
 
-  // ash::ShellObserver:
-  void OnOverviewModeStarting() override;
-  void OnOverviewModeEnded() override;
-
   // TabletModeClientObserver:
   void OnTabletModeToggled(bool enabled) override;
 
@@ -153,8 +147,8 @@
                            AvatarDisplayOnTeleportedWindow);
   FRIEND_TEST_ALL_PREFIXES(BrowserNonClientFrameViewAshTest,
                            HeaderVisibilityInOverviewAndSplitview);
-  FRIEND_TEST_ALL_PREFIXES(NonHomeLauncherBrowserNonClientFrameViewAshTest,
-                           HeaderHeightForSnappedBrowserInSplitView);
+  FRIEND_TEST_ALL_PREFIXES(BrowserNonClientFrameViewAshTest,
+                           ImmersiveModeTopViewInset);
   FRIEND_TEST_ALL_PREFIXES(BrowserNonClientFrameViewAshBackButtonTest,
                            V1BackButton);
   FRIEND_TEST_ALL_PREFIXES(BrowserNonClientFrameViewAshTest,
@@ -169,6 +163,8 @@
                            TabletModeBrowserCaptionButtonVisibility);
   FRIEND_TEST_ALL_PREFIXES(HomeLauncherBrowserNonClientFrameViewAshTest,
                            TabletModeAppCaptionButtonVisibility);
+  FRIEND_TEST_ALL_PREFIXES(NonHomeLauncherBrowserNonClientFrameViewAshTest,
+                           HeaderHeightForSnappedBrowserInSplitView);
 
   friend class HostedAppNonClientFrameViewAshTest;
   friend class ImmersiveModeControllerAshHostedAppBrowserTest;
@@ -208,6 +204,13 @@
 
   ws::Id GetServerWindowId() const;
 
+  // Returns whether this window is currently in the overview list.
+  bool IsInOverviewMode() const;
+
+  // Returns the top level aura::Window for this browser window.
+  const aura::Window* GetFrameWindow() const;
+  aura::Window* GetFrameWindow();
+
   // View which contains the window controls.
   ash::FrameCaptionButtonContainerView* caption_button_container_ = nullptr;
 
@@ -239,12 +242,6 @@
 
   ScopedObserver<aura::Window, aura::WindowObserver> window_observer_{this};
 
-  // Indicates whether overview mode is active. Hide the header for V1 apps in
-  // overview mode because a fake header is added for better UX. If also in
-  // immersive mode before entering overview mode, the flag will be ignored
-  // because the reveal lock will determine the show/hide header.
-  bool in_overview_mode_ = false;
-
   // Maintains the current split view state.
   ash::mojom::SplitViewState split_view_state_ =
       ash::mojom::SplitViewState::NO_SNAP;
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
index c1fe5d6..4163dda2 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
@@ -343,18 +343,14 @@
   BrowserNonClientFrameViewAsh* frame_view = GetFrameViewAsh(browser_view);
 
   const gfx::Rect initial = frame_view->caption_button_container_->bounds();
-  ash::TabletModeController* tablet_mode_controller =
-      ash::Shell::Get()->tablet_mode_controller();
-  tablet_mode_controller->EnableTabletModeWindowManager(true);
-  tablet_mode_controller->FlushForTesting();
+  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(true));
   ash::FrameCaptionButtonContainerView::TestApi test(
       frame_view->caption_button_container_);
   test.EndAnimations();
   const gfx::Rect during_maximize =
       frame_view->caption_button_container_->bounds();
   EXPECT_GT(initial.width(), during_maximize.width());
-  tablet_mode_controller->EnableTabletModeWindowManager(false);
-  tablet_mode_controller->FlushForTesting();
+  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(false));
   test.EndAnimations();
   const gfx::Rect after_restore =
       frame_view->caption_button_container_->bounds();
@@ -1078,10 +1074,10 @@
 
   // Test that the header is invisible for the browser window in overview mode
   // and visible when not in overview mode.
-  ash::Shell* shell = ash::Shell::Get();
-  shell->window_selector_controller()->ToggleOverview();
+  frame_view->GetFrameWindow()->SetProperty(ash::kIsShowingInOverviewKey, true);
   EXPECT_FALSE(frame_view->caption_button_container_->visible());
-  shell->window_selector_controller()->ToggleOverview();
+  frame_view->GetFrameWindow()->SetProperty(ash::kIsShowingInOverviewKey,
+                                            false);
   EXPECT_TRUE(frame_view->caption_button_container_->visible());
 
   // Create another browser window.
@@ -1105,6 +1101,7 @@
   // snapped browser window, but invisible for the browser window still in
   // overview mode.
   ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(true));
+  ash::Shell* shell = ash::Shell::Get();
   ash::SplitViewController* split_view_controller =
       shell->split_view_controller();
   split_view_controller->BindRequest(
@@ -1118,7 +1115,9 @@
   frame_view->split_view_controller_.FlushForTesting();
   frame_view2->split_view_controller_.FlushForTesting();
 
-  shell->window_selector_controller()->ToggleOverview();
+  frame_view->GetFrameWindow()->SetProperty(ash::kIsShowingInOverviewKey, true);
+  frame_view2->GetFrameWindow()->SetProperty(ash::kIsShowingInOverviewKey,
+                                             true);
   split_view_controller->SnapWindow(widget->GetNativeWindow(),
                                     ash::SplitViewController::LEFT);
   frame_view->split_view_controller_.FlushForTesting();
@@ -1137,7 +1136,10 @@
   // Toggle overview mode while splitview mode is active. Test that the header
   // is visible for the snapped browser window but not for the other browser
   // window in overview mode.
-  shell->window_selector_controller()->ToggleOverview();
+  frame_view->GetFrameWindow()->SetProperty(ash::kIsShowingInOverviewKey,
+                                            false);
+  frame_view2->GetFrameWindow()->SetProperty(ash::kIsShowingInOverviewKey,
+                                             true);
   frame_view->split_view_controller_.FlushForTesting();
   frame_view2->split_view_controller_.FlushForTesting();
   EXPECT_TRUE(frame_view->caption_button_container_->visible());
@@ -1175,6 +1177,7 @@
   params.initial_show_state = ui::SHOW_STATE_DEFAULT;
   Browser* browser = new Browser(params);
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
+  BrowserNonClientFrameViewAsh* frame_view = GetFrameViewAsh(browser_view);
   ImmersiveModeController* immersive_mode_controller =
       browser_view->immersive_mode_controller();
   aura::Window* window = browser->window()->GetNativeWindow();
@@ -1202,16 +1205,12 @@
   EXPECT_FALSE(immersive_mode_controller->IsEnabled());
   EXPECT_LT(0, window->GetProperty(aura::client::kTopViewInset));
 
-  // TODO(estade): deal with overview mode in Mash.
-  if (!features::IsUsingWindowService()) {
-    // The kTopViewInset is the same as in overview mode.
-    const int inset_normal = window->GetProperty(aura::client::kTopViewInset);
-    EXPECT_TRUE(
-        ash::Shell::Get()->window_selector_controller()->ToggleOverview());
-    const int inset_in_overview_mode =
-        window->GetProperty(aura::client::kTopViewInset);
-    EXPECT_EQ(inset_normal, inset_in_overview_mode);
-  }
+  // The kTopViewInset is the same as in overview mode.
+  const int inset_normal = window->GetProperty(aura::client::kTopViewInset);
+  frame_view->GetFrameWindow()->SetProperty(ash::kIsShowingInOverviewKey, true);
+  const int inset_in_overview_mode =
+      window->GetProperty(aura::client::kTopViewInset);
+  EXPECT_EQ(inset_normal, inset_in_overview_mode);
 }
 
 namespace {
@@ -1274,20 +1273,16 @@
   BrowserNonClientFrameViewAsh* frame_view = GetFrameViewAsh(browser_view);
 
   EXPECT_TRUE(frame_view->caption_button_container_->visible());
-  ash::Shell* shell = ash::Shell::Get();
-  ash::TabletModeController* tablet_mode_controller =
-      shell->tablet_mode_controller();
-  tablet_mode_controller->EnableTabletModeWindowManager(true);
-  tablet_mode_controller->FlushForTesting();
+  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(true));
   EXPECT_FALSE(frame_view->caption_button_container_->visible());
 
-  shell->window_selector_controller()->ToggleOverview();
+  frame_view->GetFrameWindow()->SetProperty(ash::kIsShowingInOverviewKey, true);
   EXPECT_FALSE(frame_view->caption_button_container_->visible());
-  shell->window_selector_controller()->ToggleOverview();
+  frame_view->GetFrameWindow()->SetProperty(ash::kIsShowingInOverviewKey,
+                                            false);
   EXPECT_FALSE(frame_view->caption_button_container_->visible());
 
-  tablet_mode_controller->EnableTabletModeWindowManager(false);
-  tablet_mode_controller->FlushForTesting();
+  ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(false));
   EXPECT_TRUE(frame_view->caption_button_container_->visible());
 }
 
@@ -1311,15 +1306,11 @@
   ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(true));
   EXPECT_TRUE(frame_view->caption_button_container_->visible());
 
-  // TODO(estade): deal with overview mode in Mash.
-  if (features::IsUsingWindowService())
-    return;
-
   // However, overview mode does.
-  ash::Shell* shell = ash::Shell::Get();
-  shell->window_selector_controller()->ToggleOverview();
+  frame_view->GetFrameWindow()->SetProperty(ash::kIsShowingInOverviewKey, true);
   EXPECT_FALSE(frame_view->caption_button_container_->visible());
-  shell->window_selector_controller()->ToggleOverview();
+  frame_view->GetFrameWindow()->SetProperty(ash::kIsShowingInOverviewKey,
+                                            false);
   EXPECT_TRUE(frame_view->caption_button_container_->visible());
 
   ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(false));
@@ -1356,7 +1347,7 @@
   frame_view->split_view_controller_.FlushForTesting();
 
   ASSERT_NO_FATAL_FAILURE(test::SetAndWaitForTabletMode(true));
-  shell->window_selector_controller()->ToggleOverview();
+  frame_view->GetFrameWindow()->SetProperty(ash::kIsShowingInOverviewKey, true);
   split_view_controller->SnapWindow(widget->GetNativeWindow(),
                                     ash::SplitViewController::LEFT);
   frame_view->split_view_controller_.FlushForTesting();
diff --git a/chrome/browser/ui/views/ime_driver/remote_text_input_client.cc b/chrome/browser/ui/views/ime_driver/remote_text_input_client.cc
index 0f0d789..9792013 100644
--- a/chrome/browser/ui/views/ime_driver/remote_text_input_client.cc
+++ b/chrome/browser/ui/views/ime_driver/remote_text_input_client.cc
@@ -4,6 +4,10 @@
 
 #include "chrome/browser/ui/views/ime_driver/remote_text_input_client.h"
 
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "ui/events/event_dispatcher.h"
+
 RemoteTextInputClient::RemoteTextInputClient(
     ws::mojom::TextInputClientPtr remote_client,
     ui::TextInputType text_input_type,
@@ -183,8 +187,10 @@
 }
 
 ui::EventDispatchDetails RemoteTextInputClient::DispatchKeyEventPostIME(
-    ui::KeyEvent* event) {
-  remote_client_->DispatchKeyEventPostIME(ui::Event::Clone(*event),
-                                          base::OnceCallback<void(bool)>());
+    ui::KeyEvent* event,
+    base::OnceCallback<void(bool)> ack_callback) {
+  remote_client_->DispatchKeyEventPostIME(
+      ui::Event::Clone(*event),
+      ack_callback ? std::move(ack_callback) : base::DoNothing());
   return ui::EventDispatchDetails();
 }
diff --git a/chrome/browser/ui/views/ime_driver/remote_text_input_client.h b/chrome/browser/ui/views/ime_driver/remote_text_input_client.h
index 97b640d..837d683 100644
--- a/chrome/browser/ui/views/ime_driver/remote_text_input_client.h
+++ b/chrome/browser/ui/views/ime_driver/remote_text_input_client.h
@@ -62,7 +62,8 @@
 
   // ui::internal::InputMethodDelegate:
   ui::EventDispatchDetails DispatchKeyEventPostIME(
-      ui::KeyEvent* event) override;
+      ui::KeyEvent* event,
+      base::OnceCallback<void(bool)> ack_callback) override;
 
   ws::mojom::TextInputClientPtr remote_client_;
   ui::TextInputType text_input_type_;
diff --git a/chrome/browser/ui/views/media_router/media_router_ui_browsertest.cc b/chrome/browser/ui/views/media_router/media_router_ui_browsertest.cc
index a846c117..f4f03a7 100644
--- a/chrome/browser/ui/views/media_router/media_router_ui_browsertest.cc
+++ b/chrome/browser/ui/views/media_router/media_router_ui_browsertest.cc
@@ -41,24 +41,6 @@
 
 namespace media_router {
 
-namespace {
-
-// Returns true if the browser uses Cocoa UI. The Views dialog tests should not
-// be run if this is true.
-bool IsCocoaBrowser() {
-#if defined(OS_MACOSX)
-#if BUILDFLAG(MAC_VIEWS_BROWSER)
-  return features::IsViewsBrowserCocoa();
-#else  // !BUILDFLAG(MAC_VIEWS_BROWSER)
-  return true;
-#endif
-#else  // !defined(OS_MACOSX)
-  return false;
-#endif
-}
-
-}  // namespace
-
 // Uses the WebUI Cast dialog. The Views Cast dialog is used in
 // MediaRouterViewsUIBrowserTest below.
 class MediaRouterUIBrowserTest : public InProcessBrowserTest {
@@ -416,14 +398,8 @@
   ui::SimpleMenuModel* context_menu = GetIconContextMenu();
   const int command_index = context_menu->GetIndexOfCommandId(
       IDC_MEDIA_ROUTER_ALWAYS_SHOW_TOOLBAR_ACTION);
-  if (IsCocoaBrowser()) {
-    // With Cocoa, OnContextMenuClosed() gets called before command execution.
-    GetMediaRouterAction()->OnContextMenuClosed();
-    context_menu->ActivatedAt(command_index);
-  } else {
-    context_menu->ActivatedAt(command_index);
-    GetMediaRouterAction()->OnContextMenuClosed();
-  }
+  context_menu->ActivatedAt(command_index);
+  GetMediaRouterAction()->OnContextMenuClosed();
   GetDialogController()->HideMediaRouterDialog();
   EXPECT_TRUE(ToolbarIconExists());
 
@@ -446,27 +422,19 @@
 
 IN_PROC_BROWSER_TEST_F(MediaRouterViewsUIBrowserTest,
                        OpenDialogFromContextMenu) {
-  if (IsCocoaBrowser())
-    return;
   TestOpenDialogFromContextMenu();
 }
 
 IN_PROC_BROWSER_TEST_F(MediaRouterViewsUIBrowserTest, OpenDialogFromAppMenu) {
-  if (IsCocoaBrowser())
-    return;
   TestOpenDialogFromAppMenu();
 }
 
 IN_PROC_BROWSER_TEST_F(MediaRouterViewsUIBrowserTest,
                        EphemeralToolbarIconForDialog) {
-  if (IsCocoaBrowser())
-    return;
   TestEphemeralToolbarIconForDialog();
 }
 
 IN_PROC_BROWSER_TEST_F(MediaRouterViewsUIBrowserTest, PinAndUnpinToolbarIcon) {
-  if (IsCocoaBrowser())
-    return;
   GetDialogController()->ShowMediaRouterDialog();
   EXPECT_TRUE(ToolbarIconExists());
   // Pin the icon via its context menu.
diff --git a/chrome/browser/ui/views/overlay/overlay_window_views.cc b/chrome/browser/ui/views/overlay/overlay_window_views.cc
index 371e1a6..c3807c3 100644
--- a/chrome/browser/ui/views/overlay/overlay_window_views.cc
+++ b/chrome/browser/ui/views/overlay/overlay_window_views.cc
@@ -360,20 +360,20 @@
 }
 
 void OverlayWindowViews::UpdateCustomControlsSize(
-    views::ControlImageButton* control) {
-  if (!control)
+    views::ControlImageButton* control_button) {
+  if (!control_button)
     return;
   UpdateButtonSize();
-  control->SetSize(button_size_);
+  control_button->SetSize(button_size_);
   // TODO(sawtelle): Download the images and add them to the controls.
   // https://crbug.com/864271.
-  if (control == first_custom_controls_view_.get()) {
+  if (control_button == first_custom_controls_view_.get()) {
     first_custom_controls_view_->SetImage(
         views::Button::STATE_NORMAL,
         gfx::CreateVectorIcon(kPlayArrowIcon, button_size_.width() / 2,
                               kControlIconColor));
   }
-  if (control == second_custom_controls_view_.get()) {
+  if (control_button == second_custom_controls_view_.get()) {
     second_custom_controls_view_->SetImage(
         views::Button::STATE_NORMAL,
         gfx::CreateVectorIcon(kPauseIcon, button_size_.width() / 2,
@@ -381,8 +381,8 @@
   }
   const gfx::ImageSkia control_background = gfx::CreateVectorIcon(
       kPictureInPictureControlBackgroundIcon, button_size_.width(), kBgColor);
-  control->SetBackgroundImage(kBgColor, &control_background,
-                              &control_background);
+  control_button->SetBackgroundImage(kBgColor, &control_background,
+                                     &control_background);
 }
 
 void OverlayWindowViews::UpdatePlayPauseControlsSize() {
@@ -402,32 +402,30 @@
       kBgColor, &play_pause_background, &play_pause_background);
 }
 
-void OverlayWindowViews::SetUpCustomControl(
-    std::unique_ptr<views::ControlImageButton>& control,
-    const blink::PictureInPictureControlInfo& web_control,
+void OverlayWindowViews::CreateCustomControl(
+    std::unique_ptr<views::ControlImageButton>& control_button,
+    const blink::PictureInPictureControlInfo& info,
     ControlPosition position) {
-  if (!control) {
-    control = std::make_unique<views::ControlImageButton>(this);
-    controls_parent_view_->AddChildView(control.get());
-    control->set_owned_by_client();
-    control->SetImageAlignment(views::ImageButton::ALIGN_CENTER,
-                               views::ImageButton::ALIGN_MIDDLE);
-  }
+  control_button = std::make_unique<views::ControlImageButton>(this);
+  controls_parent_view_->AddChildView(control_button.get());
+  control_button->set_id(info.id);
+  control_button->set_owned_by_client();
 
-  // Sizing/positioning.
-  UpdateCustomControlsSize(control.get());
-  control->set_id(web_control.id);
+  // Sizing / positioning.
+  control_button->SetImageAlignment(views::ImageButton::ALIGN_CENTER,
+                                    views::ImageButton::ALIGN_MIDDLE);
+  UpdateCustomControlsSize(control_button.get());
   UpdateControlsBounds();
 
   // Accessibility.
-  base::string16 custom_button_label = base::UTF8ToUTF16(web_control.label);
-  control->SetAccessibleName(custom_button_label);
-  control->SetTooltipText(custom_button_label);
-  control->SetInstallFocusRingOnFocus(true);
-  control->SetFocusForPlatform();
+  base::string16 custom_button_label = base::UTF8ToUTF16(info.label);
+  control_button->SetAccessibleName(custom_button_label);
+  control_button->SetTooltipText(custom_button_label);
+  control_button->SetInstallFocusRingOnFocus(true);
+  control_button->SetFocusForPlatform();
 }
 
-bool OverlayWindowViews::OnlyOneCustomControlAdded() {
+bool OverlayWindowViews::HasOnlyOneCustomControl() {
   return first_custom_controls_view_ && !second_custom_controls_view_;
 }
 
@@ -448,7 +446,7 @@
   // |        [1]   [P]         |
   // |                          |
   // |__________________________|
-  if (OnlyOneCustomControlAdded()) {
+  if (HasOnlyOneCustomControl()) {
     play_pause_controls_view_->SetBoundsRect(
         CalculateControlsBounds(mid_window_x, button_size_));
     first_custom_controls_view_->SetBoundsRect(CalculateControlsBounds(
@@ -556,12 +554,16 @@
 
 void OverlayWindowViews::SetPictureInPictureCustomControls(
     const std::vector<blink::PictureInPictureControlInfo>& controls) {
+  // Clear any existing controls.
+  first_custom_controls_view_.reset();
+  second_custom_controls_view_.reset();
+
   if (controls.size() > 0)
-    SetUpCustomControl(first_custom_controls_view_, controls[0],
-                       ControlPosition::kLeft);
+    CreateCustomControl(first_custom_controls_view_, controls[0],
+                        ControlPosition::kLeft);
   if (controls.size() > 1)
-    SetUpCustomControl(second_custom_controls_view_, controls[1],
-                       ControlPosition::kRight);
+    CreateCustomControl(second_custom_controls_view_, controls[1],
+                        ControlPosition::kRight);
 }
 
 ui::Layer* OverlayWindowViews::GetWindowBackgroundLayer() {
diff --git a/chrome/browser/ui/views/overlay/overlay_window_views.h b/chrome/browser/ui/views/overlay/overlay_window_views.h
index 15eb4f6..81f1830 100644
--- a/chrome/browser/ui/views/overlay/overlay_window_views.h
+++ b/chrome/browser/ui/views/overlay/overlay_window_views.h
@@ -99,15 +99,16 @@
   void UpdateButtonSize();
 
   // Update the size of each controls view as the size of the window changes.
-  void UpdateCustomControlsSize(views::ControlImageButton*);
+  void UpdateCustomControlsSize(views::ControlImageButton* control_button);
   void UpdatePlayPauseControlsSize();
 
-  void SetUpCustomControl(std::unique_ptr<views::ControlImageButton>&,
-                          const blink::PictureInPictureControlInfo&,
-                          ControlPosition);
+  void CreateCustomControl(
+      std::unique_ptr<views::ControlImageButton>& control_button,
+      const blink::PictureInPictureControlInfo& info,
+      ControlPosition position);
 
   // Returns whether there is exactly one custom control on the window.
-  bool OnlyOneCustomControlAdded();
+  bool HasOnlyOneCustomControl();
 
   // Calculate and set the bounds of the controls.
   gfx::Rect CalculateControlsBounds(int x, const gfx::Size& size);
diff --git a/chrome/browser/ui/views/proximity_auth/proximity_auth_error_bubble_view.cc b/chrome/browser/ui/views/proximity_auth/proximity_auth_error_bubble_view.cc
index 8f8c2834..779993b 100644
--- a/chrome/browser/ui/views/proximity_auth/proximity_auth_error_bubble_view.cc
+++ b/chrome/browser/ui/views/proximity_auth/proximity_auth_error_bubble_view.cc
@@ -9,7 +9,6 @@
 #include "build/build_config.h"
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "chrome/browser/ui/proximity_auth/proximity_auth_error_bubble.h"
-#include "chrome/browser/ui/views_mode_controller.h"
 #include "chrome/grit/theme_resources.h"
 #include "content/public/browser/page_navigator.h"
 #include "content/public/browser/web_contents.h"
@@ -43,12 +42,6 @@
                                   const GURL& link_url,
                                   const gfx::Rect& anchor_rect,
                                   content::WebContents* web_contents) {
-#if defined(OS_MACOSX)
-  if (views_mode_controller::IsViewsBrowserCocoa()) {
-    NOTIMPLEMENTED();
-    return;
-  }
-#endif
   // Only one error bubble should be visible at a time.
   // Note that it suffices to check just the |message| for equality, because the
   // |link_range| and |link_url| are always the same for a given |message|
@@ -64,12 +57,6 @@
 }
 
 void HideProximityAuthErrorBubble() {
-#if defined(OS_MACOSX)
-  if (views_mode_controller::IsViewsBrowserCocoa()) {
-    NOTIMPLEMENTED();
-    return;
-  }
-#endif
   if (g_bubble.Get())
     g_bubble.Get()->GetWidget()->Close();
 }
diff --git a/chrome/browser/ui/webui/chromeos/login/assistant_optin_flow_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/assistant_optin_flow_screen_handler.cc
index 700e47a2..dbc2b1e3 100644
--- a/chrome/browser/ui/webui/chromeos/login/assistant_optin_flow_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/assistant_optin_flow_screen_handler.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/webui/chromeos/login/assistant_optin_flow_screen_handler.h"
 
+#include "base/metrics/histogram_macros.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/login/oobe_screen.h"
 #include "chrome/browser/chromeos/login/screens/assistant_optin_flow_screen.h"
@@ -88,6 +89,8 @@
       &AssistantOptInFlowScreenHandler::HandleGetMoreScreenShown);
   AddPrefixedCallback("ReadyScreen.screenShown",
                       &AssistantOptInFlowScreenHandler::HandleReadyScreenShown);
+  AddPrefixedCallback("LoadingScreen.timeout",
+                      &AssistantOptInFlowScreenHandler::HandleLoadingTimeout);
   AddPrefixedCallback("hotwordResult",
                       &AssistantOptInFlowScreenHandler::HandleHotwordResult);
   AddPrefixedCallback("flowFinished",
@@ -207,6 +210,7 @@
       selector.SerializeAsString(),
       base::BindOnce(&AssistantOptInFlowScreenHandler::OnGetSettingsResponse,
                      weak_factory_.GetWeakPtr()));
+  send_request_time_ = base::TimeTicks::Now();
 }
 
 void AssistantOptInFlowScreenHandler::ReloadContent(const base::Value& dict) {
@@ -220,6 +224,11 @@
 
 void AssistantOptInFlowScreenHandler::OnGetSettingsResponse(
     const std::string& settings) {
+  const base::TimeDelta time_since_request_sent =
+      base::TimeTicks::Now() - send_request_time_;
+  UMA_HISTOGRAM_TIMES("Assistant.OptInFlow.GetSettingsRequestTime",
+                      time_since_request_sent);
+
   assistant::SettingsUi settings_ui;
   settings_ui.ParseFromString(settings);
 
@@ -374,6 +383,10 @@
   RecordAssistantOptInStatus(READY_SCREEN_SHOWN);
 }
 
+void AssistantOptInFlowScreenHandler::HandleLoadingTimeout() {
+  ++loading_timeout_counter_;
+}
+
 void AssistantOptInFlowScreenHandler::HandleHotwordResult(bool enable_hotword) {
   enable_hotword_ = enable_hotword;
 
@@ -387,6 +400,8 @@
 }
 
 void AssistantOptInFlowScreenHandler::HandleFlowFinished() {
+  UMA_HISTOGRAM_EXACT_LINEAR("Assistant.OptInFlow.LoadingTimeoutCount",
+                             loading_timeout_counter_, 10);
   if (screen_)
     screen_->OnUserAction(kFlowFinished);
   else
diff --git a/chrome/browser/ui/webui/chromeos/login/assistant_optin_flow_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/assistant_optin_flow_screen_handler.h
index 8b14cd4f..6ddc110 100644
--- a/chrome/browser/ui/webui/chromeos/login/assistant_optin_flow_screen_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/assistant_optin_flow_screen_handler.h
@@ -77,6 +77,7 @@
   void HandleThirdPartyScreenShown();
   void HandleGetMoreScreenShown();
   void HandleReadyScreenShown();
+  void HandleLoadingTimeout();
   void HandleHotwordResult(bool enable_hotword);
   void HandleFlowFinished();
   void HandleFlowInitialized();
@@ -101,6 +102,12 @@
   // Whether user chose to enable hotword.
   bool enable_hotword_ = true;
 
+  // Time that get settings request is sent.
+  base::TimeTicks send_request_time_;
+
+  // Counter for the number of loading timeout happens.
+  int loading_timeout_counter_ = 0;
+
   assistant::mojom::AssistantSettingsManagerPtr settings_manager_;
   base::WeakPtrFactory<AssistantOptInFlowScreenHandler> weak_factory_;
 
diff --git a/chrome/browser/ui/webui/chromeos/login/multidevice_setup_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/multidevice_setup_screen_handler.cc
new file mode 100644
index 0000000..82c621a
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/login/multidevice_setup_screen_handler.cc
@@ -0,0 +1,44 @@
+// 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/ui/webui/chromeos/login/multidevice_setup_screen_handler.h"
+
+#include "chrome/browser/chromeos/login/screens/multidevice_setup_screen.h"
+#include "chrome/browser/ui/webui/chromeos/multidevice_setup/multidevice_setup_localized_strings_provider.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/login/localized_values_builder.h"
+
+namespace {
+
+const char kJsScreenPath[] = "login.MultiDeviceSetupScreen";
+
+}  // namespace
+
+namespace chromeos {
+
+MultiDeviceSetupScreenHandler::MultiDeviceSetupScreenHandler()
+    : BaseScreenHandler(kScreenId) {
+  set_call_js_prefix(kJsScreenPath);
+}
+
+MultiDeviceSetupScreenHandler::~MultiDeviceSetupScreenHandler() = default;
+
+void MultiDeviceSetupScreenHandler::DeclareLocalizedValues(
+    ::login::LocalizedValuesBuilder* builder) {
+  multidevice_setup::AddLocalizedValuesToBuilder(builder);
+}
+
+void MultiDeviceSetupScreenHandler::Bind(MultiDeviceSetupScreen* screen) {
+  BaseScreenHandler::SetBaseScreen(screen);
+}
+
+void MultiDeviceSetupScreenHandler::Show() {
+  ShowScreen(kScreenId);
+}
+
+void MultiDeviceSetupScreenHandler::Hide() {}
+
+void MultiDeviceSetupScreenHandler::Initialize() {}
+
+}  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/login/multidevice_setup_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/multidevice_setup_screen_handler.h
new file mode 100644
index 0000000..7df6c393
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/login/multidevice_setup_screen_handler.h
@@ -0,0 +1,41 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_CHROMEOS_LOGIN_MULTIDEVICE_SETUP_SCREEN_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_CHROMEOS_LOGIN_MULTIDEVICE_SETUP_SCREEN_HANDLER_H_
+
+#include "base/macros.h"
+#include "chrome/browser/chromeos/login/screens/multidevice_setup_screen_view.h"
+#include "chrome/browser/ui/webui/chromeos/login/base_screen_handler.h"
+
+namespace chromeos {
+
+class MultiDeviceSetupScreen;
+
+// Concrete MultiDeviceSetupScreenView WebUI-based implementation.
+class MultiDeviceSetupScreenHandler : public BaseScreenHandler,
+                                      public MultiDeviceSetupScreenView {
+ public:
+  MultiDeviceSetupScreenHandler();
+  ~MultiDeviceSetupScreenHandler() override;
+
+  // BaseScreenHandler:
+  void DeclareLocalizedValues(
+      ::login::LocalizedValuesBuilder* builder) override;
+
+  // MultiDeviceSetupScreenView:
+  void Bind(MultiDeviceSetupScreen* screen) override;
+  void Show() override;
+  void Hide() override;
+
+ private:
+  // BaseScreenHandler:
+  void Initialize() override;
+
+  DISALLOW_COPY_AND_ASSIGN(MultiDeviceSetupScreenHandler);
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_UI_WEBUI_CHROMEOS_LOGIN_MULTIDEVICE_SETUP_SCREEN_HANDLER_H_
diff --git a/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc b/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc
index 70efbe9..24f9748 100644
--- a/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc
+++ b/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc
@@ -59,6 +59,7 @@
 #include "chrome/browser/ui/webui/chromeos/login/kiosk_autolaunch_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/kiosk_enable_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/marketing_opt_in_screen_handler.h"
+#include "chrome/browser/ui/webui/chromeos/login/multidevice_setup_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/network_dropdown_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/network_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/network_state_informer.h"
@@ -420,6 +421,8 @@
 
   AddScreenHandler(std::make_unique<AssistantOptInFlowScreenHandler>());
 
+  AddScreenHandler(std::make_unique<MultiDeviceSetupScreenHandler>());
+
   // Initialize KioskAppMenuHandler. Note that it is NOT a screen handler.
   auto kiosk_app_menu_handler =
       std::make_unique<KioskAppMenuHandler>(network_state_informer_);
@@ -598,6 +601,10 @@
   return GetView<AssistantOptInFlowScreenHandler>();
 }
 
+MultiDeviceSetupScreenView* OobeUI::GetMultiDeviceSetupScreenView() {
+  return GetView<MultiDeviceSetupScreenHandler>();
+}
+
 UserImageView* OobeUI::GetUserImageView() {
   return GetView<UserImageScreenHandler>();
 }
diff --git a/chrome/browser/ui/webui/chromeos/login/oobe_ui.h b/chrome/browser/ui/webui/chromeos/login/oobe_ui.h
index 576a8433..0641452 100644
--- a/chrome/browser/ui/webui/chromeos/login/oobe_ui.h
+++ b/chrome/browser/ui/webui/chromeos/login/oobe_ui.h
@@ -52,6 +52,7 @@
 class KioskEnableScreenView;
 class LoginScreenContext;
 class MarketingOptInScreenView;
+class MultiDeviceSetupScreenView;
 class NativeWindowDelegate;
 class NetworkScreenView;
 class NetworkStateInformer;
@@ -137,6 +138,7 @@
   WaitForContainerReadyScreenView* GetWaitForContainerReadyScreenView();
   UpdateRequiredView* GetUpdateRequiredScreenView();
   AssistantOptInFlowScreenView* GetAssistantOptInFlowScreenView();
+  MultiDeviceSetupScreenView* GetMultiDeviceSetupScreenView();
   GaiaView* GetGaiaScreenView();
   UserBoardView* GetUserBoardView();
   DiscoverScreenView* GetDiscoverScreenView();
diff --git a/components/nux/BUILD.gn b/chrome/browser/ui/webui/welcome/nux/BUILD.gn
similarity index 84%
rename from components/nux/BUILD.gn
rename to chrome/browser/ui/webui/welcome/nux/BUILD.gn
index 0fdc1ed..02ddf2c 100644
--- a/components/nux/BUILD.gn
+++ b/chrome/browser/ui/webui/welcome/nux/BUILD.gn
@@ -7,8 +7,8 @@
 if (is_win && is_chrome_branded) {
   static_library("set_as_default_feature") {
     sources = [
-      "set_as_default/set_as_default_handler.cc",
-      "set_as_default/set_as_default_handler.h",
+      "set_as_default_handler.cc",
+      "set_as_default_handler.h",
     ]
 
     public_deps = [
@@ -18,6 +18,7 @@
     ]
 
     deps = [
+      "//chrome/app:generated_resources",
       "//components/resources",
       "//components/strings",
       "//components/variations",
@@ -39,8 +40,8 @@
 
   static_library("email_feature") {
     sources = [
-      "email/email_handler.cc",
-      "email/email_handler.h",
+      "email_handler.cc",
+      "email_handler.h",
     ]
 
     public_deps = [
@@ -50,6 +51,7 @@
     ]
 
     deps = [
+      "//chrome/app:generated_resources",
       "//components/bookmarks/browser",
       "//components/bookmarks/common",
       "//components/favicon/core",
@@ -65,8 +67,8 @@
 
   static_library("google_apps_feature") {
     sources = [
-      "google_apps/google_apps_handler.cc",
-      "google_apps/google_apps_handler.h",
+      "google_apps_handler.cc",
+      "google_apps_handler.h",
     ]
 
     public_deps = [
@@ -76,6 +78,7 @@
     ]
 
     deps = [
+      "//chrome/app:generated_resources",
       "//components/bookmarks/browser",
       "//components/bookmarks/common",
       "//components/favicon/core",
diff --git a/components/nux/DEPS b/chrome/browser/ui/webui/welcome/nux/DEPS
similarity index 68%
rename from components/nux/DEPS
rename to chrome/browser/ui/webui/welcome/nux/DEPS
index 168b969..cbcb3569 100644
--- a/components/nux/DEPS
+++ b/chrome/browser/ui/webui/welcome/nux/DEPS
@@ -1,10 +1,9 @@
 include_rules = [
+  "+chrome/grit",
   "+components/bookmarks",
   "+components/grit",
   "+components/prefs",
-  "+components/nux",
   "+components/favicon",
-  "+components/strings/grit/components_strings.h",
   "+content/public/browser",
   "+ui/base",
 ]
diff --git a/chrome/browser/ui/webui/welcome/nux/OWNERS b/chrome/browser/ui/webui/welcome/nux/OWNERS
new file mode 100644
index 0000000..589a5fa6
--- /dev/null
+++ b/chrome/browser/ui/webui/welcome/nux/OWNERS
@@ -0,0 +1,5 @@
+hcarmona@chromium.org
+scottchen@chromium.org
+
+# COMPONENT: UI>Browser>FirstRun
+# LABEL: Proj-Navi
\ No newline at end of file
diff --git a/chrome/browser/ui/webui/welcome/nux/README b/chrome/browser/ui/webui/welcome/nux/README
new file mode 100644
index 0000000..b2d6888
--- /dev/null
+++ b/chrome/browser/ui/webui/welcome/nux/README
@@ -0,0 +1,3 @@
+New User Experience Experiment (NUX). Code here will prompt new users to go
+through an onboarding experience to customize their new browser install to
+their common usages.
\ No newline at end of file
diff --git a/components/nux/constants.cc b/chrome/browser/ui/webui/welcome/nux/constants.cc
similarity index 91%
rename from components/nux/constants.cc
rename to chrome/browser/ui/webui/welcome/nux/constants.cc
index ef1933b..ce936c0 100644
--- a/components/nux/constants.cc
+++ b/chrome/browser/ui/webui/welcome/nux/constants.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/nux/constants.h"
+#include "chrome/browser/ui/webui/welcome/nux/constants.h"
 
 #include "base/feature_list.h"
 
diff --git a/components/nux/constants.h b/chrome/browser/ui/webui/welcome/nux/constants.h
similarity index 73%
rename from components/nux/constants.h
rename to chrome/browser/ui/webui/welcome/nux/constants.h
index 8ce07e4c..8f72a0c 100644
--- a/components/nux/constants.h
+++ b/chrome/browser/ui/webui/welcome/nux/constants.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_NUX_CONSTANTS_H_
-#define COMPONENTS_NUX_CONSTANTS_H_
+#ifndef CHROME_BROWSER_UI_WEBUI_WELCOME_NUX_CONSTANTS_H_
+#define CHROME_BROWSER_UI_WEBUI_WELCOME_NUX_CONSTANTS_H_
 
 namespace base {
 struct Feature;
@@ -19,4 +19,4 @@
 
 }  // namespace nux
 
-#endif  // COMPONENTS_NUX_CONSTANTS_H_
+#endif  // CHROME_BROWSER_UI_WEBUI_WELCOME_NUX_CONSTANTS_H_
diff --git a/components/nux/email/email_handler.cc b/chrome/browser/ui/webui/welcome/nux/email_handler.cc
similarity index 97%
rename from components/nux/email/email_handler.cc
rename to chrome/browser/ui/webui/welcome/nux/email_handler.cc
index 353362b..e8aa7fa1 100644
--- a/components/nux/email/email_handler.cc
+++ b/chrome/browser/ui/webui/welcome/nux/email_handler.cc
@@ -2,18 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/nux/email/email_handler.h"
+#include "chrome/browser/ui/webui/welcome/nux/email_handler.h"
 
 #include "base/bind.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/ui/webui/welcome/nux/show_promo_delegate.h"
+#include "chrome/grit/generated_resources.h"
 #include "components/bookmarks/common/bookmark_pref_names.h"
 #include "components/favicon/core/favicon_service.h"
 #include "components/grit/components_resources.h"
 #include "components/grit/components_scaled_resources.h"
-#include "components/nux/show_promo_delegate.h"
 #include "components/prefs/pref_service.h"
 #include "components/strings/grit/components_strings.h"
 #include "content/public/browser/web_contents.h"
diff --git a/components/nux/email/email_handler.h b/chrome/browser/ui/webui/welcome/nux/email_handler.h
similarity index 88%
rename from components/nux/email/email_handler.h
rename to chrome/browser/ui/webui/welcome/nux/email_handler.h
index 9935e7c..e66e56e 100644
--- a/components/nux/email/email_handler.h
+++ b/chrome/browser/ui/webui/welcome/nux/email_handler.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_NUX_EMAIL_EMAIL_HANDLER_H_
-#define COMPONENTS_NUX_EMAIL_EMAIL_HANDLER_H_
+#ifndef CHROME_BROWSER_UI_WEBUI_WELCOME_NUX_EMAIL_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_WELCOME_NUX_EMAIL_HANDLER_H_
 
 #include "base/macros.h"
 #include "base/values.h"
@@ -60,4 +60,4 @@
 
 }  // namespace nux
 
-#endif  // COMPONENTS_NUX_GOOGLE_APPS_GOOGLE_APPS_HANDLER_H_
+#endif  // CHROME_BROWSER_UI_WEBUI_WELCOME_NUX_EMAIL_HANDLER_H_
diff --git a/components/nux/google_apps/google_apps_handler.cc b/chrome/browser/ui/webui/welcome/nux/google_apps_handler.cc
similarity index 97%
rename from components/nux/google_apps/google_apps_handler.cc
rename to chrome/browser/ui/webui/welcome/nux/google_apps_handler.cc
index 84824ae..cbb480f 100644
--- a/components/nux/google_apps/google_apps_handler.cc
+++ b/chrome/browser/ui/webui/welcome/nux/google_apps_handler.cc
@@ -2,19 +2,20 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/nux/google_apps/google_apps_handler.h"
+#include "chrome/browser/ui/webui/welcome/nux/google_apps_handler.h"
 
 #include "base/bind.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/ui/webui/welcome/nux/show_promo_delegate.h"
+#include "chrome/grit/generated_resources.h"
 #include "components/bookmarks/browser/bookmark_model.h"
 #include "components/bookmarks/common/bookmark_pref_names.h"
 #include "components/favicon/core/favicon_service.h"
 #include "components/grit/components_resources.h"
 #include "components/grit/components_scaled_resources.h"
-#include "components/nux/show_promo_delegate.h"
 #include "components/prefs/pref_service.h"
 #include "components/strings/grit/components_strings.h"
 #include "content/public/browser/web_contents.h"
diff --git a/components/nux/google_apps/google_apps_handler.h b/chrome/browser/ui/webui/welcome/nux/google_apps_handler.h
similarity index 88%
rename from components/nux/google_apps/google_apps_handler.h
rename to chrome/browser/ui/webui/welcome/nux/google_apps_handler.h
index b0c151f..d506d19 100644
--- a/components/nux/google_apps/google_apps_handler.h
+++ b/chrome/browser/ui/webui/welcome/nux/google_apps_handler.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_NUX_GOOGLE_APPS_GOOGLE_APPS_HANDLER_H_
-#define COMPONENTS_NUX_GOOGLE_APPS_GOOGLE_APPS_HANDLER_H_
+#ifndef CHROME_BROWSER_UI_WEBUI_WELCOME_NUX_GOOGLE_APPS_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_WELCOME_NUX_GOOGLE_APPS_HANDLER_H_
 
 #include "base/macros.h"
 #include "base/values.h"
@@ -68,4 +68,4 @@
 
 }  // namespace nux
 
-#endif  // COMPONENTS_NUX_GOOGLE_APPS_GOOGLE_APPS_HANDLER_H_
+#endif  // CHROME_BROWSER_UI_WEBUI_WELCOME_NUX_GOOGLE_APPS_HANDLER_H_
diff --git a/components/nux/set_as_default/set_as_default_handler.cc b/chrome/browser/ui/webui/welcome/nux/set_as_default_handler.cc
similarity index 92%
rename from components/nux/set_as_default/set_as_default_handler.cc
rename to chrome/browser/ui/webui/welcome/nux/set_as_default_handler.cc
index 8c02d06..2683067 100644
--- a/components/nux/set_as_default/set_as_default_handler.cc
+++ b/chrome/browser/ui/webui/welcome/nux/set_as_default_handler.cc
@@ -2,13 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/nux/set_as_default/set_as_default_handler.h"
+#include "chrome/browser/ui/webui/welcome/nux/set_as_default_handler.h"
 
 #include "base/bind.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/grit/generated_resources.h"
 #include "components/grit/components_resources.h"
 #include "components/grit/components_scaled_resources.h"
 #include "components/strings/grit/components_strings.h"
diff --git a/components/nux/set_as_default/set_as_default_handler.h b/chrome/browser/ui/webui/welcome/nux/set_as_default_handler.h
similarity index 77%
rename from components/nux/set_as_default/set_as_default_handler.h
rename to chrome/browser/ui/webui/welcome/nux/set_as_default_handler.h
index 1011808..cb15edae 100644
--- a/components/nux/set_as_default/set_as_default_handler.h
+++ b/chrome/browser/ui/webui/welcome/nux/set_as_default_handler.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_NUX_SET_AS_DEFAULT_SET_AS_DEFAULT_HANDLER_H_
-#define COMPONENTS_NUX_SET_AS_DEFAULT_SET_AS_DEFAULT_HANDLER_H_
+#ifndef CHROME_BROWSER_UI_WEBUI_WELCOME_NUX_SET_AS_DEFAULT_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_WELCOME_NUX_SET_AS_DEFAULT_HANDLER_H_
 
 #include "base/macros.h"
 #include "base/values.h"
@@ -31,4 +31,4 @@
 
 }  // namespace nux
 
-#endif  // COMPONENTS_NUX_SET_AS_DEFAULT_SET_AS_DEFAULT_HANDLER_H_
+#endif  // CHROME_BROWSER_UI_WEBUI_WELCOME_NUX_SET_AS_DEFAULT_HANDLER_H_
diff --git a/components/nux/show_promo_delegate.h b/chrome/browser/ui/webui/welcome/nux/show_promo_delegate.h
similarity index 74%
rename from components/nux/show_promo_delegate.h
rename to chrome/browser/ui/webui/welcome/nux/show_promo_delegate.h
index 9c1e4fa..c95b16cc 100644
--- a/components/nux/show_promo_delegate.h
+++ b/chrome/browser/ui/webui/welcome/nux/show_promo_delegate.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_NUX_SHOW_PROMO_DELEGATE_H_
-#define COMPONENTS_NUX_SHOW_PROMO_DELEGATE_H_
+#ifndef CHROME_BROWSER_UI_WEBUI_WELCOME_NUX_SHOW_PROMO_DELEGATE_H_
+#define CHROME_BROWSER_UI_WEBUI_WELCOME_NUX_SHOW_PROMO_DELEGATE_H_
 
 #include <memory>
 
@@ -23,4 +23,4 @@
       int string_specifier);
 };
 
-#endif  //  COMPONENTS_NUX_SHOW_PROMO_DELEGATE_H_
+#endif  //  CHROME_BROWSER_UI_WEBUI_WELCOME_NUX_SHOW_PROMO_DELEGATE_H_
diff --git a/chrome/browser/ui/webui/welcome/nux_helper.cc b/chrome/browser/ui/webui/welcome/nux_helper.cc
index 27e965a..a9617a4 100644
--- a/chrome/browser/ui/webui/welcome/nux_helper.cc
+++ b/chrome/browser/ui/webui/welcome/nux_helper.cc
@@ -10,7 +10,7 @@
 // chrome/browser/ui/webui/welcome/ and included by non-win platforms.
 // Also check if it makes sense to merge nux_helper.* with nux/constants.*.
 #if defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)
-#include "components/nux/constants.h"
+#include "chrome/browser/ui/webui/welcome/nux/constants.h"
 #endif  // defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)
 
 namespace nux {
diff --git a/chrome/browser/ui/webui/welcome/welcome_ui.cc b/chrome/browser/ui/webui/welcome/welcome_ui.cc
index efb5eab..2c7a134 100644
--- a/chrome/browser/ui/webui/welcome/welcome_ui.cc
+++ b/chrome/browser/ui/webui/welcome/welcome_ui.cc
@@ -25,11 +25,11 @@
 #if defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)
 #include "base/metrics/histogram_macros.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
-#include "components/nux/constants.h"
-#include "components/nux/email/email_handler.h"
-#include "components/nux/google_apps/google_apps_handler.h"
-#include "components/nux/set_as_default/set_as_default_handler.h"
-#include "components/nux/show_promo_delegate.h"
+#include "chrome/browser/ui/webui/welcome/nux/constants.h"
+#include "chrome/browser/ui/webui/welcome/nux/email_handler.h"
+#include "chrome/browser/ui/webui/welcome/nux/google_apps_handler.h"
+#include "chrome/browser/ui/webui/welcome/nux/set_as_default_handler.h"
+#include "chrome/browser/ui/webui/welcome/nux/show_promo_delegate.h"
 #include "content/public/browser/web_contents.h"
 #endif  // defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)
 
@@ -100,7 +100,6 @@
         IDR_WELCOME_ONBOARDING_WELCOME_WELCOME_BROWSER_PROXY_JS);
   } else if (kIsBranded && is_dice) {
     // Use special layout if the application is branded and DICE is enabled.
-    // Otherwise use the default layout.
     html_source->AddLocalizedString("headerText", IDS_WELCOME_HEADER);
     html_source->AddLocalizedString("acceptText",
                                     IDS_PROFILES_DICE_SIGNIN_BUTTON);
diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/common/extensions/api/_permission_features.json
index 8af9ca8..a0b761e 100644
--- a/chrome/common/extensions/api/_permission_features.json
+++ b/chrome/common/extensions/api/_permission_features.json
@@ -320,7 +320,12 @@
     "platforms" : ["win", "mac", "linux"],
     "whitelist" : [
       "FD15C63ABA854733FDCBC1D4D34A71E963A12ABD",  // https://crbug.com/825015
-      "08455FA7CB8734168378F731B00B354CEEE0088F"   // https://crbug.com/825015 - Test Extension
+      "08455FA7CB8734168378F731B00B354CEEE0088F",  // https://crbug.com/825015 - Test Extension
+      "86D63D90308742AA65B8B29AE2D39FED2D6DC310",  // https://crbug.com/882461 SecureConnect
+      "031E5E4A54C39E4F46E11CE643584E9187915908",  // https://crbug.com/882461 SecureConnect
+      "349E89FE0296161007623C0B4096B617D7CECD54",  // https://crbug.com/882461 SecureConnect
+      "18B761AA5B58FA89E596EB7996B4C92C86775C7F",  // https://crbug.com/882461 SecureConnect
+      "2012C0122892D332325339998251D1CC3CAE396A"   // https://crbug.com/882461 SecureConnect
     ]
   },
   "experimental": {
diff --git a/chrome/common/trace_event_args_whitelist.cc b/chrome/common/trace_event_args_whitelist.cc
index 6ef2c7bf..09c21825 100644
--- a/chrome/common/trace_event_args_whitelist.cc
+++ b/chrome/common/trace_event_args_whitelist.cc
@@ -18,6 +18,7 @@
   const char* const* arg_name_filter;
 };
 
+const char* const kGPUAllowedArgs[] = {nullptr};
 const char* const kInputLatencyAllowedArgs[] = {"data", nullptr};
 const char* const kMemoryDumpAllowedArgs[] = {"dumps", nullptr};
 
@@ -29,6 +30,7 @@
     {"__metadata", "chrome_library_module", nullptr},
     {"__metadata", "stackFrames", nullptr},
     {"__metadata", "typeNames", nullptr},
+    {"GPU", "*", kGPUAllowedArgs},
     {"ipc", "GpuChannelHost::Send", nullptr},
     {"ipc", "SyncChannel::Send", nullptr},
     {"latencyInfo", "*", kInputLatencyAllowedArgs},
diff --git a/chrome/test/chromedriver/test/run_py_tests.py b/chrome/test/chromedriver/test/run_py_tests.py
index 8a68c711..97de8f4 100755
--- a/chrome/test/chromedriver/test/run_py_tests.py
+++ b/chrome/test/chromedriver/test/run_py_tests.py
@@ -123,16 +123,12 @@
 _OS_SPECIFIC_FILTER['win'] = [
     # https://bugs.chromium.org/p/chromedriver/issues/detail?id=299
     'ChromeLogPathCapabilityTest.testChromeLogPath',
-    # https://bugs.chromium.org/p/chromedriver/issues/detail?id=1945
-    'ChromeDriverTest.testWindowFullScreen',
 ]
 _OS_SPECIFIC_FILTER['linux'] = [
 ]
 _OS_SPECIFIC_FILTER['mac'] = [
     # https://bugs.chromium.org/p/chromedriver/issues/detail?id=1927
     'MobileEmulationCapabilityTest.testTapElement',
-    # https://bugs.chromium.org/p/chromedriver/issues/detail?id=1945
-    'ChromeDriverTest.testWindowFullScreen',
     # crbug.com/827171
     'ChromeDriverTest.testWindowMinimize',
     # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2522
diff --git a/chrome/test/data/webui/md_bookmarks/command_manager_test.js b/chrome/test/data/webui/md_bookmarks/command_manager_test.js
index 9508a9a..fe20ecd 100644
--- a/chrome/test/data/webui/md_bookmarks/command_manager_test.js
+++ b/chrome/test/data/webui/md_bookmarks/command_manager_test.js
@@ -114,10 +114,6 @@
     MockInteractions.pressAndReleaseKeyOn(document.body, '', [], key);
     commandManager.assertLastCommand(Command.EDIT, ['13']);
 
-    // close dialog for other tests.
-    const dialog = commandManager.$.editDialog.getIfExists();
-    dialog.onCancelButtonTap_();
-
     // Doesn't trigger when multiple items are selected.
     store.data.selection.items = new Set(['11', '13']);
     store.notifyObservers();
diff --git a/chrome/test/data/webui/settings/about_page_tests.js b/chrome/test/data/webui/settings/about_page_tests.js
index b59e933..fe0885e 100644
--- a/chrome/test/data/webui/settings/about_page_tests.js
+++ b/chrome/test/data/webui/settings/about_page_tests.js
@@ -50,7 +50,7 @@
           updateAvailable: false,
         };
 
-        /** @private {boolean} */
+        /** @private {boolean|Promise} */
         this.hasEndOfLife_ = false;
       }
     }
@@ -129,7 +129,7 @@
       this.regulatoryInfo_ = regulatoryInfo;
     };
 
-    /** @param {boolean} hasEndOfLife */
+    /** @param {boolean|Promise} hasEndOfLife */
     TestAboutPageBrowserProxy.prototype.setHasEndOfLife = function(
         hasEndOfLife) {
       this.hasEndOfLife_ = hasEndOfLife;
@@ -648,13 +648,26 @@
                 });
           }
 
-          return checkHasEndOfLife(false)
+          // Force test proxy to not respond to JS requests.
+          // End of life message should still be hidden in this case.
+          aboutBrowserProxy.setHasEndOfLife(new Promise(function(res, rej) {}));
+          return initNewPage()
+              .then(function() {
+                return checkHasEndOfLife(false);
+              })
               .then(function() {
                 aboutBrowserProxy.setHasEndOfLife(true);
                 return initNewPage();
               })
               .then(function() {
                 return checkHasEndOfLife(true);
+              })
+              .then(function() {
+                aboutBrowserProxy.setHasEndOfLife(false);
+                return initNewPage();
+              })
+              .then(function() {
+                return checkHasEndOfLife(false);
               });
         });
       }
diff --git a/chromecast/browser/BUILD.gn b/chromecast/browser/BUILD.gn
index 2714494..665d5c64 100644
--- a/chromecast/browser/BUILD.gn
+++ b/chromecast/browser/BUILD.gn
@@ -300,8 +300,6 @@
         "cast_web_view_extension.h",
         "ui/aura/accessibility/automation_manager_aura.cc",
         "ui/aura/accessibility/automation_manager_aura.h",
-        "ui/aura/accessibility/ax_root_obj_wrapper.cc",
-        "ui/aura/accessibility/ax_root_obj_wrapper.h",
         "ui/aura/accessibility/ax_tree_source_aura.cc",
         "ui/aura/accessibility/ax_tree_source_aura.h",
       ]
diff --git a/chromecast/browser/ui/aura/accessibility/automation_manager_aura.h b/chromecast/browser/ui/aura/accessibility/automation_manager_aura.h
index 5683ce11..81e9f2a 100644
--- a/chromecast/browser/ui/aura/accessibility/automation_manager_aura.h
+++ b/chromecast/browser/ui/aura/accessibility/automation_manager_aura.h
@@ -32,14 +32,12 @@
 class View;
 }  // namespace views
 
-using AuraAXTreeSerializer =
-    ui::AXTreeSerializer<views::AXAuraObjWrapper*,
-                         ui::AXNodeData,
-                         ui::AXTreeData>;
+using AuraAXTreeSerializer = ui::
+    AXTreeSerializer<views::AXAuraObjWrapper*, ui::AXNodeData, ui::AXTreeData>;
 
 // Manages a tree of automation nodes.
 class AutomationManagerAura : public ui::AXHostDelegate,
-                              views::AXAuraObjCache::Delegate {
+                              public views::AXAuraObjCache::Delegate {
  public:
   // Get the single instance of this class.
   static AutomationManagerAura* GetInstance();
diff --git a/chromecast/browser/ui/aura/accessibility/ax_root_obj_wrapper.cc b/chromecast/browser/ui/aura/accessibility/ax_root_obj_wrapper.cc
deleted file mode 100644
index 10f3a5e..0000000
--- a/chromecast/browser/ui/aura/accessibility/ax_root_obj_wrapper.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chromecast/browser/ui/aura/accessibility/ax_root_obj_wrapper.h"
-
-#include "base/stl_util.h"
-#include "base/strings/utf_string_conversions.h"
-#include "ui/accessibility/ax_node_data.h"
-#include "ui/aura/window.h"
-#include "ui/views/accessibility/ax_aura_obj_cache.h"
-#include "ui/views/accessibility/ax_window_obj_wrapper.h"
-
-AXRootObjWrapper::AXRootObjWrapper() : alert_window_(new aura::Window(NULL)) {
-  alert_window_->Init(ui::LAYER_NOT_DRAWN);
-}
-
-AXRootObjWrapper::~AXRootObjWrapper() {
-  if (alert_window_) {
-    delete alert_window_;
-    alert_window_ = NULL;
-  }
-}
-
-views::AXAuraObjWrapper* AXRootObjWrapper::GetAlertForText(
-    const std::string& text) {
-  alert_window_->SetTitle(base::UTF8ToUTF16((text)));
-  views::AXWindowObjWrapper* window_obj =
-      static_cast<views::AXWindowObjWrapper*>(
-          views::AXAuraObjCache::GetInstance()->GetOrCreate(alert_window_));
-  window_obj->set_is_alert(true);
-  return window_obj;
-}
-
-bool AXRootObjWrapper::HasChild(views::AXAuraObjWrapper* child) {
-  std::vector<views::AXAuraObjWrapper*> children;
-  GetChildren(&children);
-  return base::ContainsValue(children, child);
-}
-
-bool AXRootObjWrapper::IsIgnored() {
-  return false;
-}
-
-views::AXAuraObjWrapper* AXRootObjWrapper::GetParent() {
-  return NULL;
-}
-
-void AXRootObjWrapper::GetChildren(
-    std::vector<views::AXAuraObjWrapper*>* out_children) {
-  views::AXAuraObjCache::GetInstance()->GetTopLevelWindows(out_children);
-  out_children->push_back(
-      views::AXAuraObjCache::GetInstance()->GetOrCreate(alert_window_));
-}
-
-void AXRootObjWrapper::Serialize(ui::AXNodeData* out_node_data) {
-  out_node_data->id = unique_id_.Get();
-  out_node_data->role = ax::mojom::Role::kDesktop;
-}
-
-const ui::AXUniqueId& AXRootObjWrapper::GetUniqueId() const {
-  return unique_id_;
-}
diff --git a/chromecast/browser/ui/aura/accessibility/ax_root_obj_wrapper.h b/chromecast/browser/ui/aura/accessibility/ax_root_obj_wrapper.h
deleted file mode 100644
index 6d2dbcd9..0000000
--- a/chromecast/browser/ui/aura/accessibility/ax_root_obj_wrapper.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMECAST_BROWSER_UI_AURA_ACCESSIBILITY_AX_ROOT_OBJ_WRAPPER_H_
-#define CHROMECAST_BROWSER_UI_AURA_ACCESSIBILITY_AX_ROOT_OBJ_WRAPPER_H_
-
-#include <stdint.h>
-
-#include <string>
-
-#include "base/macros.h"
-#include "ui/accessibility/platform/ax_unique_id.h"
-#include "ui/views/accessibility/ax_aura_obj_wrapper.h"
-
-namespace aura {
-class Window;
-}  // namespace aura
-
-class AXRootObjWrapper : public views::AXAuraObjWrapper {
- public:
-  AXRootObjWrapper();
-  ~AXRootObjWrapper() override;
-
-  // Returns an AXAuraObjWrapper for an alert window with title set to |text|.
-  views::AXAuraObjWrapper* GetAlertForText(const std::string& text);
-
-  // Convenience method to check for existence of a child.
-  bool HasChild(views::AXAuraObjWrapper* child);
-
-  // views::AXAuraObjWrapper overrides.
-  bool IsIgnored() override;
-  views::AXAuraObjWrapper* GetParent() override;
-  void GetChildren(
-      std::vector<views::AXAuraObjWrapper*>* out_children) override;
-  void Serialize(ui::AXNodeData* out_node_data) override;
-  const ui::AXUniqueId& GetUniqueId() const override;
-
- private:
-  ui::AXUniqueId unique_id_;
-
-  aura::Window* alert_window_;
-
-  DISALLOW_COPY_AND_ASSIGN(AXRootObjWrapper);
-};
-
-#endif  // CHROMECAST_BROWSER_UI_AURA_ACCESSIBILITY_AX_ROOT_OBJ_WRAPPER_H_
diff --git a/chromecast/browser/ui/aura/accessibility/ax_tree_source_aura.cc b/chromecast/browser/ui/aura/accessibility/ax_tree_source_aura.cc
index 3b89c87..17252760 100644
--- a/chromecast/browser/ui/aura/accessibility/ax_tree_source_aura.cc
+++ b/chromecast/browser/ui/aura/accessibility/ax_tree_source_aura.cc
@@ -6,8 +6,6 @@
 
 #include <stddef.h>
 
-#include <vector>
-
 #include "chromecast/browser/ui/aura/accessibility/automation_manager_aura.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
@@ -25,7 +23,7 @@
 using views::AXAuraObjWrapper;
 
 AXTreeSourceAura::AXTreeSourceAura() {
-  root_.reset(new AXRootObjWrapper());
+  root_.reset(new AXRootObjWrapper(AutomationManagerAura::GetInstance()));
 }
 
 AXTreeSourceAura::~AXTreeSourceAura() {
@@ -73,7 +71,7 @@
   if (focus)
     tree_data->focus_id = focus->GetUniqueId().Get();
   else
-    tree_data->focus_id = 1; // root node
+    tree_data->focus_id = 1;  // root node
 
   return true;
 }
diff --git a/chromecast/browser/ui/aura/accessibility/ax_tree_source_aura.h b/chromecast/browser/ui/aura/accessibility/ax_tree_source_aura.h
index 741b2b6..b170f1c8 100644
--- a/chromecast/browser/ui/aura/accessibility/ax_tree_source_aura.h
+++ b/chromecast/browser/ui/aura/accessibility/ax_tree_source_aura.h
@@ -9,11 +9,13 @@
 
 #include <map>
 #include <memory>
+#include <string>
+#include <vector>
 
 #include "base/macros.h"
-#include "chromecast/browser/ui/aura/accessibility/ax_root_obj_wrapper.h"
 #include "ui/accessibility/ax_tree_data.h"
 #include "ui/accessibility/ax_tree_source.h"
+#include "ui/views/accessibility/ax_root_obj_wrapper.h"
 
 namespace ui {
 struct AXActionData;
@@ -25,10 +27,9 @@
 
 // This class exposes the views hierarchy as an accessibility tree permitting
 // use with other accessibility classes.
-class AXTreeSourceAura
-    : public ui::AXTreeSource<views::AXAuraObjWrapper*,
-                              ui::AXNodeData,
-                              ui::AXTreeData> {
+class AXTreeSourceAura : public ui::AXTreeSource<views::AXAuraObjWrapper*,
+                                                 ui::AXNodeData,
+                                                 ui::AXTreeData> {
  public:
   AXTreeSourceAura();
   ~AXTreeSourceAura() override;
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.cc b/chromeos/services/assistant/assistant_manager_service_impl.cc
index 130199c..903451e3 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.cc
+++ b/chromeos/services/assistant/assistant_manager_service_impl.cc
@@ -13,6 +13,7 @@
 #include "base/feature_list.h"
 #include "base/i18n/rtl.h"
 #include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/sys_info.h"
@@ -100,6 +101,8 @@
   // Set the flag to avoid starting the service multiple times.
   state_ = State::STARTED;
 
+  started_time_ = base::TimeTicks::Now();
+
   // LibAssistant creation will make file IO and sync wait. Post the creation to
   // background thread to avoid DCHECK.
   background_thread_.task_runner()->PostTaskAndReply(
@@ -704,6 +707,10 @@
 
   state_ = State::RUNNING;
 
+  const base::TimeDelta time_since_started =
+      base::TimeTicks::Now() - started_time_;
+  UMA_HISTOGRAM_TIMES("Assistant.ServiceStartTime", time_since_started);
+
   std::move(post_init_callback).Run();
   UpdateDeviceSettings();
 }
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.h b/chromeos/services/assistant/assistant_manager_service_impl.h
index 061286a..f461d760 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.h
+++ b/chromeos/services/assistant/assistant_manager_service_impl.h
@@ -231,6 +231,7 @@
   std::vector<uint8_t> assistant_screenshot_;
   std::string last_search_source_;
   base::Lock last_search_source_lock_;
+  base::TimeTicks started_time_;
 
   base::Thread background_thread_;
 
diff --git a/components/OWNERS b/components/OWNERS
index 683e6d0..a111e92 100644
--- a/components/OWNERS
+++ b/components/OWNERS
@@ -14,8 +14,6 @@
 per-file error_page_strings.grdp=file://components/error_page/OWNERS
 per-file management_strings.grdp=file://components/policy/OWNERS
 per-file ntp_snippets_strings.grdp=file://components/ntp_snippets/OWNERS
-per-file nux_email_strings.grdp=file://components/nux/OWNERS
-per-file nux_google_apps_strings.grdp=file://components/nux/OWNERS
 per-file omnibox_strings.grdp=file://components/omnibox/OWNERS
 per-file page_info_strings.grdp=file://chrome/browser/ui/page_info/OWNERS
 per-file page_info_strings_grdp=file://chrome/browser/ui/page_info/OWNERS
diff --git a/components/autofill/core/browser/autofill_manager.cc b/components/autofill/core/browser/autofill_manager.cc
index 9c2db9a..d88f532 100644
--- a/components/autofill/core/browser/autofill_manager.cc
+++ b/components/autofill/core/browser/autofill_manager.cc
@@ -1680,8 +1680,13 @@
       card.GetMatchingTypes(value, app_locale, &matching_types);
     }
 
-    if (matching_types.empty())
+    if (matching_types.empty()) {
       matching_types.insert(UNKNOWN_TYPE);
+      std::map<ServerFieldType, AutofillProfile::ValidityState>
+          matching_types_validities;
+      matching_types_validities[UNKNOWN_TYPE] = AutofillProfile::UNVALIDATED;
+      field->add_possible_types_validities(matching_types_validities);
+    }
 
     field->set_possible_types(matching_types);
   }
diff --git a/components/autofill/core/browser/autofill_manager_unittest.cc b/components/autofill/core/browser/autofill_manager_unittest.cc
index 1325168..84db4fc 100644
--- a/components/autofill/core/browser/autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/autofill_manager_unittest.cc
@@ -4472,7 +4472,6 @@
       public ::testing::WithParamInterface<
           std::tuple<ProfileMatchingTypesTestCase,
                      int,        // AutofillProfile::ValidityState
-                     bool,       // Enable AutofillVoteUsingInvalidProfileValues
                      bool>> {};  // AutofillProfile::ValidationSource
 
 const ProfileMatchingTypesTestCase kProfileMatchingTypesTestCases[] = {
@@ -4568,28 +4567,19 @@
   const auto& test_case = std::get<0>(GetParam());
   auto validity_state =
       static_cast<AutofillProfile::ValidityState>(std::get<1>(GetParam()));
-  const auto& vote_using_invalid_profile_data = std::get<2>(GetParam());
   const auto& validation_source =
-      static_cast<AutofillProfile::ValidationSource>(std::get<3>(GetParam()));
+      static_cast<AutofillProfile::ValidationSource>(std::get<2>(GetParam()));
 
   SCOPED_TRACE(base::StringPrintf(
       "Test: input_value='%s', field_type=%s, validity_state=%d, "
-      "validation_source=%d, vote_using_invalid_profile_data=%d",
+      "validation_source=%d ",
       test_case.input_value,
       AutofillType(test_case.field_type).ToString().c_str(), validity_state,
-      validation_source, vote_using_invalid_profile_data));
+      validation_source));
 
   ASSERT_LE(AutofillProfile::UNVALIDATED, validity_state);
   ASSERT_LE(validity_state, AutofillProfile::UNSUPPORTED);
 
-  // Enable/Disable ignoring invalid profile data for the scope of this test.
-  base::test::ScopedFeatureList sfl;
-  if (vote_using_invalid_profile_data) {
-    sfl.InitAndEnableFeature(features::kAutofillVoteUsingInvalidProfileData);
-  } else {
-    sfl.InitAndDisableFeature(features::kAutofillVoteUsingInvalidProfileData);
-  }
-
   // Set up the test profiles.
   std::vector<AutofillProfile> profiles;
   profiles.resize(3);
@@ -4611,10 +4601,14 @@
   profiles[2].set_guid("00000000-0000-0000-0000-000000000001");
 
   // Set the validity state for the matching field type.
-  auto field_type_group = AutofillType(test_case.field_type).group();
-  if (field_type_group != CREDIT_CARD) {
+  if (GroupTypeOfServerFieldType(test_case.field_type) != CREDIT_CARD) {
     for (auto& profile : profiles) {
-      if (profile.IsAnInvalidPhoneNumber(test_case.field_type)) {
+      if (test_case.field_type == UNKNOWN_TYPE) {
+        // An UNKNOWN type is always UNVALIDATED
+        validity_state = AutofillProfile::UNVALIDATED;
+      } else if (profile.IsAnInvalidPhoneNumber(test_case.field_type)) {
+        // a phone field is a compound field, an invalid part would make it
+        // invalid.
         validity_state = AutofillProfile::INVALID;
       }
       profile.SetValidityState(test_case.field_type, validity_state,
@@ -4647,54 +4641,22 @@
       profiles, credit_cards, "en-us", &form_structure);
 
   ASSERT_EQ(1U, form_structure.field_count());
+
   ServerFieldTypeSet possible_types = form_structure.field(0)->possible_types();
-  EXPECT_EQ(1U, possible_types.size());
+  ASSERT_EQ(1U, possible_types.size());
+  EXPECT_EQ(*possible_types.begin(), test_case.field_type);
 
-  if (test_case.field_type != EMPTY_TYPE &&
-      test_case.field_type != UNKNOWN_TYPE &&
-      ((validation_source == AutofillProfile::SERVER &&
-        GroupTypeOfServerFieldType(test_case.field_type) != CREDIT_CARD) ||
-       (validation_source == AutofillProfile::CLIENT &&
-        AutofillProfile::IsClientValidationSupportedForType(
-            test_case.field_type))) &&
-      validity_state == AutofillProfile::INVALID) {
-    // For the phone types we get more than one possible type match for one
-    // piece of data, therefore the count is not clear.
-    if (AutofillType(test_case.field_type).group() != PHONE_HOME) {
-      // There are two addresses in the US, every other type/value pair is
-      // unique.
-      int expected_count =
-          (test_case.field_type == ADDRESS_HOME_COUNTRY &&
-           base::StartsWith(test_case.input_value, "U",
-                            base::CompareCase::INSENSITIVE_ASCII))
-              ? 2
-              : 1;
-      histogram_tester.ExpectBucketCount(
-          "Autofill.InvalidProfileData.UsedForMetrics",
-          vote_using_invalid_profile_data, expected_count);
-    }
-    EXPECT_EQ(*possible_types.begin(), vote_using_invalid_profile_data
-                                           ? test_case.field_type
-                                           : UNKNOWN_TYPE);
-
+  // We don't add validity states for credit card fields.
+  if (GroupTypeOfServerFieldType(test_case.field_type) != CREDIT_CARD) {
     ServerFieldTypeValidityStatesMap possible_types_validities =
         form_structure.field(0)->possible_types_validities();
-    bool has_possible_types_validities =
-        field_type_group != CREDIT_CARD &&
-        (vote_using_invalid_profile_data ||
-         validity_state != AutofillProfile::INVALID);
-    EXPECT_EQ(has_possible_types_validities ? 1U : 0U,
-              possible_types_validities.size());
-    if (has_possible_types_validities) {
-      EXPECT_NE(possible_types_validities.end(),
-                possible_types_validities.find(test_case.field_type));
-      EXPECT_EQ(possible_types_validities[test_case.field_type][0],
-                (validation_source == AutofillProfile::SERVER)
-                    ? validity_state
-                    : AutofillProfile::UNVALIDATED);
-    }
-  } else {
-    EXPECT_EQ(*possible_types.begin(), test_case.field_type);
+    ASSERT_EQ(1U, possible_types_validities.size());
+    EXPECT_NE(possible_types_validities.end(),
+              possible_types_validities.find(test_case.field_type));
+    EXPECT_EQ(possible_types_validities[test_case.field_type][0],
+              (validation_source == AutofillProfile::SERVER)
+                  ? validity_state
+                  : AutofillProfile::UNVALIDATED);
   }
 }
 
@@ -4705,7 +4667,6 @@
         testing::ValuesIn(kProfileMatchingTypesTestCases),
         testing::Range(static_cast<int>(AutofillProfile::UNVALIDATED),
                        static_cast<int>(AutofillProfile::UNSUPPORTED) + 1),
-        testing::Bool(),
         testing::Bool()));
 
 // Tests that DeterminePossibleFieldTypesForUpload is called when a form is
@@ -4793,7 +4754,7 @@
     std::vector<AutofillProfile::ValidityState> expected_validity_states;
   } TestFieldData;
 
-  std::vector<TestFieldData> test_cases[2];
+  std::vector<TestFieldData> test_cases[3];
   // Tennessee appears in both of the user's profile as ADDRESS_HOME_STATE. In
   // the first one, it's VALID, and for the other, it's INVALID. Therefore, the
   // possible_field_types would only include the type ADDRESS_HOME_STATE, and
@@ -4806,6 +4767,9 @@
   // UNVALIDATED.
   test_cases[1].push_back(
       {"Alice", NAME_FIRST, {AutofillProfile::UNVALIDATED}});
+  // An UNKNOWN type is always UNVALIDATED.
+  test_cases[2].push_back(
+      {"What a beautiful day!", UNKNOWN_TYPE, {AutofillProfile::UNVALIDATED}});
 
   for (const std::vector<TestFieldData>& test_fields : test_cases) {
     FormData form;
diff --git a/components/autofill/core/browser/autofill_profile.cc b/components/autofill/core/browser/autofill_profile.cc
index ffe5996..4eb7e51 100644
--- a/components/autofill/core/browser/autofill_profile.cc
+++ b/components/autofill/core/browser/autofill_profile.cc
@@ -318,16 +318,6 @@
   }
 
   for (auto type : matching_types_in_this_profile) {
-    if (GetValidityState(type, CLIENT) == INVALID ||
-        GetValidityState(type, SERVER) == INVALID ||
-        IsAnInvalidPhoneNumber(type)) {
-      bool vote_using_invalid_data = base::FeatureList::IsEnabled(
-          features::kAutofillVoteUsingInvalidProfileData);
-      UMA_HISTOGRAM_BOOLEAN("Autofill.InvalidProfileData.UsedForMetrics",
-                            vote_using_invalid_data);
-      if (!vote_using_invalid_data)
-        continue;
-    }
     matching_types->insert(type);
   }
 }
@@ -349,16 +339,6 @@
   }
 
   for (auto type : matching_types_in_this_profile) {
-    if (GetValidityState(type, CLIENT) == INVALID ||
-        GetValidityState(type, SERVER) == INVALID ||
-        IsAnInvalidPhoneNumber(type)) {
-      bool vote_using_invalid_data = base::FeatureList::IsEnabled(
-          features::kAutofillVoteUsingInvalidProfileData);
-      UMA_HISTOGRAM_BOOLEAN("Autofill.InvalidProfileData.UsedForMetrics",
-                            vote_using_invalid_data);
-      if (!vote_using_invalid_data)
-        continue;
-    }
     if (matching_types_validities) {
       // TODO(crbug.com/879655): Set the client validities and look them up when
       // the server validities are not available.
diff --git a/components/autofill/core/common/autofill_features.cc b/components/autofill/core/common/autofill_features.cc
index c7e3341..a3eed220 100644
--- a/components/autofill/core/common/autofill_features.cc
+++ b/components/autofill/core/common/autofill_features.cc
@@ -264,9 +264,6 @@
 const base::Feature kAutofillUsePaymentsCustomerData{
     "AutofillUsePaymentsCustomerData", base::FEATURE_DISABLED_BY_DEFAULT};
 
-const base::Feature kAutofillVoteUsingInvalidProfileData{
-    "AutofillVoteUsingInvalidProfileData", base::FEATURE_ENABLED_BY_DEFAULT};
-
 // Controls whether password generation is offered automatically on fields
 // perceived as eligible for generation.
 #if defined(OS_ANDROID)
diff --git a/components/autofill/core/common/autofill_features.h b/components/autofill/core/common/autofill_features.h
index 681a6b7..8cb99c0 100644
--- a/components/autofill/core/common/autofill_features.h
+++ b/components/autofill/core/common/autofill_features.h
@@ -74,7 +74,6 @@
 extern const base::Feature kAutofillUpstreamUpdatePromptExplanation;
 extern const base::Feature kAutofillUpstreamUseGooglePayBrandingOnMobile;
 extern const base::Feature kAutofillUsePaymentsCustomerData;
-extern const base::Feature kAutofillVoteUsingInvalidProfileData;
 extern const base::Feature kAutomaticPasswordGeneration;
 extern const base::Feature kSingleClickAutofill;
 extern const base::Feature kAutofillCreditCardLocalCardMigration;
diff --git a/components/autofill_assistant/browser/actions/wait_for_dom_action.cc b/components/autofill_assistant/browser/actions/wait_for_dom_action.cc
index 335a756..c69b659c 100644
--- a/components/autofill_assistant/browser/actions/wait_for_dom_action.cc
+++ b/components/autofill_assistant/browser/actions/wait_for_dom_action.cc
@@ -31,6 +31,15 @@
 void WaitForDomAction::ProcessAction(ActionDelegate* delegate,
                                      ProcessActionCallback callback) {
   processed_action_proto_ = std::make_unique<ProcessedActionProto>();
+
+  // Fail the action if the selector is empty.
+  if (proto_.wait_for_dom().element().selectors().empty()) {
+    UpdateProcessedAction(false);
+    DLOG(ERROR) << "Empty selector, failing action.";
+    std::move(callback).Run(std::move(processed_action_proto_));
+    return;
+  }
+
   int check_rounds = kDefaultCheckRounds;
 
   int timeout_ms = proto_.wait_for_dom().timeout_ms();
diff --git a/components/browsing_data_strings.grdp b/components/browsing_data_strings.grdp
index ec780eb5..b1a53bbd 100644
--- a/components/browsing_data_strings.grdp
+++ b/components/browsing_data_strings.grdp
@@ -43,6 +43,18 @@
      =1 {1 password (synced)}
      other {# passwords (synced)}}
   </message>
+  <message name="IDS_DEL_SIGNIN_DATA_COUNTER" desc="A counter showing how many sign-in data items the user has.">
+    {COUNT, plural,
+     =0 {None}
+     =1 {sign-in data for 1 account}
+     other {sign-in data for # accounts}}
+  </message>
+  <message name="IDS_DEL_PASSWORDS_AND_SIGNIN_DATA_COUNTER_NONE" desc="A counter showing that the user has no passwords or other sign-in data.">
+    None
+  </message>
+  <message name="IDS_DEL_PASSWORDS_AND_SIGNIN_DATA_COUNTER_COMBINATION" desc="A counter showing that the user has passwords and other sign-in data. The types and their counts will be substituted, this message only provides the semicolon separator.">
+    <ph name="PASSWORDS">$1<ex>2 passwords</ex></ph>; <ph name="SIGNIN_DATA">$2<ex>sign-in data from 1 account</ex></ph>
+  </message>
   <message name="IDS_DEL_SITE_SETTINGS_COUNTER" desc="A counter showing how many sites with site settings the user has.">
     {COUNT, plural,
      =0 {None}
diff --git a/components/components_strings.grd b/components/components_strings.grd
index 81a471f2..81c7f67 100644
--- a/components/components_strings.grd
+++ b/components/components_strings.grd
@@ -196,8 +196,6 @@
       <part file="history_strings.grdp" />
       <part file="login_dialog_strings.grdp" />
       <part file="new_or_sad_tab_strings.grdp" />
-      <part file="nux_google_apps_strings.grdp" />
-      <part file="nux_email_strings.grdp" />
       <part file="ntp_snippets_strings.grdp" />
       <part file="omnibox_strings.grdp" />
       <part file="page_info_strings.grdp" />
diff --git a/components/flags_ui/resources/flags.html b/components/flags_ui/resources/flags.html
index ab34618..6b41dac 100644
--- a/components/flags_ui/resources/flags.html
+++ b/components/flags_ui/resources/flags.html
@@ -23,7 +23,7 @@
   <div class="flex-container">
     <div class="flex">
       <div class="search-container">
-        <input type="text" id="search" placeholder="Search flags" tabindex="1" autocomplete="search">
+        <input type="text" id="search" placeholder="Search flags" tabindex="1" autocomplete="off">
         <button class="clear-search" title="Clear search" tabindex="2"></button>
       </div>
     </div>
diff --git a/components/nux/README b/components/nux/README
deleted file mode 100644
index 4218a87..0000000
--- a/components/nux/README
+++ /dev/null
@@ -1,5 +0,0 @@
-New User Experience Experiment (NUX). Code here will enable new users to
-bookmark popular Google Apps as part of the first run steps.
-
-Feature is initially Windows Only. After collecting stats, we will determine
-how useful it is for users, this data will help decide next steps for component.
diff --git a/components/nux_google_apps_strings.grdp b/components/nux_google_apps_strings.grdp
deleted file mode 100644
index 1dd1bf24..0000000
--- a/components/nux_google_apps_strings.grdp
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<grit-part>
-  <message name="IDS_NUX_GOOGLE_APPS_GET_STARTED" desc="Message for a confirmation button that will add the selected apps from a list.">
-    Get started
-  </message>
-  <message name="IDS_NUX_GOOGLE_APPS_DESCRIPTION" desc="Description of what selecting apps and pressing 'Get started' will do.">
-    Get quick access to your favorite Google Apps
-  </message>
-  <message name="IDS_NUX_GOOGLE_APPS_DESCRIPTION_PROMO_BUBBLE" desc="Text shown when Google Apps have been added to highlight where bookmarks have been placed.">
-    Open apps easily with bookmarks
-  </message>
-</grit-part>
diff --git a/components/offline_items_collection/core/launch_location.h b/components/offline_items_collection/core/launch_location.h
index dcfca7f9..ec9d6b0 100644
--- a/components/offline_items_collection/core/launch_location.h
+++ b/components/offline_items_collection/core/launch_location.h
@@ -23,6 +23,8 @@
   SUGGESTION,
   // Due to clicking a suggestion on the net error page.
   NET_ERROR_SUGGESTION,
+  // From Download shelf.
+  DOWNLOAD_SHELF,
 };
 
 }  // namespace offline_items_collection
diff --git a/components/omnibox/browser/omnibox_field_trial.cc b/components/omnibox/browser/omnibox_field_trial.cc
index cba8ae0..675a7a1 100644
--- a/components/omnibox/browser/omnibox_field_trial.cc
+++ b/components/omnibox/browser/omnibox_field_trial.cc
@@ -774,18 +774,6 @@
 }
 
 bool OmniboxFieldTrial::IsHideSteadyStateUrlSchemeAndSubdomainsEnabled() {
-#if defined(OS_MACOSX)
-#if BUILDFLAG(MAC_VIEWS_BROWSER)
-  // Disable Steady State Elisions on Mac if browser is in Cocoa mode.
-  if (features::IsViewsBrowserCocoa())
-    return false;
-#else   // !BUILDFLAG(MAC_VIEWS_BROWSER)
-  // If MacViews is not even built on Mac, we must be on Cocoa, so disable
-  // State State Elisions.
-  return false;
-#endif  // BUILDFLAG(MAC_VIEWS_BROWSER)
-#endif  // defined(OS_MACOSX)
-
   return base::FeatureList::IsEnabled(
              omnibox::kUIExperimentHideSteadyStateUrlSchemeAndSubdomains) ||
          base::FeatureList::IsEnabled(features::kExperimentalUi);
diff --git a/components/password_manager/core/browser/http_credentials_cleaner.cc b/components/password_manager/core/browser/http_credentials_cleaner.cc
index c35e7ae..072d27c 100644
--- a/components/password_manager/core/browser/http_credentials_cleaner.cc
+++ b/components/password_manager/core/browser/http_credentials_cleaner.cc
@@ -5,7 +5,6 @@
 #include "components/password_manager/core/browser/http_credentials_cleaner.h"
 
 #include "base/metrics/histogram_functions.h"
-#include "base/strings/string_piece.h"
 #include "components/password_manager/core/browser/password_manager_util.h"
 #include "url/gurl.h"
 
@@ -30,11 +29,10 @@
   });
 
   for (auto& form : results) {
-    // The next signon-realm has the protocol excluded. For example if original
-    // signon_realm is "https://google.com/". After excluding protocol it
-    // becomes "google.com/".
-    FormKey form_key({GURL(form->signon_realm).GetContent(), form->scheme,
-                      form->username_value});
+    FormKey form_key(
+        {std::string(
+             password_manager_util::GetSignonRealmWithProtocolExcluded(*form)),
+         form->scheme, form->username_value});
     if (form->origin.SchemeIs(url::kHttpScheme)) {
       PostHSTSQueryForHostAndNetworkContext(
           form->origin, network_context_getter_.Run(),
diff --git a/components/password_manager/core/browser/http_credentials_cleaner_unittest.cc b/components/password_manager/core/browser/http_credentials_cleaner_unittest.cc
index f4b678d..571e3ed 100644
--- a/components/password_manager/core/browser/http_credentials_cleaner_unittest.cc
+++ b/components/password_manager/core/browser/http_credentials_cleaner_unittest.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "components/password_manager/core/browser/http_credentials_cleaner.h"
+
 #include "base/macros.h"
 #include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
@@ -17,77 +19,171 @@
 
 namespace password_manager {
 
-TEST(HttpCredentialCleaner, ReportHttpMigrationMetrics) {
-  enum class HttpCredentialType { kNoMatching, kEquivalent, kConflicting };
+namespace {
 
-  struct TestCase {
-    bool is_hsts_enabled;
-    autofill::PasswordForm::Scheme http_form_scheme;
-    bool same_signon_realm;
-    bool same_scheme;
-    bool same_username;
-    bool same_password;
-    HttpCredentialType expected;
-  };
+enum class HttpCredentialType { kNoMatching, kEquivalent, kConflicting };
 
+struct TestCase {
+  bool is_hsts_enabled;
+  autofill::PasswordForm::Scheme http_form_scheme;
+  bool same_signon_realm;
+  bool same_scheme;
+  bool same_username;
+  bool same_password;
+  // |expected| == kNoMatching if:
+  // same_signon_realm & same_scheme & same_username = false
+  // Otherwise:
+  // |expected| == kEquivalent if:
+  // same_signon_realm & same_scheme & same_username & same_password = true
+  // Otherwise:
+  // |expected| == kConflicting if:
+  // same_signon_realm & same_scheme & same_username = true but same_password =
+  // false
+  HttpCredentialType expected;
+};
+
+constexpr static TestCase kCases[] = {
+
+    {true, autofill::PasswordForm::Scheme::SCHEME_HTML, false, true, true, true,
+     HttpCredentialType::kNoMatching},
+    {true, autofill::PasswordForm::Scheme::SCHEME_HTML, true, false, true, true,
+     HttpCredentialType::kNoMatching},
+    {true, autofill::PasswordForm::Scheme::SCHEME_HTML, true, true, false, true,
+     HttpCredentialType::kNoMatching},
+    {true, autofill::PasswordForm::Scheme::SCHEME_HTML, true, true, true, false,
+     HttpCredentialType::kConflicting},
+    {true, autofill::PasswordForm::Scheme::SCHEME_HTML, true, true, true, true,
+     HttpCredentialType::kEquivalent},
+
+    {false, autofill::PasswordForm::Scheme::SCHEME_HTML, false, true, true,
+     true, HttpCredentialType::kNoMatching},
+    {false, autofill::PasswordForm::Scheme::SCHEME_HTML, true, false, true,
+     true, HttpCredentialType::kNoMatching},
+    {false, autofill::PasswordForm::Scheme::SCHEME_HTML, true, true, false,
+     true, HttpCredentialType::kNoMatching},
+    {false, autofill::PasswordForm::Scheme::SCHEME_HTML, true, true, true,
+     false, HttpCredentialType::kConflicting},
+    {false, autofill::PasswordForm::Scheme::SCHEME_HTML, true, true, true, true,
+     HttpCredentialType::kEquivalent},
+
+    {true, autofill::PasswordForm::Scheme::SCHEME_BASIC, false, true, true,
+     true, HttpCredentialType::kNoMatching},
+    {true, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, false, true,
+     true, HttpCredentialType::kNoMatching},
+    {true, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, true, false,
+     true, HttpCredentialType::kNoMatching},
+    {true, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, true, true,
+     false, HttpCredentialType::kConflicting},
+    {true, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, true, true, true,
+     HttpCredentialType::kEquivalent},
+
+    {false, autofill::PasswordForm::Scheme::SCHEME_BASIC, false, true, true,
+     true, HttpCredentialType::kNoMatching},
+    {false, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, false, true,
+     true, HttpCredentialType::kNoMatching},
+    {false, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, true, false,
+     true, HttpCredentialType::kNoMatching},
+    {false, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, true, true,
+     false, HttpCredentialType::kConflicting},
+    {false, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, true, true,
+     true, HttpCredentialType::kEquivalent}};
+
+}  // namespace
+
+class HttpCredentialCleanerTest : public ::testing::TestWithParam<TestCase> {
+ public:
+  HttpCredentialCleanerTest() = default;
+
+  ~HttpCredentialCleanerTest() override = default;
+
+ protected:
+  scoped_refptr<TestPasswordStore> store_ =
+      base::MakeRefCounted<TestPasswordStore>();
+
+  DISALLOW_COPY_AND_ASSIGN(HttpCredentialCleanerTest);
+};
+
+TEST_P(HttpCredentialCleanerTest, ReportHttpMigrationMetrics) {
   struct Histogram {
     bool test_hsts_enabled;
     HttpCredentialType test_type;
     std::string histogram_name;
   };
 
-  constexpr static TestCase cases[] = {
+  static const std::string signon_realm[2] = {"https://example.org/realm/",
+                                              "https://example.org/"};
 
-      {true, autofill::PasswordForm::Scheme::SCHEME_HTML, false, true, true,
-       true, HttpCredentialType::kNoMatching},
-      {true, autofill::PasswordForm::Scheme::SCHEME_HTML, true, false, true,
-       true, HttpCredentialType::kNoMatching},
-      {true, autofill::PasswordForm::Scheme::SCHEME_HTML, true, true, false,
-       true, HttpCredentialType::kNoMatching},
-      {true, autofill::PasswordForm::Scheme::SCHEME_HTML, true, true, true,
-       false, HttpCredentialType::kConflicting},
-      {true, autofill::PasswordForm::Scheme::SCHEME_HTML, true, true, true,
-       true, HttpCredentialType::kEquivalent},
+  static const base::string16 username[2] = {base::ASCIIToUTF16("user0"),
+                                             base::ASCIIToUTF16("user1")};
 
-      {false, autofill::PasswordForm::Scheme::SCHEME_HTML, false, true, true,
-       true, HttpCredentialType::kNoMatching},
-      {false, autofill::PasswordForm::Scheme::SCHEME_HTML, true, false, true,
-       true, HttpCredentialType::kNoMatching},
-      {false, autofill::PasswordForm::Scheme::SCHEME_HTML, true, true, false,
-       true, HttpCredentialType::kNoMatching},
-      {false, autofill::PasswordForm::Scheme::SCHEME_HTML, true, true, true,
-       false, HttpCredentialType::kConflicting},
-      {false, autofill::PasswordForm::Scheme::SCHEME_HTML, true, true, true,
-       true, HttpCredentialType::kEquivalent},
+  static const base::string16 password[2] = {base::ASCIIToUTF16("pass0"),
+                                             base::ASCIIToUTF16("pass1")};
 
-      {true, autofill::PasswordForm::Scheme::SCHEME_BASIC, false, true, true,
-       true, HttpCredentialType::kNoMatching},
-      {true, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, false, true,
-       true, HttpCredentialType::kNoMatching},
-      {true, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, true, false,
-       true, HttpCredentialType::kNoMatching},
-      {true, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, true, true,
-       false, HttpCredentialType::kConflicting},
-      {true, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, true, true,
-       true, HttpCredentialType::kEquivalent},
+  base::test::ScopedTaskEnvironment scoped_task_environment;
+  ASSERT_TRUE(store_->Init(syncer::SyncableService::StartSyncFlare(), nullptr));
+  TestCase test = GetParam();
+  SCOPED_TRACE(testing::Message()
+               << "is_hsts_enabled=" << test.is_hsts_enabled
+               << ", http_form_scheme="
+               << static_cast<int>(test.http_form_scheme)
+               << ", same_signon_realm=" << test.same_signon_realm
+               << ", same_scheme=" << test.same_scheme
+               << ", same_username=" << test.same_username
+               << ", same_password=" << test.same_password);
 
-      {false, autofill::PasswordForm::Scheme::SCHEME_BASIC, false, true, true,
-       true, HttpCredentialType::kNoMatching},
-      {false, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, false, true,
-       true, HttpCredentialType::kNoMatching},
-      {false, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, true, false,
-       true, HttpCredentialType::kNoMatching},
-      {false, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, true, true,
-       false, HttpCredentialType::kConflicting},
-      {false, autofill::PasswordForm::Scheme::SCHEME_BASIC, true, true, true,
-       true, HttpCredentialType::kEquivalent}
+  autofill::PasswordForm http_form;
+  http_form.origin = GURL("http://example.org/");
+  http_form.signon_realm = "http://example.org/";
+  http_form.scheme = test.http_form_scheme;
+  http_form.username_value = username[1];
+  http_form.password_value = password[1];
+  store_->AddLogin(http_form);
 
-  };
+  autofill::PasswordForm https_form;
+  https_form.origin = GURL("https://example.org/");
+  https_form.signon_realm = signon_realm[test.same_signon_realm];
+  https_form.username_value = username[test.same_username];
+  https_form.password_value = password[test.same_password];
+  https_form.scheme = test.http_form_scheme;
+  if (!test.same_scheme) {
+    https_form.scheme =
+        (http_form.scheme == autofill::PasswordForm::Scheme::SCHEME_BASIC
+             ? autofill::PasswordForm::Scheme::SCHEME_HTML
+             : autofill::PasswordForm::Scheme::SCHEME_BASIC);
+  }
+  store_->AddLogin(https_form);
 
-  const base::string16 username[2] = {base::ASCIIToUTF16("user0"),
-                                      base::ASCIIToUTF16("user1")};
-  const base::string16 password[2] = {base::ASCIIToUTF16("pass0"),
-                                      base::ASCIIToUTF16("pass1")};
+  auto request_context = base::MakeRefCounted<net::TestURLRequestContextGetter>(
+      base::ThreadTaskRunnerHandle::Get());
+  network::mojom::NetworkContextPtr network_context_pipe;
+  auto network_context = std::make_unique<network::NetworkContext>(
+      nullptr, mojo::MakeRequest(&network_context_pipe),
+      request_context->GetURLRequestContext());
+
+  if (test.is_hsts_enabled) {
+    network_context->AddHSTSForTesting(
+        http_form.origin.host(), base::Time::Max(),
+        false /*include_subdomains*/, base::DoNothing());
+  }
+  scoped_task_environment.RunUntilIdle();
+
+  const TestPasswordStore::PasswordMap passwords_before_cleaning =
+      store_->stored_passwords();
+
+  base::HistogramTester histogram_tester;
+
+  password_manager_util::ReportHttpMigrationMetrics(
+      store_,
+      base::BindLambdaForTesting([&]() -> network::mojom::NetworkContext* {
+        // This needs to be network_context_pipe.get() and
+        // not network_context.get() to make HSTS queries asynchronous, which
+        // is what the progress tracking logic in HttpMetricsMigrationReporter
+        // assumes.  This also matches reality, since
+        // StoragePartition::GetNetworkContext will return a mojo pipe
+        // even in the in-process case.
+        return network_context_pipe.get();
+      }));
+  scoped_task_environment.RunUntilIdle();
 
   std::vector<Histogram> histograms_to_test;
   for (bool test_hsts_enabled : {true, false}) {
@@ -106,87 +202,19 @@
          "PasswordManager.HttpCredentialsWithConflictingHttpsCredential." +
              suffix});
   }
-  for (const auto& test : cases) {
-    SCOPED_TRACE(testing::Message()
-                 << "is_hsts_enabled=" << test.is_hsts_enabled
-                 << ", http_form_scheme="
-                 << static_cast<int>(test.http_form_scheme)
-                 << ", same_signon_realm=" << test.same_signon_realm
-                 << ", same_scheme=" << test.same_scheme
-                 << ", same_username=" << test.same_username
-                 << ", same_password=" << test.same_password);
-
-    base::test::ScopedTaskEnvironment scoped_task_environment;
-    auto request_context =
-        base::MakeRefCounted<net::TestURLRequestContextGetter>(
-            base::ThreadTaskRunnerHandle::Get());
-    network::mojom::NetworkContextPtr network_context_pipe;
-    auto network_context = std::make_unique<network::NetworkContext>(
-        nullptr, mojo::MakeRequest(&network_context_pipe),
-        request_context->GetURLRequestContext());
-
-    auto password_store =
-        base::MakeRefCounted<password_manager::TestPasswordStore>();
-    ASSERT_TRUE(password_store->Init(syncer::SyncableService::StartSyncFlare(),
-                                     nullptr));
-
-    autofill::PasswordForm http_form;
-    http_form.origin = GURL("http://example.org/");
-    http_form.signon_realm = http_form.origin.GetOrigin().spec();
-    http_form.scheme = test.http_form_scheme;
-    http_form.username_value = username[1];
-    http_form.password_value = password[1];
-    password_store->AddLogin(http_form);
-
-    autofill::PasswordForm https_form;
-    https_form.origin = GURL("https://example.org/");
-    https_form.scheme = test.http_form_scheme;
-    if (!test.same_scheme) {
-      if (https_form.scheme == autofill::PasswordForm::Scheme::SCHEME_BASIC)
-        https_form.scheme = autofill::PasswordForm::Scheme::SCHEME_HTML;
-      else
-        https_form.scheme = autofill::PasswordForm::Scheme::SCHEME_BASIC;
-    }
-
-    https_form.signon_realm = https_form.origin.GetOrigin().spec();
-    if (!test.same_signon_realm)
-      https_form.signon_realm += "different/";
-
-    https_form.username_value = username[test.same_username];
-    https_form.password_value = password[test.same_password];
-    password_store->AddLogin(https_form);
-
-    if (test.is_hsts_enabled) {
-      network_context->AddHSTSForTesting(
-          http_form.origin.host(), base::Time::Max(),
-          false /*include_subdomains*/, base::DoNothing());
-    }
-    scoped_task_environment.RunUntilIdle();
-
-    base::HistogramTester histogram_tester;
-    password_manager_util::ReportHttpMigrationMetrics(
-        password_store,
-        base::BindLambdaForTesting([&]() -> network::mojom::NetworkContext* {
-          // This needs to be network_context_pipe.get() and
-          // not network_context.get() to make HSTS queries asynchronous, which
-          // is what the progress tracking logic in HttpMetricsMigrationReporter
-          // assumes.  This also matches reality, since
-          // StoragePartition::GetNetworkContext will return a mojo pipe
-          // even in the in-process case.
-          return network_context_pipe.get();
-        }));
-    scoped_task_environment.RunUntilIdle();
-
-    for (const auto& histogram : histograms_to_test) {
-      int sample =
-          static_cast<int>(histogram.test_type == test.expected &&
-                           histogram.test_hsts_enabled == test.is_hsts_enabled);
-      histogram_tester.ExpectUniqueSample(histogram.histogram_name, sample, 1);
-    }
-
-    password_store->ShutdownOnUIThread();
-    scoped_task_environment.RunUntilIdle();
+  for (const auto& histogram : histograms_to_test) {
+    int sample =
+        static_cast<int>(histogram.test_type == test.expected &&
+                         histogram.test_hsts_enabled == test.is_hsts_enabled);
+    histogram_tester.ExpectUniqueSample(histogram.histogram_name, sample, 1);
   }
+
+  store_->ShutdownOnUIThread();
+  scoped_task_environment.RunUntilIdle();
 }
 
+INSTANTIATE_TEST_CASE_P(,
+                        HttpCredentialCleanerTest,
+                        ::testing::ValuesIn(kCases));
+
 }  // namespace password_manager
\ No newline at end of file
diff --git a/components/password_manager/core/browser/invalid_realm_credential_cleaner.cc b/components/password_manager/core/browser/invalid_realm_credential_cleaner.cc
index aa72fc8..a5437f0 100644
--- a/components/password_manager/core/browser/invalid_realm_credential_cleaner.cc
+++ b/components/password_manager/core/browser/invalid_realm_credential_cleaner.cc
@@ -13,6 +13,7 @@
 
 #include "base/strings/string_piece.h"
 #include "components/autofill/core/common/password_form.h"
+#include "components/password_manager/core/browser/password_manager_util.h"
 #include "components/password_manager/core/browser/password_store.h"
 #include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/prefs/pref_service.h"
@@ -198,12 +199,9 @@
   // HTTP forms to the expected signon_realm (excluding the protocol).
   std::map<FormKeyForHttpMatch, std::string> http_credentials_map;
   for (const auto& form : http_forms) {
-    base::StringPiece signon_realm = form->signon_realm;
-    // Find the web origin in the signon_realm and remove what is before it.
-    // This will result in removing the protocol ("http://").
-    signon_realm = signon_realm.substr(
-        signon_realm.find(form->origin.GetOrigin().GetContent()));
-    http_credentials_map.emplace(GetFormKeyForHttpMatch(*form), signon_realm);
+    http_credentials_map.emplace(
+        GetFormKeyForHttpMatch(*form),
+        password_manager_util::GetSignonRealmWithProtocolExcluded(*form));
   }
 
   // Separate HTML and non-HTML HTTPS credentials.
diff --git a/components/password_manager/core/browser/password_manager_util.cc b/components/password_manager/core/browser/password_manager_util.cc
index f8b6087..c0511d16 100644
--- a/components/password_manager/core/browser/password_manager_util.cc
+++ b/components/password_manager/core/browser/password_manager_util.cc
@@ -243,6 +243,20 @@
       base::TimeDelta::FromSeconds(delay_in_seconds));
 }
 
+base::StringPiece GetSignonRealmWithProtocolExcluded(const PasswordForm& form) {
+  base::StringPiece signon_realm_protocol_excluded = form.signon_realm;
+
+  // Find the web origin (with protocol excluded) in the signon_realm.
+  const size_t after_protocol =
+      signon_realm_protocol_excluded.find(form.origin.GetOrigin().GetContent());
+  DCHECK_NE(after_protocol, base::StringPiece::npos);
+
+  // Keep the string starting with position |after_protocol|.
+  signon_realm_protocol_excluded =
+      signon_realm_protocol_excluded.substr(after_protocol);
+  return signon_realm_protocol_excluded;
+}
+
 void FindBestMatches(
     std::vector<const PasswordForm*> matches,
     std::map<base::string16, const PasswordForm*>* best_matches,
diff --git a/components/password_manager/core/browser/password_manager_util.h b/components/password_manager/core/browser/password_manager_util.h
index cb80509a..a855bab 100644
--- a/components/password_manager/core/browser/password_manager_util.h
+++ b/components/password_manager/core/browser/password_manager_util.h
@@ -105,6 +105,14 @@
     PrefService* prefs,
     int delay_in_seconds);
 
+// Excluding protocol from a signon_realm means to remove from the signon_realm
+// what is before the web origin (with the protocol excluded as well). For
+// example if the signon_realm is "https://www.google.com/", after
+// excluding protocol it becomes "www.google.com/".
+// This assumes that the |form|'s origin is a substring of the signon_realm.
+base::StringPiece GetSignonRealmWithProtocolExcluded(
+    const autofill::PasswordForm& form);
+
 // Report metrics about HTTP to HTTPS migration process. This function cannot be
 // used on iOS platform because the HSTS query is not supported.
 // |network_context_getter| should return nullptr if it can't get the network
diff --git a/components/password_manager/core/browser/password_manager_util_unittest.cc b/components/password_manager/core/browser/password_manager_util_unittest.cc
index c24abe8..1577721 100644
--- a/components/password_manager/core/browser/password_manager_util_unittest.cc
+++ b/components/password_manager/core/browser/password_manager_util_unittest.cc
@@ -208,6 +208,18 @@
   }
 }
 
+TEST(PasswordManagerUtil, GetSignonRealmWithProtocolExcluded) {
+  autofill::PasswordForm http_form;
+  http_form.origin = GURL("http://www.google.com/page-1/");
+  http_form.signon_realm = "http://www.google.com/";
+  EXPECT_EQ(GetSignonRealmWithProtocolExcluded(http_form), "www.google.com/");
+
+  autofill::PasswordForm https_form;
+  https_form.origin = GURL("https://www.google.com/page-1/");
+  https_form.signon_realm = "https://www.google.com/";
+  EXPECT_EQ(GetSignonRealmWithProtocolExcluded(https_form), "www.google.com/");
+}
+
 TEST(PasswordManagerUtil, FindBestMatches) {
   const int kNotFound = -1;
   struct TestMatch {
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index 362898c..0cb6c79c 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -9478,9 +9478,9 @@
         },
       ],
       'supported_on': [
-        'chrome.*:58-',
-        'chrome_os:58-',
-        'android:58-',
+        'chrome.*:58-74',
+        'chrome_os:58-74',
+        'android:58-74',
       ],
       'features': {
         'dynamic_refresh': True,
@@ -9490,7 +9490,7 @@
       'id': 361,
       'caption': '''Maximum SSL version enabled''',
       'tags': ['system-security'],
-      'desc': '''Warning: The max TLS version policy will be entirely removed from <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> around version 72 (around January 2019).
+      'desc': '''Warning: The max TLS version policy will be entirely removed from <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> around version 75 (around June 2019).
 
       If this policy is not configured then <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> uses the default maximum version.
 
diff --git a/components/sync/engine_impl/loopback_server/loopback_server.cc b/components/sync/engine_impl/loopback_server/loopback_server.cc
index 1b0de9f..29c3977 100644
--- a/components/sync/engine_impl/loopback_server/loopback_server.cc
+++ b/components/sync/engine_impl/loopback_server/loopback_server.cc
@@ -133,7 +133,8 @@
 }  // namespace
 
 LoopbackServer::LoopbackServer(const base::FilePath& persistent_file)
-    : version_(0),
+    : strong_consistency_model_enabled_(false),
+      version_(0),
       store_birthday_(0),
       persistent_file_(persistent_file),
       observer_for_tests_(nullptr) {
@@ -271,6 +272,10 @@
   SaveStateToFile(persistent_file_);
 }
 
+void LoopbackServer::EnableStrongConsistencyWithConflictDetectionModel() {
+  strong_consistency_model_enabled_ = true;
+}
+
 bool LoopbackServer::HandleGetUpdatesRequest(
     const sync_pb::GetUpdatesMessage& get_updates,
     sync_pb::GetUpdatesResponse* response) {
@@ -323,6 +328,20 @@
     return string();
   }
 
+  // If strong consistency model is enabled (usually on a per-datatype level,
+  // but implemented here as a global state), the server detects version
+  // mismatches and responds with CONFLICT.
+  if (strong_consistency_model_enabled_) {
+    EntityMap::const_iterator iter = entities_.find(client_entity.id_string());
+    if (iter != entities_.end()) {
+      const LoopbackServerEntity* server_entity = iter->second.get();
+      if (server_entity->GetVersion() != client_entity.version()) {
+        entry_response->set_response_type(sync_pb::CommitResponse::CONFLICT);
+        return client_entity.id_string();
+      }
+    }
+  }
+
   std::unique_ptr<LoopbackServerEntity> entity;
   syncer::ModelType type = GetModelType(client_entity);
   if (client_entity.deleted()) {
diff --git a/components/sync/engine_impl/loopback_server/loopback_server.h b/components/sync/engine_impl/loopback_server/loopback_server.h
index cecc8f2..407f2c6e 100644
--- a/components/sync/engine_impl/loopback_server/loopback_server.h
+++ b/components/sync/engine_impl/loopback_server/loopback_server.h
@@ -53,6 +53,9 @@
                      int64_t* response_code,
                      std::string* response);
 
+  // Enables strong consistency model (i.e. server detects conflicts).
+  void EnableStrongConsistencyWithConflictDetectionModel();
+
  private:
   // Allow the FakeServer decorator to inspect the internals of this class.
   friend class fake_server::FakeServer;
@@ -177,6 +180,8 @@
     observer_for_tests_ = observer;
   }
 
+  bool strong_consistency_model_enabled_;
+
   // This is the last version number assigned to an entity. The next entity will
   // have a version number of version_ + 1.
   int64_t version_;
diff --git a/components/sync/model/model_type_change_processor.cc b/components/sync/model/model_type_change_processor.cc
index 6cb0f5ff..2b42fa6 100644
--- a/components/sync/model/model_type_change_processor.cc
+++ b/components/sync/model/model_type_change_processor.cc
@@ -4,8 +4,6 @@
 
 #include "components/sync/model/model_type_change_processor.h"
 
-#include "components/sync/model_impl/client_tag_based_model_type_processor.h"
-
 namespace syncer {
 
 ModelTypeChangeProcessor::ModelTypeChangeProcessor() {}
diff --git a/components/sync/model/model_type_change_processor.h b/components/sync/model/model_type_change_processor.h
index 399a9b36..d9105de 100644
--- a/components/sync/model/model_type_change_processor.h
+++ b/components/sync/model/model_type_change_processor.h
@@ -30,7 +30,8 @@
   // Inform the processor of a new or updated entity. The |entity_data| param
   // does not need to be fully set, but it should at least have specifics and
   // non-unique name. The processor will fill in the rest if the bridge does
-  // not have a reason to care.
+  // not have a reason to care. For example, if |client_tag_hash| is not set,
+  // the bridge's GetClientTag() will be exercised (and must be supported).
   virtual void Put(const std::string& storage_key,
                    std::unique_ptr<EntityData> entity_data,
                    MetadataChangeList* metadata_change_list) = 0;
diff --git a/components/sync/model/model_type_sync_bridge.cc b/components/sync/model/model_type_sync_bridge.cc
index 7684116..28278a1 100644
--- a/components/sync/model/model_type_sync_bridge.cc
+++ b/components/sync/model/model_type_sync_bridge.cc
@@ -24,6 +24,10 @@
 void ModelTypeSyncBridge::OnSyncStarting(
     const DataTypeActivationRequest& request) {}
 
+bool ModelTypeSyncBridge::SupportsGetClientTag() const {
+  return true;
+}
+
 bool ModelTypeSyncBridge::SupportsGetStorageKey() const {
   return true;
 }
diff --git a/components/sync/model/model_type_sync_bridge.h b/components/sync/model/model_type_sync_bridge.h
index 3c6abd1..411fc44 100644
--- a/components/sync/model/model_type_sync_bridge.h
+++ b/components/sync/model/model_type_sync_bridge.h
@@ -103,6 +103,8 @@
   // Used for getting all data in Sync Node Browser of chrome://sync-internals.
   virtual void GetAllDataForDebugging(DataCallback callback) = 0;
 
+  // Must not be called unless SupportsGetClientTag() returns true.
+  //
   // Get or generate a client tag for |entity_data|. This must be the same tag
   // that was/would have been generated in the SyncableService/Directory world
   // for backward compatibility with pre-USS clients. The only time this
@@ -112,6 +114,8 @@
   // GetStorageKey(). Only the hash of this value is kept.
   virtual std::string GetClientTag(const EntityData& entity_data) = 0;
 
+  // Must not be called unless SupportsGetStorageKey() returns true.
+  //
   // Get or generate a storage key for |entity_data|. This will only ever be
   // called once when first encountering a remote entity. Local changes will
   // provide their storage keys directly to Put instead of using this method.
@@ -121,6 +125,12 @@
   // type should strive to keep these keys as small as possible.
   virtual std::string GetStorageKey(const EntityData& entity_data) = 0;
 
+  // Whether or not the bridge is capable of producing a client tag from
+  // |EntityData| (usually remote changes), via GetClientTag(). Most bridges do,
+  // but in rare cases including commit-only types and read-only types, it may
+  // not.
+  virtual bool SupportsGetClientTag() const;
+
   // By returning true in this function datatype indicates that it can generate
   // storage key from EntityData. In this case for all new entities received
   // from server, change processor will call GetStorageKey and update
diff --git a/components/sync/model_impl/client_tag_based_model_type_processor.cc b/components/sync/model_impl/client_tag_based_model_type_processor.cc
index 103ac956..ccd425a1 100644
--- a/components/sync/model_impl/client_tag_based_model_type_processor.cc
+++ b/components/sync/model_impl/client_tag_based_model_type_processor.cc
@@ -370,9 +370,10 @@
     // |data->client_tag_hash|, so let's ask for the client tag if needed.
     if (data->client_tag_hash.empty()) {
       data->client_tag_hash = GetClientTagHash(storage_key, *data);
-    } else {
+    } else if (bridge_->SupportsGetClientTag()) {
       // If the Put() call already included the client tag, let's verify that
-      // it's consistent with the bridge's regular GetClientTag() function.
+      // it's consistent with the bridge's regular GetClientTag() function (if
+      // supported by the bridge).
       DCHECK_EQ(data->client_tag_hash,
                 GenerateSyncableHash(type_, bridge_->GetClientTag(*data)));
     }
@@ -427,6 +428,9 @@
     MetadataChangeList* metadata_change_list) {
   const std::string& client_tag_hash = entity_data.client_tag_hash;
   DCHECK(!client_tag_hash.empty());
+  DCHECK(!storage_key.empty());
+  DCHECK(!bridge_->SupportsGetStorageKey());
+
   ProcessorEntityTracker* entity = GetEntityForTagHash(client_tag_hash);
   DCHECK(entity);
 
@@ -585,6 +589,7 @@
     const ModelType& type,
     ModelTypeSyncBridge* bridge,
     const UpdateResponseDataList& updates) {
+  DCHECK(bridge->SupportsGetClientTag());
   UpdateResponseDataList updates_with_client_tags;
   for (const UpdateResponseData& update : updates) {
     if (update.entity->parent_id == "0") {
@@ -677,7 +682,7 @@
   }
 
   // Filter out unexpected client tag hashes.
-  if (!data.is_deleted() &&
+  if (!data.is_deleted() && bridge_->SupportsGetClientTag() &&
       client_tag_hash !=
           GenerateSyncableHash(type_, bridge_->GetClientTag(data))) {
     DLOG(WARNING) << "Received unexpected client tag hash: " << client_tag_hash;
@@ -1096,8 +1101,9 @@
 
 std::string ClientTagBasedModelTypeProcessor::GetClientTagHash(
     const std::string& storage_key,
-    const EntityData& data) {
+    const EntityData& data) const {
   auto iter = storage_key_to_tag_hash_.find(storage_key);
+  DCHECK(bridge_->SupportsGetClientTag());
   return iter == storage_key_to_tag_hash_.end()
              ? GenerateSyncableHash(type_, bridge_->GetClientTag(data))
              : iter->second;
@@ -1138,8 +1144,10 @@
 
 ProcessorEntityTracker* ClientTagBasedModelTypeProcessor::CreateEntity(
     const EntityData& data) {
-  DCHECK_EQ(data.client_tag_hash,
-            GenerateSyncableHash(type_, bridge_->GetClientTag(data)));
+  if (bridge_->SupportsGetClientTag()) {
+    DCHECK_EQ(data.client_tag_hash,
+              GenerateSyncableHash(type_, bridge_->GetClientTag(data)));
+  }
   std::string storage_key;
   if (bridge_->SupportsGetStorageKey())
     storage_key = bridge_->GetStorageKey(data);
diff --git a/components/sync/model_impl/client_tag_based_model_type_processor.h b/components/sync/model_impl/client_tag_based_model_type_processor.h
index f669942..5d78063 100644
--- a/components/sync/model_impl/client_tag_based_model_type_processor.h
+++ b/components/sync/model_impl/client_tag_based_model_type_processor.h
@@ -37,9 +37,11 @@
 
 // A sync component embedded on the model type's thread that tracks entity
 // metadata in the model store and coordinates communication between sync and
-// model type threads. See
-// //docs/sync/uss/client_tag_based_model_type_processor.md for a more thorough
-// description.
+// model type threads. All changes in flight (either incoming from the server
+// or local changes reported by the bridge) must specify a client tag.
+//
+// See //docs/sync/uss/client_tag_based_model_type_processor.md for a more
+// thorough description.
 class ClientTagBasedModelTypeProcessor : public ModelTypeProcessor,
                                          public ModelTypeChangeProcessor,
                                          public ModelTypeControllerDelegate {
@@ -175,7 +177,7 @@
   // with |data| if the lookup finds nothing. Does not update the storage key to
   // client tag hash mapping.
   std::string GetClientTagHash(const std::string& storage_key,
-                               const EntityData& data);
+                               const EntityData& data) const;
 
   // Gets the entity for the given storage key, or null if there isn't one.
   ProcessorEntityTracker* GetEntityForStorageKey(
diff --git a/components/sync/test/fake_server/fake_server.cc b/components/sync/test/fake_server/fake_server.cc
index f069a4a..c2b55fc 100644
--- a/components/sync/test/fake_server/fake_server.cc
+++ b/components/sync/test/fake_server/fake_server.cc
@@ -441,6 +441,11 @@
   network_enabled_ = false;
 }
 
+void FakeServer::EnableStrongConsistencyWithConflictDetectionModel() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  loopback_server_->EnableStrongConsistencyWithConflictDetectionModel();
+}
+
 base::WeakPtr<FakeServer> FakeServer::AsWeakPtr() {
   DCHECK(thread_checker_.CalledOnValidThread());
   return weak_ptr_factory_.GetWeakPtr();
diff --git a/components/sync/test/fake_server/fake_server.h b/components/sync/test/fake_server/fake_server.h
index 2abc1c0..fdede785 100644
--- a/components/sync/test/fake_server/fake_server.h
+++ b/components/sync/test/fake_server/fake_server.h
@@ -164,6 +164,9 @@
   // This can be used to trigger exponential backoff in the client.
   void DisableNetwork();
 
+  // Enables strong consistency model (i.e. server detects conflicts).
+  void EnableStrongConsistencyWithConflictDetectionModel();
+
   // Implement LoopbackServer::ObserverForTests:
   void OnCommit(const std::string& committer_id,
                 syncer::ModelTypeSet committed_model_types) override;
diff --git a/content/browser/child_process_security_policy_impl.cc b/content/browser/child_process_security_policy_impl.cc
index ac1c0a0..09ebf9c 100644
--- a/content/browser/child_process_security_policy_impl.cc
+++ b/content/browser/child_process_security_policy_impl.cc
@@ -795,7 +795,8 @@
 }
 
 bool ChildProcessSecurityPolicyImpl::CanCommitURL(int child_id,
-                                                  const GURL& url) {
+                                                  const GURL& url,
+                                                  bool check_origin_locks) {
   if (!url.is_valid())
     return false;  // Can't commit invalid URLs.
 
@@ -813,9 +814,19 @@
       return false;
 
     url::Origin origin = url::Origin::Create(url);
-    return origin.unique() || CanCommitURL(child_id, GURL(origin.Serialize()));
+    return origin.unique() ||
+           CanCommitURL(child_id, GURL(origin.Serialize()), check_origin_locks);
   }
 
+  // With site isolation, a URL from a site may only be committed in a process
+  // dedicated to that site.  This check will ensure that |url| can't commit if
+  // the process is locked to a different site.  Note that this check is only
+  // effective for processes that are locked to a site, but even with strict
+  // site isolation, currently not all processes are locked (e.g., extensions
+  // or <webview> tags - see ShouldLockToOrigin()).
+  if (check_origin_locks && !CanAccessDataForOrigin(child_id, url))
+    return false;
+
   {
     base::AutoLock lock(lock_);
 
@@ -823,12 +834,7 @@
     // schemes_okay_to_commit_in_any_process_ here, which is stricter than
     // IsWebSafeScheme().
     //
-    // TODO(creis, nick): https://crbug.com/515309: in generalized Site
-    // Isolation and/or --site-per-process, there will be no such thing as a
-    // scheme that is okay to commit in any process. Instead, an URL from a site
-    // that is isolated may only be committed in a process dedicated to that
-    // site, so CanCommitURL will need to rely on explicit, per-process grants.
-    // Note how today, even with extension isolation, the line below does not
+    // TODO(creis, nick): https://crbug.com/515309: The line below does not
     // enforce that http pages cannot commit in an extension process.
     if (base::ContainsKey(schemes_okay_to_commit_in_any_process_, scheme))
       return true;
@@ -843,6 +849,11 @@
   }
 }
 
+bool ChildProcessSecurityPolicyImpl::CanCommitURL(int child_id,
+                                                  const GURL& url) {
+  return CanCommitURL(child_id, url, true /* check_origin_lock */);
+}
+
 bool ChildProcessSecurityPolicyImpl::CanSetAsOriginHeader(int child_id,
                                                           const GURL& url) {
   if (!url.is_valid())
@@ -854,7 +865,12 @@
 
   // If this process can commit |url|, it can use |url| as an origin for
   // outbound requests.
-  if (CanCommitURL(child_id, url))
+  //
+  // TODO(alexmos): This should eventually also check the origin lock, but
+  // currently this is not done due to certain corner cases involving HTML
+  // imports and layout tests that simulate requests from isolated worlds.  See
+  // https://crbug.com/515309.
+  if (CanCommitURL(child_id, url, false /* check_origin_lock */))
     return true;
 
   // Allow schemes which may come from scripts executing in isolated worlds;
diff --git a/content/browser/child_process_security_policy_impl.h b/content/browser/child_process_security_policy_impl.h
index ec66c1e..9dbc29b 100644
--- a/content/browser/child_process_security_policy_impl.h
+++ b/content/browser/child_process_security_policy_impl.h
@@ -171,6 +171,20 @@
   // Revoke read raw cookies permission.
   void RevokeReadRawCookies(int child_id);
 
+  // A version of the public ChildProcessSecurityPolicy::CanCommitURL() which
+  // takes an additional bool |check_origin_lock|, specifying whether to
+  // reject |url| if it does not match the origin lock on process |child_id|.
+  // Passing true for |check_origin_lock| provides stronger enforcement with
+  // strict site isolation; it is only set to false by features (e.g., Origin
+  // header validation) that aren't yet ready for this enforcement. This
+  // function should *not* be used by new features; use the public
+  // ChildProcessSecurityPolicy::CanCommitURL() instead, which internally calls
+  // this with |check_origin_lock| being true.
+  //
+  // TODO(alexmos): Remove |check_origin_lock| and check origin locks
+  // unconditionally once https://crbug.com/515309 is fixed.
+  bool CanCommitURL(int child_id, const GURL& url, bool check_origin_lock);
+
   // Whether the given origin is valid for an origin header. Valid origin
   // headers are commitable URLs.
   bool CanSetAsOriginHeader(int child_id, const GURL& url);
diff --git a/content/browser/devtools/devtools_video_consumer.cc b/content/browser/devtools/devtools_video_consumer.cc
index f06866c..1dd814c 100644
--- a/content/browser/devtools/devtools_video_consumer.cc
+++ b/content/browser/devtools/devtools_video_consumer.cc
@@ -57,7 +57,7 @@
   skbitmap.allocN32Pixels(frame->visible_rect().width(),
                           frame->visible_rect().height());
   cc::SkiaPaintCanvas canvas(skbitmap);
-  renderer.Copy(frame, &canvas, media::Context3D());
+  renderer.Copy(frame, &canvas, media::Context3D(), nullptr);
   return skbitmap;
 }
 
diff --git a/content/browser/renderer_host/render_view_host_impl.cc b/content/browser/renderer_host/render_view_host_impl.cc
index 7d1eebf2..1468588 100644
--- a/content/browser/renderer_host/render_view_host_impl.cc
+++ b/content/browser/renderer_host/render_view_host_impl.cc
@@ -96,8 +96,6 @@
 #include "url/url_constants.h"
 
 #if defined(OS_WIN)
-#include "base/win/win_client_metrics.h"
-#include "ui/display/win/dpi.h"
 #include "ui/display/win/screen_win.h"
 #include "ui/gfx/geometry/dip_util.h"
 #include "ui/gfx/platform_font_win.h"
@@ -122,42 +120,33 @@
 base::LazyInstance<RoutingIDViewMap>::Leaky g_routing_id_view_map =
     LAZY_INSTANCE_INITIALIZER;
 
+#if defined(OS_WIN)
+// Fetches the name and font size of a particular Windows system font.
+void GetFontInfo(gfx::PlatformFontWin::SystemFont system_font,
+                 base::string16* name,
+                 int32_t* size) {
+  const gfx::Font& font = gfx::PlatformFontWin::GetSystemFont(system_font);
+  *name = base::UTF8ToUTF16(font.GetFontName());
+  *size = font.GetFontSize();
+}
+#endif  // OS_WIN
+
 void GetPlatformSpecificPrefs(RendererPreferences* prefs) {
 #if defined(OS_WIN)
-  NONCLIENTMETRICS_XP metrics = {0};
-  base::win::GetNonClientMetrics(&metrics);
-
-  // Render process has the same problem with Windows applying text scaling
-  // before Chrome DPI + accessibility scaling is applied, resulting in double
-  // scaling. These fonts are only used for very specific CSS styles, but to
-  // remain consistent with the rest of chrome we will make sure they aren't
-  // incorrectly scaled:
-  display::win::AdjustFontForAccessibility(&metrics.lfCaptionFont);
-  display::win::AdjustFontForAccessibility(&metrics.lfSmCaptionFont);
-  display::win::AdjustFontForAccessibility(&metrics.lfMenuFont);
-  display::win::AdjustFontForAccessibility(&metrics.lfStatusFont);
-  display::win::AdjustFontForAccessibility(&metrics.lfMessageFont);
-
-  // Store the preferred font faces and sizes:
-  prefs->caption_font_family_name = metrics.lfCaptionFont.lfFaceName;
-  prefs->caption_font_height = gfx::PlatformFontWin::GetFontSize(
-      metrics.lfCaptionFont);
-
-  prefs->small_caption_font_family_name = metrics.lfSmCaptionFont.lfFaceName;
-  prefs->small_caption_font_height = gfx::PlatformFontWin::GetFontSize(
-      metrics.lfSmCaptionFont);
-
-  prefs->menu_font_family_name = metrics.lfMenuFont.lfFaceName;
-  prefs->menu_font_height = gfx::PlatformFontWin::GetFontSize(
-      metrics.lfMenuFont);
-
-  prefs->status_font_family_name = metrics.lfStatusFont.lfFaceName;
-  prefs->status_font_height = gfx::PlatformFontWin::GetFontSize(
-      metrics.lfStatusFont);
-
-  prefs->message_font_family_name = metrics.lfMessageFont.lfFaceName;
-  prefs->message_font_height = gfx::PlatformFontWin::GetFontSize(
-      metrics.lfMessageFont);
+  // Note that what is called "height" in this struct is actually the font size;
+  // font "height" typically includes ascender, descender, and padding and is
+  // often a third or so larger than the given font size.
+  GetFontInfo(gfx::PlatformFontWin::SystemFont::kCaption,
+              &prefs->caption_font_family_name, &prefs->caption_font_height);
+  GetFontInfo(gfx::PlatformFontWin::SystemFont::kSmallCaption,
+              &prefs->small_caption_font_family_name,
+              &prefs->small_caption_font_height);
+  GetFontInfo(gfx::PlatformFontWin::SystemFont::kMenu,
+              &prefs->menu_font_family_name, &prefs->menu_font_height);
+  GetFontInfo(gfx::PlatformFontWin::SystemFont::kMessage,
+              &prefs->message_font_family_name, &prefs->message_font_height);
+  GetFontInfo(gfx::PlatformFontWin::SystemFont::kStatus,
+              &prefs->status_font_family_name, &prefs->status_font_height);
 
   prefs->vertical_scroll_bar_width_in_dips =
       display::win::ScreenWin::GetSystemMetricsInDIP(SM_CXVSCROLL);
diff --git a/content/browser/scheduler/responsiveness/native_event_observer.cc b/content/browser/scheduler/responsiveness/native_event_observer.cc
index 4225e9a..0aa5e6e 100644
--- a/content/browser/scheduler/responsiveness/native_event_observer.cc
+++ b/content/browser/scheduler/responsiveness/native_event_observer.cc
@@ -17,14 +17,9 @@
 
 #include "ui/events/platform/platform_event_source.h"
 
-#if defined(USE_X11)
-#include "ui/events/platform/x11/x11_event_source.h"  // nogncheck
-#elif defined(USE_OZONE)
-#include "ui/events/event.h"
-#endif
-
 #if defined(OS_LINUX)
-#include "ui/events/platform_event.h"
+#include "ui/aura/env.h"
+#include "ui/events/event.h"
 #endif
 
 #if defined(OS_WIN)
@@ -71,41 +66,25 @@
 
 #if defined(OS_LINUX)
 void NativeEventObserver::RegisterObserver() {
-  ui::PlatformEventSource::GetInstance()->AddPlatformEventObserver(this);
+  aura::Env::GetInstance()->AddWindowEventDispatcherObserver(this);
 }
 void NativeEventObserver::DeregisterObserver() {
-  ui::PlatformEventSource::GetInstance()->RemovePlatformEventObserver(this);
+  aura::Env::GetInstance()->RemoveWindowEventDispatcherObserver(this);
 }
 
-void NativeEventObserver::WillProcessEvent(const ui::PlatformEvent& event) {
+void NativeEventObserver::OnWindowEventDispatcherStartedProcessing(
+    aura::WindowEventDispatcher* dispatcher,
+    const ui::Event& event) {
+  EventInfo info{&event, event.time_stamp()};
+  events_being_processed_.push_back(info);
   will_run_event_callback_.Run(&event);
 }
 
-void NativeEventObserver::DidProcessEvent(const ui::PlatformEvent& event) {
-#if defined(USE_OZONE)
-  did_run_event_callback_.Run(&event, event->time_stamp());
-#elif defined(USE_X11)
-  // X11 uses a uint32_t on the wire protocol. Xlib casts this to an unsigned
-  // long by prepending with 0s. We cast back to a uint32_t so that subtraction
-  // works properly when the timestamp overflows back to 0.
-  uint32_t event_server_time_ms =
-      static_cast<uint32_t>(ui::X11EventSource::GetInstance()->GetTimestamp());
-  uint32_t current_server_time_ms = static_cast<uint32_t>(
-      ui::X11EventSource::GetInstance()->GetCurrentServerTime());
-
-  // On X11, event times are in X11 Server time. To convert to base::TimeTicks,
-  // we perform a round-trip to the X11 Server, subtract the two times to get a
-  // TimeDelta, and then subtract that from base::TimeTicks::Now(). Since we're
-  // working with units of time from an external source, we clamp the TimeDelta
-  // to reasonable values.
-  uint32_t delta_ms = current_server_time_ms - event_server_time_ms;
-  base::TimeDelta delta = base::TimeDelta::FromMilliseconds(delta_ms);
-  base::TimeDelta sanitized = ClampDeltaFromExternalSource(delta);
-
-  did_run_event_callback_.Run(&event, base::TimeTicks::Now() - sanitized);
-#else
-#error
-#endif
+void NativeEventObserver::OnWindowEventDispatcherFinishedProcessingEvent(
+    aura::WindowEventDispatcher* dispatcher) {
+  EventInfo& info = events_being_processed_.back();
+  did_run_event_callback_.Run(info.unique_id, info.creation_time);
+  events_being_processed_.pop_back();
 }
 #endif  // defined(OS_LINUX)
 
diff --git a/content/browser/scheduler/responsiveness/native_event_observer.h b/content/browser/scheduler/responsiveness/native_event_observer.h
index 5aaefd3..cf2ef28d 100644
--- a/content/browser/scheduler/responsiveness/native_event_observer.h
+++ b/content/browser/scheduler/responsiveness/native_event_observer.h
@@ -7,6 +7,7 @@
 
 #include "base/callback.h"
 #include "base/macros.h"
+#include "base/time/time.h"
 #include "build/build_config.h"
 #include "content/common/content_export.h"
 
@@ -15,7 +16,7 @@
 #endif
 
 #if defined(OS_LINUX)
-#include "ui/events/platform/platform_event_observer.h"
+#include "ui/aura/window_event_dispatcher_observer.h"
 #endif
 
 #if defined(OS_WIN)
@@ -40,7 +41,7 @@
 #if defined(OS_MACOSX)
     : public NativeEventProcessorObserver
 #elif defined(OS_LINUX)
-    : public ui::PlatformEventObserver
+    : public aura::WindowEventDispatcherObserver
 #elif defined(OS_WIN)
     : public base::MessagePumpForUI::Observer
 #endif
@@ -73,10 +74,12 @@
   void DidRunNativeEvent(const void* opaque_identifier,
                          base::TimeTicks creation_time) override;
 #elif defined(OS_LINUX)
-  // PlatformEventObserver overrides:
-  // Exposed for tests.
-  void WillProcessEvent(const ui::PlatformEvent& event) override;
-  void DidProcessEvent(const ui::PlatformEvent& event) override;
+  // aura::WindowEventDispatcherObserver overrides:
+  void OnWindowEventDispatcherStartedProcessing(
+      aura::WindowEventDispatcher* dispatcher,
+      const ui::Event& event) override;
+  void OnWindowEventDispatcherFinishedProcessingEvent(
+      aura::WindowEventDispatcher* dispatcher) override;
 #elif defined(OS_WIN)
   // base::MessagePumpForUI::Observer overrides:
   void WillDispatchMSG(const MSG& msg) override;
@@ -87,6 +90,14 @@
   void RegisterObserver();
   void DeregisterObserver();
 
+#if defined(OS_LINUX)
+  struct EventInfo {
+    const void* unique_id;
+    base::TimeTicks creation_time;
+  };
+  std::vector<EventInfo> events_being_processed_;
+#endif
+
   WillRunEventCallback will_run_event_callback_;
   DidRunEventCallback did_run_event_callback_;
 
diff --git a/content/browser/security_exploit_browsertest.cc b/content/browser/security_exploit_browsertest.cc
index 0c8e737..df32985 100644
--- a/content/browser/security_exploit_browsertest.cc
+++ b/content/browser/security_exploit_browsertest.cc
@@ -10,6 +10,7 @@
 #include "base/macros.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/post_task.h"
+#include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "content/browser/bad_message.h"
 #include "content/browser/dom_storage/dom_storage_context_wrapper.h"
@@ -24,6 +25,7 @@
 #include "content/common/frame_messages.h"
 #include "content/common/render_message_filter.mojom.h"
 #include "content/common/view_messages.h"
+#include "content/public/browser/blob_handle.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
@@ -47,6 +49,7 @@
 #include "content/test/mock_widget_impl.h"
 #include "content/test/test_content_browser_client.h"
 #include "ipc/ipc_security_test_util.h"
+#include "mojo/public/cpp/bindings/strong_associated_binding.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/url_request/url_request_slow_download_job.h"
@@ -56,6 +59,8 @@
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/mojom/url_loader.mojom.h"
 #include "services/network/test/test_url_loader_client.h"
+#include "storage/browser/blob/blob_registry_impl.h"
+#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/web/web_triggering_event_info.h"
 
 using IPC::IpcSecurityTestUtil;
@@ -146,6 +151,55 @@
   return request;
 }
 
+std::unique_ptr<content::BlobHandle> CreateMemoryBackedBlob(
+    BrowserContext* browser_context,
+    const std::string& contents,
+    const std::string& content_type) {
+  std::unique_ptr<content::BlobHandle> result;
+  base::RunLoop loop;
+  BrowserContext::CreateMemoryBackedBlob(
+      browser_context, contents.c_str(), contents.length(), content_type,
+      base::BindOnce(
+          [](std::unique_ptr<content::BlobHandle>* out_blob,
+             base::OnceClosure done,
+             std::unique_ptr<content::BlobHandle> blob) {
+            *out_blob = std::move(blob);
+            std::move(done).Run();
+          },
+          &result, loop.QuitClosure()));
+  loop.Run();
+  EXPECT_TRUE(result);
+  return result;
+}
+
+// Helper class to interpose on Blob URL registrations, replacing the URL
+// contained in incoming registration requests with the specified URL.
+class BlobURLStoreInterceptor
+    : public blink::mojom::BlobURLStoreInterceptorForTesting {
+ public:
+  explicit BlobURLStoreInterceptor(GURL target_url) : target_url_(target_url) {}
+
+  void Intercept(
+      mojo::StrongAssociatedBindingPtr<blink::mojom::BlobURLStore> binding) {
+    url_store_ = binding->SwapImplForTesting(this);
+  }
+
+  blink::mojom::BlobURLStore* GetForwardingInterface() override {
+    return url_store_;
+  }
+
+  void Register(blink::mojom::BlobPtr blob,
+                const GURL& url,
+                RegisterCallback callback) override {
+    GetForwardingInterface()->Register(std::move(blob), target_url_,
+                                       std::move(callback));
+  }
+
+ private:
+  blink::mojom::BlobURLStore* url_store_;
+  GURL target_url_;
+};
+
 }  // namespace
 
 // The goal of these tests will be to "simulate" exploited renderer processes,
@@ -769,4 +823,163 @@
   ASSERT_EQ(child1_url, child1->current_url());
 }
 
+// Check that when site isolation is enabled, an origin can't create a blob URL
+// for a different origin.  See https://crbug.com/886976.
+IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
+                       CreateBlobURLInDifferentOrigin) {
+  IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
+
+  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  EXPECT_TRUE(NavigateToURL(shell(), main_url));
+  RenderFrameHost* rfh = shell()->web_contents()->GetMainFrame();
+
+  // All these are attacker controlled values.
+  std::string blob_type = "text/html";
+  std::string blob_contents = "<html><body>pwned.</body></html>";
+  std::string blob_path = "5881f76e-10d2-410d-8c61-ef210502acfd";
+
+  // Target a different origin.
+  std::string target_origin = "http://b.com";
+
+  // Set up a blob ID and populate it with attacker-controlled value. This
+  // is just using the blob APIs directly since creating arbitrary blobs is not
+  // what is prohibited; this data is not in any origin.
+  std::unique_ptr<BlobHandle> blob = CreateMemoryBackedBlob(
+      rfh->GetSiteInstance()->GetBrowserContext(), blob_contents, blob_type);
+  std::string blob_id = blob->GetUUID();
+
+  base::HistogramTester histograms;
+  // Try registering a blob URL for b.com.  This IPC should result
+  // in a kill because a.com should not be abllowed to create blob URLs outside
+  // of its own origin.
+  RenderProcessHostWatcher crash_observer(
+      rfh->GetProcess(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
+  PwnMessageHelper::RegisterBlobURL(
+      rfh->GetProcess(), GURL("blob:" + target_origin + "/" + blob_path),
+      blob_id);
+  // If the process is killed, this test passes.
+  crash_observer.Wait();
+  histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content",
+                                139 /* BDH_DISALLOWED_ORIGIN */, 1);
+}
+
+class SecurityExploitBrowserTestMojoBlobURLs
+    : public SecurityExploitBrowserTest {
+ public:
+  SecurityExploitBrowserTestMojoBlobURLs() {
+    scoped_feature_list_.InitAndEnableFeature(blink::features::kMojoBlobURLs);
+  }
+
+  void TearDown() override {
+    storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting(nullptr);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// Check that when site isolation is enabled, an origin can't create a blob URL
+// for a different origin.  Similar to the test above, but checks the
+// mojo-based Blob URL implementation.  See https://crbug.com/886976.
+IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestMojoBlobURLs,
+                       CreateMojoBlobURLInDifferentOrigin) {
+  IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
+
+  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  EXPECT_TRUE(NavigateToURL(shell(), main_url));
+  RenderFrameHost* rfh = shell()->web_contents()->GetMainFrame();
+
+  // Intercept future blob URL registrations and overwrite the blob URL origin
+  // with b.com.
+  std::string target_origin = "http://b.com";
+  std::string blob_path = "5881f76e-10d2-410d-8c61-ef210502acfd";
+  BlobURLStoreInterceptor interceptor(
+      GURL("blob:" + target_origin + "/" + blob_path));
+  auto intercept_hook = base::BindRepeating(&BlobURLStoreInterceptor::Intercept,
+                                            base::Unretained(&interceptor));
+  storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting(&intercept_hook);
+
+  // Register a blob URL from the a.com main frame, which will go through the
+  // interceptor above and be rewritten to register the blob URL with the b.com
+  // origin. This should result in a kill because a.com should not be allowed
+  // to create blob URLs outside of its own origin.
+  base::HistogramTester histograms;
+  RenderProcessHostWatcher crash_observer(
+      rfh->GetProcess(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
+
+  // The renderer should always get killed, but sometimes ExecuteScript returns
+  // true anyway, so just ignore the result.
+  ignore_result(
+      content::ExecuteScript(rfh, "URL.createObjectURL(new Blob(['foo']))"));
+
+  // If the process is killed, this test passes.
+  crash_observer.Wait();
+  histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content", 123,
+                                1);
+}
+
+// Check that with site isolation enabled, an origin can't create a filesystem
+// URL for a different origin.  See https://crbug.com/888001.
+IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
+                       CreateFilesystemURLInDifferentOrigin) {
+  IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
+
+  GURL main_url(embedded_test_server()->GetURL(
+      "a.com", "/cross_site_iframe_factory.html?a(b)"));
+  EXPECT_TRUE(NavigateToURL(shell(), main_url));
+  RenderFrameHost* rfh = shell()->web_contents()->GetMainFrame();
+
+  // Block the renderer on operation that never completes, to shield it from
+  // receiving unexpected browser->renderer IPCs that might CHECK.
+  rfh->ExecuteJavaScriptWithUserGestureForTests(
+      base::ASCIIToUTF16("var r = new XMLHttpRequest();"
+                         "r.open('GET', '/slow?99999', false);"
+                         "r.send(null);"
+                         "while (1);"));
+
+  // Set up a blob ID and populate it with attacker-controlled value. This
+  // is just using the blob APIs directly since creating arbitrary blobs is not
+  // what is prohibited; this data is not in any origin.
+  std::string payload = "<html><body>pwned.</body></html>";
+  std::string payload_type = "text/html";
+  std::unique_ptr<content::BlobHandle> blob = CreateMemoryBackedBlob(
+      rfh->GetSiteInstance()->GetBrowserContext(), payload, payload_type);
+  std::string blob_id = blob->GetUUID();
+
+  // Target a different origin.
+  std::string target_origin = "http://b.com";
+  GURL target_url =
+      GURL("filesystem:" + target_origin + "/temporary/exploit.html");
+
+  // Note: a well-behaved renderer would always call Open first before calling
+  // Create and Write, but it's actually not necessary for the original attack
+  // to succeed, so we omit it. As a result there are some log warnings from the
+  // quota observer.
+
+  PwnMessageHelper::FileSystemCreate(rfh->GetProcess(), 23, target_url, false,
+                                     false, false);
+
+  // Write the blob into the file. If successful, this places an
+  // attacker-controlled value in a resource on the target origin.
+  PwnMessageHelper::FileSystemWrite(rfh->GetProcess(), 24, target_url, blob_id,
+                                    0);
+
+  // Now navigate to |target_url| in a subframe. It should not succeed, and the
+  // subframe should not contain |payload|.
+  TestNavigationObserver observer(shell()->web_contents());
+  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
+                            ->GetFrameTree()
+                            ->root();
+  NavigateFrameToURL(root->child_at(0), target_url);
+  EXPECT_FALSE(observer.last_navigation_succeeded());
+  EXPECT_EQ(net::ERR_FILE_NOT_FOUND, observer.last_net_error_code());
+
+  RenderFrameHost* attacked_rfh = root->child_at(0)->current_frame_host();
+  std::string body =
+      EvalJs(attacked_rfh, "document.body.innerText").ExtractString();
+  EXPECT_TRUE(base::StartsWith(body, "Could not load the requested resource",
+                               base::CompareCase::INSENSITIVE_ASCII))
+      << " body=" << body;
+}
+
 }  // namespace content
diff --git a/content/browser/tracing/background_tracing_manager_impl.cc b/content/browser/tracing/background_tracing_manager_impl.cc
index 430a315..e71ddb1 100644
--- a/content/browser/tracing/background_tracing_manager_impl.cc
+++ b/content/browser/tracing/background_tracing_manager_impl.cc
@@ -654,7 +654,10 @@
           "disabled-by-default-v8.runtime_stats",
           record_mode);
     case BackgroundTracingConfigImpl::CategoryPreset::BENCHMARK_GPU:
-      return TraceConfig("benchmark,toplevel,gpu", record_mode);
+      return TraceConfig(
+          "benchmark,toplevel,gpu,base,mojom,"
+          "disabled-by-default-system_stats,disabled-by-default-cpu_profiler",
+          record_mode);
     case BackgroundTracingConfigImpl::CategoryPreset::BENCHMARK_IPC:
       return TraceConfig("benchmark,toplevel,ipc", record_mode);
     case BackgroundTracingConfigImpl::CategoryPreset::BENCHMARK_STARTUP: {
diff --git a/content/gpu/gpu_main.cc b/content/gpu/gpu_main.cc
index 357b430..8397889 100644
--- a/content/gpu/gpu_main.cc
+++ b/content/gpu/gpu_main.cc
@@ -20,6 +20,7 @@
 #include "base/timer/hi_res_timer_manager.h"
 #include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
+#include "components/tracing/common/tracing_sampler_profiler.h"
 #include "components/viz/service/main/viz_main_impl.h"
 #include "content/common/content_constants_internal.h"
 #include "content/common/content_switches_internal.h"
@@ -336,6 +337,12 @@
 
   gpu_process.set_main_thread(child_thread);
 
+  // Setup tracing sampler profiler as early as possible.
+  auto tracing_sampler_profiler =
+      std::make_unique<tracing::TracingSamplerProfiler>(
+          base::PlatformThread::CurrentId());
+  tracing_sampler_profiler->OnMessageLoopStarted();
+
 #if defined(OS_ANDROID)
   base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
       tracing::GraphicsMemoryDumpProvider::GetInstance(), "AndroidGraphics",
diff --git a/content/renderer/media/media_permission_dispatcher.cc b/content/renderer/media/media_permission_dispatcher.cc
index b22e098728..64203183 100644
--- a/content/renderer/media/media_permission_dispatcher.cc
+++ b/content/renderer/media/media_permission_dispatcher.cc
@@ -43,15 +43,12 @@
 namespace content {
 
 MediaPermissionDispatcher::MediaPermissionDispatcher(
-    const ConnectToServiceCB& connect_to_service_cb,
-    const IsEncryptedMediaEnabledCB& is_encrypted_media_enabled_cb)
-    : connect_to_service_cb_(connect_to_service_cb),
-      is_encrypted_media_enabled_cb_(is_encrypted_media_enabled_cb),
-      task_runner_(base::ThreadTaskRunnerHandle::Get()),
+    RenderFrameImpl* render_frame)
+    : task_runner_(base::ThreadTaskRunnerHandle::Get()),
       next_request_id_(0),
+      render_frame_(render_frame),
       weak_factory_(this) {
-  DCHECK(!connect_to_service_cb_.is_null());
-  DCHECK(!is_encrypted_media_enabled_cb_.is_null());
+  DCHECK(render_frame_);
   weak_ptr_ = weak_factory_.GetWeakPtr();
 }
 
@@ -108,13 +105,14 @@
 
   GetPermissionService()->RequestPermission(
       MediaPermissionTypeToPermissionDescriptor(type),
-      blink::WebUserGestureIndicator::IsProcessingUserGesture(),
+      blink::WebUserGestureIndicator::IsProcessingUserGesture(
+          render_frame_->GetWebFrame()),
       base::BindOnce(&MediaPermissionDispatcher::OnPermissionStatus, weak_ptr_,
                      request_id));
 }
 
 bool MediaPermissionDispatcher::IsEncryptedMediaEnabled() {
-  return is_encrypted_media_enabled_cb_.Run();
+  return render_frame_->GetRendererPreferences().enable_encrypted_media;
 }
 
 uint32_t MediaPermissionDispatcher::RegisterCallback(
@@ -131,7 +129,8 @@
 blink::mojom::PermissionService*
 MediaPermissionDispatcher::GetPermissionService() {
   if (!permission_service_) {
-    connect_to_service_cb_.Run(mojo::MakeRequest(&permission_service_));
+    render_frame_->GetRemoteInterfaces()->GetInterface(
+        mojo::MakeRequest(&permission_service_));
     permission_service_.set_connection_error_handler(base::BindOnce(
         &MediaPermissionDispatcher::OnConnectionError, base::Unretained(this)));
   }
diff --git a/content/renderer/media/media_permission_dispatcher.h b/content/renderer/media/media_permission_dispatcher.h
index 5660721..f1ad133 100644
--- a/content/renderer/media/media_permission_dispatcher.h
+++ b/content/renderer/media/media_permission_dispatcher.h
@@ -14,6 +14,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "content/common/content_export.h"
+#include "content/renderer/render_frame_impl.h"
 #include "media/base/media_permission.h"
 #include "third_party/blink/public/platform/modules/permissions/permission.mojom.h"
 
@@ -26,13 +27,7 @@
 // MediaPermission implementation using content PermissionService.
 class CONTENT_EXPORT MediaPermissionDispatcher : public media::MediaPermission {
  public:
-  using ConnectToServiceCB = base::RepeatingCallback<void(
-      mojo::InterfaceRequest<blink::mojom::PermissionService>)>;
-  using IsEncryptedMediaEnabledCB = base::RepeatingCallback<bool()>;
-
-  MediaPermissionDispatcher(
-      const ConnectToServiceCB& connect_to_service_cb,
-      const IsEncryptedMediaEnabledCB& is_encrypted_media_enabled_cb);
+  explicit MediaPermissionDispatcher(RenderFrameImpl* render_frame);
   ~MediaPermissionDispatcher() override;
 
   // Called when the frame owning this MediaPermissionDispatcher is navigated.
@@ -66,13 +61,16 @@
   // Callback for |permission_service_| connection errors.
   void OnConnectionError();
 
-  ConnectToServiceCB connect_to_service_cb_;
-  IsEncryptedMediaEnabledCB is_encrypted_media_enabled_cb_;
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
   uint32_t next_request_id_;
   RequestMap requests_;
   blink::mojom::PermissionServicePtr permission_service_;
 
+  // The |RenderFrameImpl| that owns this MediaPermissionDispatcher.  It's okay
+  // to hold a raw pointer here because the lifetime of this object is bounded
+  // by the render frame's life (the latter holds a unique pointer to this).
+  RenderFrameImpl* const render_frame_;
+
   // Used to safely post MediaPermission calls for execution on |task_runner_|.
   base::WeakPtr<MediaPermissionDispatcher> weak_ptr_;
 
diff --git a/content/renderer/media/stream/webmediaplayer_ms.cc b/content/renderer/media/stream/webmediaplayer_ms.cc
index d2b6ef2..8d5a6ab 100644
--- a/content/renderer/media/stream/webmediaplayer_ms.cc
+++ b/content/renderer/media/stream/webmediaplayer_ms.cc
@@ -829,6 +829,7 @@
       compositor_->GetCurrentFrameWithoutUpdatingStatistics();
 
   media::Context3D context_3d;
+  gpu::ContextSupport* context_support = nullptr;
   if (frame && frame->HasTextures()) {
     auto* provider =
         RenderThreadImpl::current()->SharedMainThreadContextProvider().get();
@@ -836,11 +837,11 @@
     if (!provider)
       return;
     context_3d = media::Context3D(provider->ContextGL(), provider->GrContext());
-    DCHECK(context_3d.gl);
+    context_support = provider->ContextSupport();
   }
   const gfx::RectF dest_rect(rect.x, rect.y, rect.width, rect.height);
   video_renderer_.Paint(frame, canvas, dest_rect, flags, video_rotation_,
-                        context_3d);
+                        context_3d, context_support);
 }
 
 bool WebMediaPlayerMS::DidGetOpaqueResponseFromServiceWorker() const {
@@ -1016,8 +1017,8 @@
   DCHECK(context_3d.gl);
 
   return video_renderer_.CopyVideoFrameTexturesToGLTexture(
-      context_3d, gl, video_frame.get(), target, texture, internal_format,
-      format, type, level, premultiply_alpha, flip_y);
+      context_3d, provider->ContextSupport(), gl, video_frame.get(), target,
+      texture, internal_format, format, type, level, premultiply_alpha, flip_y);
 }
 
 bool WebMediaPlayerMS::CopyVideoYUVDataToPlatformTexture(
diff --git a/content/renderer/media/stream/webmediaplayer_ms_compositor.cc b/content/renderer/media/stream/webmediaplayer_ms_compositor.cc
index c53c0be2..92b6b60 100644
--- a/content/renderer/media/stream/webmediaplayer_ms_compositor.cc
+++ b/content/renderer/media/stream/webmediaplayer_ms_compositor.cc
@@ -68,7 +68,8 @@
     DCHECK(provider->ContextGL());
     video_renderer->Copy(
         frame.get(), &paint_canvas,
-        media::Context3D(provider->ContextGL(), provider->GrContext()));
+        media::Context3D(provider->ContextGL(), provider->GrContext()),
+        provider->ContextSupport());
 
     SkPixmap pixmap;
     const bool result = bitmap.peekPixels(&pixmap);
diff --git a/content/renderer/media_recorder/video_track_recorder.cc b/content/renderer/media_recorder/video_track_recorder.cc
index c7fb346..ef28d3170 100644
--- a/content/renderer/media_recorder/video_track_recorder.cc
+++ b/content/renderer/media_recorder/video_track_recorder.cc
@@ -313,7 +313,8 @@
     DCHECK(context_provider->ContextGL());
     video_renderer_->Copy(video_frame.get(), canvas_.get(),
                           media::Context3D(context_provider->ContextGL(),
-                                           context_provider->GrContext()));
+                                           context_provider->GrContext()),
+                          context_provider->ContextSupport());
 
     SkPixmap pixmap;
     if (!bitmap_.peekPixels(&pixmap)) {
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 5c2b7a3..917eea9 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -6972,14 +6972,8 @@
 }
 
 media::MediaPermission* RenderFrameImpl::GetMediaPermission() {
-  if (!media_permission_dispatcher_) {
-    media_permission_dispatcher_.reset(new MediaPermissionDispatcher(
-        base::Bind(
-            &RenderFrameImpl::GetInterface<blink::mojom::PermissionService>,
-            base::Unretained(this)),
-        base::Bind(&RenderFrameImpl::IsEncryptedMediaEnabled,
-                   base::Unretained(this))));
-  }
+  if (!media_permission_dispatcher_)
+    media_permission_dispatcher_.reset(new MediaPermissionDispatcher(this));
   return media_permission_dispatcher_.get();
 }
 
@@ -7065,15 +7059,6 @@
   }
 }
 
-template <typename Interface>
-void RenderFrameImpl::GetInterface(mojo::InterfaceRequest<Interface> request) {
-  GetRemoteInterfaces()->GetInterface(std::move(request));
-}
-
-bool RenderFrameImpl::IsEncryptedMediaEnabled() const {
-  return GetRendererPreferences().enable_encrypted_media;
-}
-
 void RenderFrameImpl::OnHostZoomClientRequest(
     mojom::HostZoomAssociatedRequest request) {
   DCHECK(!host_zoom_binding_.is_bound());
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index f6448a8..63418b1 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -1214,10 +1214,6 @@
 
   void RegisterMojoInterfaces();
 
-  // Connect to an interface provided by the service registry.
-  template <typename Interface>
-  void GetInterface(mojo::InterfaceRequest<Interface> request);
-
   void OnHostZoomClientRequest(mojom::HostZoomAssociatedRequest request);
 
   void InitializeBlameContext(RenderFrameImpl* parent_frame);
@@ -1226,11 +1222,6 @@
   void GetInterface(const std::string& interface_name,
                     mojo::ScopedMessagePipeHandle interface_pipe) override;
 
-  // Whether to allow the use of Encrypted Media Extensions (EME), except for
-  // the use of Clear Key key systems, which is always allowed as required by
-  // the spec.
-  bool IsEncryptedMediaEnabled() const;
-
   // Send |callback| our AndroidOverlay routing token when it arrives.  We may
   // call |callback| before returning.
   void RequestOverlayRoutingToken(media::RoutingTokenCallback callback);
diff --git a/docs/sync/uss/client_tag_based_model_type_processor.md b/docs/sync/uss/client_tag_based_model_type_processor.md
index bcadfc9a4..1182fac 100644
--- a/docs/sync/uss/client_tag_based_model_type_processor.md
+++ b/docs/sync/uss/client_tag_based_model_type_processor.md
@@ -1,13 +1,19 @@
 # ClientTagBasedModelTypeProcessor
 
-The [`ClientTagBasedModelTypeProcessor`][SMTP] is a crucial piece of the USS codepath.
-It lives on the model thread and performs the tracking of sync metadata for the
-[`ModelTypeSyncBridge`][MTSB] that owns it by implementing the
+The [`ClientTagBasedModelTypeProcessor`][SMTP] is a crucial piece of the USS
+codepath. It lives on the model thread and performs the tracking of sync
+metadata for the [`ModelTypeSyncBridge`][MTSB] that owns it by implementing the
 [`ModelTypeChangeProcessor`][MTCP] interface, as well as sending commit requests
 to the [`ModelTypeWorker`][MTW] on the sync thread via the [`CommitQueue`][CQ]
 interface and receiving updates from the same worker via the
 [`ModelTypeProcessor`][MTP] interface.
 
+This processor supports types that use a client tag, which is currently
+includes all except bookmarks. This means all changes in flight (either incoming
+remote changes provided via the [`ModelTypeWorker`][MTW], or local changes
+reported by the [`ModelTypeSyncBridge`][MTSB]) must specify a client tag, which
+is considered (after being hashed) the main global identifier of a sync entity.
+
 [SMTP]: https://cs.chromium.org/chromium/src/components/sync/model_impl/client_tag_based_model_type_processor.h
 [MTSB]: https://cs.chromium.org/chromium/src/components/sync/model/model_type_sync_bridge.h
 [MTCP]: https://cs.chromium.org/chromium/src/components/sync/model/model_type_change_processor.h
diff --git a/extensions/browser/api/web_request/web_request_permissions.cc b/extensions/browser/api/web_request/web_request_permissions.cc
index 083d171d..dfff113 100644
--- a/extensions/browser/api/web_request/web_request_permissions.cc
+++ b/extensions/browser/api/web_request/web_request_permissions.cc
@@ -156,7 +156,7 @@
       PermissionsData::PageAccess request_access =
           GetHostAccessForURL(*extension, url, tab_id);
       PermissionsData::PageAccess initiator_access =
-          initiator
+          initiator && !initiator->unique()
               ? GetHostAccessForURL(*extension, initiator->GetURL(), tab_id)
               : PermissionsData::PageAccess::kAllowed;
       access = GetMinimumAccessType(request_access, initiator_access);
diff --git a/extensions/renderer/messaging_util_unittest.cc b/extensions/renderer/messaging_util_unittest.cc
index 80a7ab0..bdc08b0 100644
--- a/extensions/renderer/messaging_util_unittest.cc
+++ b/extensions/renderer/messaging_util_unittest.cc
@@ -86,7 +86,7 @@
   v8::HandleScope handle_scope(isolate());
   v8::Local<v8::Context> context = MainContext();
 
-  scoped_refptr<Extension> extension = ExtensionBuilder("foo").Build();
+  scoped_refptr<const Extension> extension = ExtensionBuilder("foo").Build();
   RegisterExtension(extension);
 
   ScriptContext* script_context = CreateScriptContext(
diff --git a/extensions/renderer/native_extension_bindings_system_test_base.cc b/extensions/renderer/native_extension_bindings_system_test_base.cc
index 84db583..40a4047 100644
--- a/extensions/renderer/native_extension_bindings_system_test_base.cc
+++ b/extensions/renderer/native_extension_bindings_system_test_base.cc
@@ -73,7 +73,7 @@
 
 ScriptContext* NativeExtensionBindingsSystemUnittest::CreateScriptContext(
     v8::Local<v8::Context> v8_context,
-    Extension* extension,
+    const Extension* extension,
     Feature::Context context_type) {
   auto script_context = std::make_unique<ScriptContext>(
       v8_context, nullptr, extension, context_type, extension, context_type);
diff --git a/extensions/renderer/native_extension_bindings_system_test_base.h b/extensions/renderer/native_extension_bindings_system_test_base.h
index 92950b43..e680c8b 100644
--- a/extensions/renderer/native_extension_bindings_system_test_base.h
+++ b/extensions/renderer/native_extension_bindings_system_test_base.h
@@ -118,7 +118,7 @@
   std::unique_ptr<TestJSRunner::Scope> CreateTestJSRunner() override;
 
   ScriptContext* CreateScriptContext(v8::Local<v8::Context> v8_context,
-                                     Extension* extension,
+                                     const Extension* extension,
                                      Feature::Context context_type);
 
   void RegisterExtension(scoped_refptr<const Extension> extension);
diff --git a/extensions/renderer/native_extension_bindings_system_unittest.cc b/extensions/renderer/native_extension_bindings_system_unittest.cc
index 9738044..764f8f731 100644
--- a/extensions/renderer/native_extension_bindings_system_unittest.cc
+++ b/extensions/renderer/native_extension_bindings_system_unittest.cc
@@ -37,7 +37,7 @@
 }  // namespace
 
 TEST_F(NativeExtensionBindingsSystemUnittest, Basic) {
-  scoped_refptr<Extension> extension =
+  scoped_refptr<const Extension> extension =
       ExtensionBuilder("foo")
           .AddPermissions({"idle", "power", "webRequest"})
           .Build();
@@ -146,7 +146,7 @@
 }
 
 TEST_F(NativeExtensionBindingsSystemUnittest, Events) {
-  scoped_refptr<Extension> extension =
+  scoped_refptr<const Extension> extension =
       ExtensionBuilder("foo").AddPermissions({"idle", "power"}).Build();
   RegisterExtension(extension);
 
@@ -192,7 +192,7 @@
 // Tests that referencing the same API multiple times returns the same object;
 // i.e. chrome.foo === chrome.foo.
 TEST_F(NativeExtensionBindingsSystemUnittest, APIObjectsAreEqual) {
-  scoped_refptr<Extension> extension =
+  scoped_refptr<const Extension> extension =
       ExtensionBuilder("foo").AddPermission("idle").Build();
   RegisterExtension(extension);
 
@@ -219,7 +219,7 @@
 // returns undefined if not yet instantiated).
 TEST_F(NativeExtensionBindingsSystemUnittest,
        ReferencingAPIAfterDisposingContext) {
-  scoped_refptr<Extension> extension =
+  scoped_refptr<const Extension> extension =
       ExtensionBuilder("foo").AddPermissions({"idle", "power"}).Build();
 
   RegisterExtension(extension);
@@ -279,7 +279,7 @@
 
   source_map()->RegisterModule("idle", kCustomBinding);
 
-  scoped_refptr<Extension> extension =
+  scoped_refptr<const Extension> extension =
       ExtensionBuilder("foo").AddPermission("idle").Build();
   RegisterExtension(extension);
 
@@ -369,7 +369,7 @@
 
   source_map()->RegisterModule("idle", kCustomBinding);
 
-  scoped_refptr<Extension> extension =
+  scoped_refptr<const Extension> extension =
       ExtensionBuilder("foo").AddPermission("idle").Build();
   RegisterExtension(extension);
 
@@ -403,7 +403,7 @@
 // Note: the notification logic is tested more thoroughly in the APIEventHandler
 // unittests.
 TEST_F(NativeExtensionBindingsSystemUnittest, TestEventRegistration) {
-  scoped_refptr<Extension> extension =
+  scoped_refptr<const Extension> extension =
       ExtensionBuilder("foo").AddPermissions({"idle", "power"}).Build();
 
   RegisterExtension(extension);
@@ -454,7 +454,7 @@
 
 TEST_F(NativeExtensionBindingsSystemUnittest,
        TestPrefixedApiEventsAndAppBinding) {
-  scoped_refptr<Extension> app =
+  scoped_refptr<const Extension> app =
       ExtensionBuilder("foo", ExtensionBuilder::Type::PLATFORM_APP).Build();
   EXPECT_TRUE(app->is_platform_app());
   RegisterExtension(app);
@@ -494,7 +494,7 @@
 
 TEST_F(NativeExtensionBindingsSystemUnittest,
        TestPrefixedApiMethodsAndSystemBinding) {
-  scoped_refptr<Extension> extension =
+  scoped_refptr<const Extension> extension =
       ExtensionBuilder("foo").AddPermission("system.cpu").Build();
   RegisterExtension(extension);
 
@@ -534,7 +534,7 @@
 }
 
 TEST_F(NativeExtensionBindingsSystemUnittest, TestLastError) {
-  scoped_refptr<Extension> extension =
+  scoped_refptr<const Extension> extension =
       ExtensionBuilder("foo").AddPermissions({"idle", "power"}).Build();
   RegisterExtension(extension);
 
@@ -584,7 +584,7 @@
 }
 
 TEST_F(NativeExtensionBindingsSystemUnittest, TestCustomProperties) {
-  scoped_refptr<Extension> extension =
+  scoped_refptr<const Extension> extension =
       ExtensionBuilder("storage extension").AddPermission("storage").Build();
   RegisterExtension(extension);
 
@@ -620,7 +620,7 @@
 // Ensure that different contexts have different API objects.
 TEST_F(NativeExtensionBindingsSystemUnittest,
        CheckDifferentContextsHaveDifferentAPIObjects) {
-  scoped_refptr<Extension> extension =
+  scoped_refptr<const Extension> extension =
       ExtensionBuilder("extension").AddPermission("idle").Build();
   RegisterExtension(extension);
 
@@ -657,7 +657,7 @@
 // context are properly present or absent from the API object.
 TEST_F(NativeExtensionBindingsSystemUnittest,
        CheckRestrictedFeaturesBasedOnContext) {
-  scoped_refptr<Extension> connectable_extension;
+  scoped_refptr<const Extension> connectable_extension;
   {
     DictionaryBuilder manifest;
     manifest.Set("name", "connectable")
@@ -726,7 +726,8 @@
 
 // Tests behavior when script sets window.chrome to be various things.
 TEST_F(NativeExtensionBindingsSystemUnittest, TestUsingOtherChromeObjects) {
-  scoped_refptr<Extension> extension = ExtensionBuilder("extension").Build();
+  scoped_refptr<const Extension> extension =
+      ExtensionBuilder("extension").Build();
   RegisterExtension(extension);
 
   v8::HandleScope handle_scope(isolate());
@@ -794,7 +795,7 @@
 
 // Tests updating a context's bindings after adding or removing permissions.
 TEST_F(NativeExtensionBindingsSystemUnittest, TestUpdatingPermissions) {
-  scoped_refptr<Extension> extension =
+  scoped_refptr<const Extension> extension =
       ExtensionBuilder("extension").AddPermission("idle").Build();
 
   RegisterExtension(extension);
@@ -890,7 +891,8 @@
 }
 
 TEST_F(NativeExtensionBindingsSystemUnittest, UnmanagedEvents) {
-  scoped_refptr<Extension> extension = ExtensionBuilder("extension").Build();
+  scoped_refptr<const Extension> extension =
+      ExtensionBuilder("extension").Build();
 
   RegisterExtension(extension);
 
@@ -922,10 +924,11 @@
 TEST_F(NativeExtensionBindingsSystemUnittest,
        AccessToAliasSourceDoesntGiveAliasAccess) {
   const char kWhitelistedId[] = "pkedcjkdefgpdelpbcmbmeomcjbeemfm";
-  scoped_refptr<Extension> extension = ExtensionBuilder("extension")
-                                           .SetID(kWhitelistedId)
-                                           .AddPermission("networkingPrivate")
-                                           .Build();
+  scoped_refptr<const Extension> extension =
+      ExtensionBuilder("extension")
+          .SetID(kWhitelistedId)
+          .AddPermission("networkingPrivate")
+          .Build();
 
   RegisterExtension(extension);
 
@@ -953,10 +956,11 @@
 TEST_F(NativeExtensionBindingsSystemUnittest,
        AccessToAliasDoesntGiveAliasSourceAccess) {
   const char kWhitelistedId[] = "pkedcjkdefgpdelpbcmbmeomcjbeemfm";
-  scoped_refptr<Extension> extension = ExtensionBuilder("extension")
-                                           .SetID(kWhitelistedId)
-                                           .AddPermission("networking.onc")
-                                           .Build();
+  scoped_refptr<const Extension> extension =
+      ExtensionBuilder("extension")
+          .SetID(kWhitelistedId)
+          .AddPermission("networking.onc")
+          .Build();
   RegisterExtension(extension);
 
   v8::HandleScope handle_scope(isolate());
@@ -982,7 +986,7 @@
 // the objects on the API are different.
 TEST_F(NativeExtensionBindingsSystemUnittest, AliasedAPIsAreDifferentObjects) {
   const char kWhitelistedId[] = "pkedcjkdefgpdelpbcmbmeomcjbeemfm";
-  scoped_refptr<Extension> extension =
+  scoped_refptr<const Extension> extension =
       ExtensionBuilder("extension")
           .SetID(kWhitelistedId)
           .AddPermissions({"networkingPrivate", "networking.onc"})
@@ -1018,7 +1022,8 @@
 
 // Tests that script can overwrite the value of an API.
 TEST_F(NativeExtensionBindingsSystemUnittest, CanOverwriteAPIs) {
-  scoped_refptr<Extension> extension = ExtensionBuilder("extension").Build();
+  scoped_refptr<const Extension> extension =
+      ExtensionBuilder("extension").Build();
 
   RegisterExtension(extension);
 
@@ -1041,7 +1046,8 @@
 
 // Tests that script can delete an API property.
 TEST_F(NativeExtensionBindingsSystemUnittest, CanDeleteAPIs) {
-  scoped_refptr<Extension> extension = ExtensionBuilder("extension").Build();
+  scoped_refptr<const Extension> extension =
+      ExtensionBuilder("extension").Build();
 
   RegisterExtension(extension);
 
@@ -1087,7 +1093,7 @@
          apiBridge.registerCustomHook(() => {});)";
   source_map()->RegisterModule("idle", kCustomBinding);
 
-  scoped_refptr<Extension> extension =
+  scoped_refptr<const Extension> extension =
       ExtensionBuilder("foo").AddPermission("idle").Build();
   RegisterExtension(extension);
 
@@ -1175,7 +1181,7 @@
   APIResponseValidator::TestHandler test_validation_failure_handler(
       base::BindLambdaForTesting(on_validation_failure));
 
-  scoped_refptr<Extension> extension =
+  scoped_refptr<const Extension> extension =
       ExtensionBuilder("foo")
           .AddPermissions({"idle", "power", "webRequest"})
           .Build();
diff --git a/extensions/renderer/native_renderer_messaging_service_unittest.cc b/extensions/renderer/native_renderer_messaging_service_unittest.cc
index 372a7126..5be44649 100644
--- a/extensions/renderer/native_renderer_messaging_service_unittest.cc
+++ b/extensions/renderer/native_renderer_messaging_service_unittest.cc
@@ -34,15 +34,13 @@
     messaging_service_ =
         std::make_unique<NativeRendererMessagingService>(bindings_system());
 
-    scoped_refptr<Extension> mutable_extension =
-        ExtensionBuilder("foo").Build();
-    RegisterExtension(mutable_extension);
-    extension_ = mutable_extension;
+    extension_ = ExtensionBuilder("foo").Build();
+    RegisterExtension(extension_);
 
     v8::HandleScope handle_scope(isolate());
     v8::Local<v8::Context> context = MainContext();
 
-    script_context_ = CreateScriptContext(context, mutable_extension.get(),
+    script_context_ = CreateScriptContext(context, extension_.get(),
                                           Feature::BLESSED_EXTENSION_CONTEXT);
     script_context_->set_url(extension_->url());
     bindings_system()->UpdateBindingsForContext(script_context_);
diff --git a/extensions/renderer/one_time_message_handler_unittest.cc b/extensions/renderer/one_time_message_handler_unittest.cc
index f633c787..dd53dd4 100644
--- a/extensions/renderer/one_time_message_handler_unittest.cc
+++ b/extensions/renderer/one_time_message_handler_unittest.cc
@@ -47,15 +47,13 @@
     message_handler_ =
         std::make_unique<OneTimeMessageHandler>(bindings_system());
 
-    scoped_refptr<Extension> mutable_extension =
-        ExtensionBuilder("foo").Build();
-    RegisterExtension(mutable_extension);
-    extension_ = mutable_extension;
+    extension_ = ExtensionBuilder("foo").Build();
+    RegisterExtension(extension_);
 
     v8::HandleScope handle_scope(isolate());
     v8::Local<v8::Context> context = MainContext();
 
-    script_context_ = CreateScriptContext(context, mutable_extension.get(),
+    script_context_ = CreateScriptContext(context, extension_.get(),
                                           Feature::BLESSED_EXTENSION_CONTEXT);
     script_context_->set_url(extension_->url());
     bindings_system()->UpdateBindingsForContext(script_context_);
diff --git a/extensions/renderer/runtime_hooks_delegate_unittest.cc b/extensions/renderer/runtime_hooks_delegate_unittest.cc
index 25fc7c7e..4ac14d3 100644
--- a/extensions/renderer/runtime_hooks_delegate_unittest.cc
+++ b/extensions/renderer/runtime_hooks_delegate_unittest.cc
@@ -60,14 +60,13 @@
     bindings_system()->api_system()->GetHooksForAPI("runtime")->SetDelegate(
         std::make_unique<RuntimeHooksDelegate>(messaging_service_.get()));
 
-    scoped_refptr<Extension> mutable_extension = BuildExtension();
-    RegisterExtension(mutable_extension);
-    extension_ = mutable_extension;
+    extension_ = BuildExtension();
+    RegisterExtension(extension_);
 
     v8::HandleScope handle_scope(isolate());
     v8::Local<v8::Context> context = MainContext();
 
-    script_context_ = CreateScriptContext(context, mutable_extension.get(),
+    script_context_ = CreateScriptContext(context, extension_.get(),
                                           Feature::BLESSED_EXTENSION_CONTEXT);
     script_context_->set_url(extension_->url());
     bindings_system()->UpdateBindingsForContext(script_context_);
@@ -80,7 +79,7 @@
   }
   bool UseStrictIPCMessageSender() override { return true; }
 
-  virtual scoped_refptr<Extension> BuildExtension() {
+  virtual scoped_refptr<const Extension> BuildExtension() {
     return ExtensionBuilder("foo").Build();
   }
 
@@ -104,7 +103,7 @@
   v8::Local<v8::Context> context = MainContext();
 
   {
-    scoped_refptr<Extension> connectable_extension =
+    scoped_refptr<const Extension> connectable_extension =
         ExtensionBuilder("connectable")
             .SetManifestPath({"externally_connectable", "matches"},
                              ListBuilder().Append("*://example.com/*").Build())
@@ -352,7 +351,7 @@
   RuntimeHooksDelegateNativeMessagingTest() {}
   ~RuntimeHooksDelegateNativeMessagingTest() override {}
 
-  scoped_refptr<Extension> BuildExtension() override {
+  scoped_refptr<const Extension> BuildExtension() override {
     return ExtensionBuilder("foo").AddPermission("nativeMessaging").Build();
   }
 };
diff --git a/extensions/renderer/storage_area_unittest.cc b/extensions/renderer/storage_area_unittest.cc
index 31c0447..c955d242 100644
--- a/extensions/renderer/storage_area_unittest.cc
+++ b/extensions/renderer/storage_area_unittest.cc
@@ -19,7 +19,7 @@
 // Test that trying to use StorageArea.get without a StorageArea `this` fails
 // (with a helpful error message).
 TEST_F(StorageAreaTest, TestUnboundedUse) {
-  scoped_refptr<Extension> extension =
+  scoped_refptr<const Extension> extension =
       ExtensionBuilder("foo").AddPermission("storage").Build();
   RegisterExtension(extension);
 
@@ -48,7 +48,7 @@
 }
 
 TEST_F(StorageAreaTest, TestUseAfterInvalidation) {
-  scoped_refptr<Extension> extension =
+  scoped_refptr<const Extension> extension =
       ExtensionBuilder("foo").AddPermission("storage").Build();
   RegisterExtension(extension);
 
@@ -80,7 +80,7 @@
 }
 
 TEST_F(StorageAreaTest, InvalidInvocationError) {
-  scoped_refptr<Extension> extension =
+  scoped_refptr<const Extension> extension =
       ExtensionBuilder("foo").AddPermission("storage").Build();
   RegisterExtension(extension);
 
diff --git a/extensions/shell/browser/shell_desktop_controller_aura.cc b/extensions/shell/browser/shell_desktop_controller_aura.cc
index 4b9cc24..79842ed 100644
--- a/extensions/shell/browser/shell_desktop_controller_aura.cc
+++ b/extensions/shell/browser/shell_desktop_controller_aura.cc
@@ -233,12 +233,13 @@
 #endif
 
 ui::EventDispatchDetails ShellDesktopControllerAura::DispatchKeyEventPostIME(
-    ui::KeyEvent* key_event) {
+    ui::KeyEvent* key_event,
+    base::OnceCallback<void(bool)> ack_callback) {
   if (key_event->target()) {
     aura::WindowTreeHost* host = static_cast<aura::Window*>(key_event->target())
                                      ->GetRootWindow()
                                      ->GetHost();
-    return host->DispatchKeyEventPostIME(key_event);
+    return host->DispatchKeyEventPostIME(key_event, std::move(ack_callback));
   }
 
   // Send the key event to the focused window.
@@ -246,10 +247,11 @@
       const_cast<aura::Window*>(focus_controller_->GetActiveWindow());
   if (active_window) {
     return active_window->GetRootWindow()->GetHost()->DispatchKeyEventPostIME(
-        key_event);
+        key_event, std::move(ack_callback));
   }
 
-  return GetPrimaryHost()->DispatchKeyEventPostIME(key_event);
+  return GetPrimaryHost()->DispatchKeyEventPostIME(key_event,
+                                                   std::move(ack_callback));
 }
 
 void ShellDesktopControllerAura::OnKeepAliveStateChanged(
diff --git a/extensions/shell/browser/shell_desktop_controller_aura.h b/extensions/shell/browser/shell_desktop_controller_aura.h
index eb5c778..bddf4df 100644
--- a/extensions/shell/browser/shell_desktop_controller_aura.h
+++ b/extensions/shell/browser/shell_desktop_controller_aura.h
@@ -97,7 +97,8 @@
 
   // ui::internal::InputMethodDelegate:
   ui::EventDispatchDetails DispatchKeyEventPostIME(
-      ui::KeyEvent* key_event) override;
+      ui::KeyEvent* key_event,
+      base::OnceCallback<void(bool)> ack_callback) override;
 
   // KeepAliveStateObserver:
   void OnKeepAliveStateChanged(bool is_keeping_alive) override;
diff --git a/gpu/config/gpu_test_config.cc b/gpu/config/gpu_test_config.cc
index b82d701..c8f2941 100644
--- a/gpu/config/gpu_test_config.cc
+++ b/gpu/config/gpu_test_config.cc
@@ -69,6 +69,8 @@
         return GPUTestConfig::kOsMacSierra;
       case 13:
         return GPUTestConfig::kOsMacHighSierra;
+      case 14:
+        return GPUTestConfig::kOsMacMojave;
     }
   }
 #elif defined(OS_ANDROID)
@@ -188,6 +190,7 @@
     case kOsMacElCapitan:
     case kOsMacSierra:
     case kOsMacHighSierra:
+    case kOsMacMojave:
     case kOsLinux:
     case kOsChromeOS:
     case kOsAndroid:
diff --git a/gpu/config/gpu_test_config.h b/gpu/config/gpu_test_config.h
index 2be4242..e4f3b894 100644
--- a/gpu/config/gpu_test_config.h
+++ b/gpu/config/gpu_test_config.h
@@ -34,13 +34,14 @@
     kOsMacElCapitan = 1 << 10,
     kOsMacSierra = 1 << 11,
     kOsMacHighSierra = 1 << 12,
+    kOsMacMojave = 1 << 13,
     kOsMac = kOsMacLeopard | kOsMacSnowLeopard | kOsMacLion |
              kOsMacMountainLion | kOsMacMavericks | kOsMacYosemite |
-             kOsMacElCapitan | kOsMacSierra | kOsMacHighSierra,
-    kOsLinux = 1 << 13,
-    kOsChromeOS = 1 << 14,
-    kOsAndroid = 1 << 15,
-    kOsWin10 = 1 << 16,
+             kOsMacElCapitan | kOsMacSierra | kOsMacHighSierra | kOsMacMojave,
+    kOsLinux = 1 << 14,
+    kOsChromeOS = 1 << 15,
+    kOsAndroid = 1 << 16,
+    kOsWin10 = 1 << 17,
     kOsWin = kOsWinXP | kOsWinVista | kOsWin7 | kOsWin8 | kOsWin10,
   };
 
diff --git a/gpu/config/gpu_test_expectations_parser.cc b/gpu/config/gpu_test_expectations_parser.cc
index c3997e7a..d106862 100644
--- a/gpu/config/gpu_test_expectations_parser.cc
+++ b/gpu/config/gpu_test_expectations_parser.cc
@@ -45,6 +45,7 @@
   kConfigMacElCapitan,
   kConfigMacSierra,
   kConfigMacHighSierra,
+  kConfigMacMojave,
   kConfigMac,
   kConfigLinux,
   kConfigChromeOS,
@@ -101,6 +102,7 @@
     {"elcapitan", GPUTestConfig::kOsMacElCapitan},
     {"sierra", GPUTestConfig::kOsMacSierra},
     {"highsierra", GPUTestConfig::kOsMacHighSierra},
+    {"mojave", GPUTestConfig::kOsMacMojave},
     {"mac", GPUTestConfig::kOsMac},
     {"linux", GPUTestConfig::kOsLinux},
     {"chromeos", GPUTestConfig::kOsChromeOS},
@@ -264,6 +266,7 @@
       case kConfigMacElCapitan:
       case kConfigMacSierra:
       case kConfigMacHighSierra:
+      case kConfigMacMojave:
       case kConfigMac:
       case kConfigLinux:
       case kConfigChromeOS:
@@ -325,6 +328,7 @@
       case kConfigMacElCapitan:
       case kConfigMacSierra:
       case kConfigMacHighSierra:
+      case kConfigMacMojave:
       case kConfigMac:
       case kConfigLinux:
       case kConfigChromeOS:
@@ -449,6 +453,7 @@
     case kConfigMacElCapitan:
     case kConfigMacSierra:
     case kConfigMacHighSierra:
+    case kConfigMacMojave:
     case kConfigMac:
     case kConfigLinux:
     case kConfigChromeOS:
diff --git a/gpu/config/gpu_test_expectations_parser_unittest.cc b/gpu/config/gpu_test_expectations_parser_unittest.cc
index 2d30b4a0..72acc7c9 100644
--- a/gpu/config/gpu_test_expectations_parser_unittest.cc
+++ b/gpu/config/gpu_test_expectations_parser_unittest.cc
@@ -38,6 +38,7 @@
     {{"ELCAPITAN", GPUTestConfig::kOsMacElCapitan}, kOsFamilyMac},
     {{"SIERRA", GPUTestConfig::kOsMacSierra}, kOsFamilyMac},
     {{"HIGHSIERRA", GPUTestConfig::kOsMacHighSierra}, kOsFamilyMac},
+    {{"MOJAVE", GPUTestConfig::kOsMacMojave}, kOsFamilyMac},
     {{"LINUX", GPUTestConfig::kOsLinux}, {"LINUX", GPUTestConfig::kOsLinux}},
     {{"CHROMEOS", GPUTestConfig::kOsChromeOS},
      {"CHROMEOS", GPUTestConfig::kOsChromeOS}},
diff --git a/gpu/ipc/in_process_command_buffer.cc b/gpu/ipc/in_process_command_buffer.cc
index d87fe91..ca0989b 100644
--- a/gpu/ipc/in_process_command_buffer.cc
+++ b/gpu/ipc/in_process_command_buffer.cc
@@ -26,6 +26,7 @@
 #include "base/threading/thread.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/memory_dump_manager.h"
+#include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
 #include "gpu/command_buffer/client/gpu_control_client.h"
 #include "gpu/command_buffer/client/gpu_memory_buffer_manager.h"
@@ -339,6 +340,7 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
   DCHECK(!share_group ||
          task_executor_.get() == share_group->task_executor_.get());
+  TRACE_EVENT0("gpu", "InProcessCommandBuffer::Initialize")
 
   gpu_memory_buffer_manager_ = gpu_memory_buffer_manager;
   gpu_channel_manager_delegate_ = gpu_channel_manager_delegate;
@@ -384,6 +386,8 @@
 gpu::ContextResult InProcessCommandBuffer::InitializeOnGpuThread(
     const InitializeOnGpuThreadParams& params) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_);
+  TRACE_EVENT0("gpu", "InProcessCommandBuffer::InitializeOnGpuThread")
+
   // TODO(crbug.com/832243): This could use the TransferBufferManager owned by
   // |context_group_| instead.
   transfer_buffer_manager_ = std::make_unique<TransferBufferManager>(nullptr);
@@ -674,6 +678,8 @@
 
 void InProcessCommandBuffer::Destroy() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
+  TRACE_EVENT0("gpu", "InProcessCommandBuffer::Destroy");
+
   client_thread_weak_ptr_factory_.InvalidateWeakPtrs();
   gpu_control_client_ = nullptr;
   base::WaitableEvent completion(
@@ -689,6 +695,8 @@
 
 bool InProcessCommandBuffer::DestroyOnGpuThread() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_);
+  TRACE_EVENT0("gpu", "InProcessCommandBuffer::DestroyOnGpuThread");
+
   // TODO(sunnyps): Should this use ScopedCrashKey instead?
   crash_keys::gpu_gl_context_is_virtual.Set(use_virtualized_gl_context_ ? "1"
                                                                         : "0");
@@ -804,6 +812,9 @@
 
 void InProcessCommandBuffer::FlushOnGpuThread(int32_t put_offset) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_);
+  TRACE_EVENT1("gpu", "InProcessCommandBuffer::FlushOnGpuThread", "put_offset",
+               put_offset);
+
   ScopedEvent handle_flush(&flush_event_);
 
   if (!MakeCurrent())
@@ -867,6 +878,9 @@
   if (last_put_offset_ == put_offset)
     return;
 
+  TRACE_EVENT1("gpu", "InProcessCommandBuffer::Flush", "put_offset",
+               put_offset);
+
   last_put_offset_ = put_offset;
   flushed_fence_sync_release_ = next_fence_sync_release_ - 1;
 
@@ -885,6 +899,9 @@
 
 CommandBuffer::State InProcessCommandBuffer::WaitForTokenInRange(int32_t start,
                                                                  int32_t end) {
+  TRACE_EVENT2("gpu", "InProcessCommandBuffer::WaitForTokenInRange", "start",
+               start, "end", end);
+
   State last_state = GetLastState();
   while (!InRange(start, end, last_state.token) &&
          last_state.error == error::kNoError) {
@@ -898,6 +915,9 @@
     uint32_t set_get_buffer_count,
     int32_t start,
     int32_t end) {
+  TRACE_EVENT2("gpu", "InProcessCommandBuffer::WaitForGetOffsetInRange",
+               "start", start, "end", end);
+
   State last_state = GetLastState();
   while (((set_get_buffer_count != last_state.set_get_buffer_count) ||
           !InRange(start, end, last_state.get_offset)) &&
@@ -1123,6 +1143,8 @@
 bool InProcessCommandBuffer::OnWaitSyncToken(const SyncToken& sync_token) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_);
   DCHECK(!waiting_for_sync_point_);
+  TRACE_EVENT0("gpu", "InProcessCommandBuffer::OnWaitSyncToken");
+
   SyncPointManager* sync_point_manager = task_executor_->sync_point_manager();
   DCHECK(sync_point_manager);
 
diff --git a/gpu/vulkan/vulkan_swap_chain.cc b/gpu/vulkan/vulkan_swap_chain.cc
index b03e2af..cd21927a 100644
--- a/gpu/vulkan/vulkan_swap_chain.cc
+++ b/gpu/vulkan/vulkan_swap_chain.cc
@@ -76,6 +76,7 @@
   DCHECK_NE(layout, old_layout);
   VkImageMemoryBarrier image_memory_barrier = {
       .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+      .pNext = nullptr,
       .srcAccessMask = GetAccessMask(old_layout),
       .dstAccessMask = GetAccessMask(layout),
       .oldLayout = old_layout,
diff --git a/infra/config/global/cr-buildbucket.cfg b/infra/config/global/cr-buildbucket.cfg
index 5562bf5..5fbc0bd 100644
--- a/infra/config/global/cr-buildbucket.cfg
+++ b/infra/config/global/cr-buildbucket.cfg
@@ -2106,6 +2106,7 @@
     }
     builders {
       name: "WebKit Win Builder"
+      # This may be obsolete (WebKit Win10 is triggered by Win Builder).
       dimensions: "os:Windows-10"
       mixins: "webkit-ci"
     }
diff --git a/infra/config/global/luci-milo.cfg b/infra/config/global/luci-milo.cfg
index 21f40ed..ec8e043 100644
--- a/infra/config/global/luci-milo.cfg
+++ b/infra/config/global/luci-milo.cfg
@@ -701,6 +701,7 @@
   }
   builders {
     name: "buildbot/chromium.webkit/WebKit Win10"
+    name: "buildbucket/luci.chromium.ci/WebKit Win10"
     category: "chromium.webkit|win rel|tester"
     short_name: "10"
   }
diff --git a/infra/config/global/luci-scheduler.cfg b/infra/config/global/luci-scheduler.cfg
index de6d2e6..20e29458 100644
--- a/infra/config/global/luci-scheduler.cfg
+++ b/infra/config/global/luci-scheduler.cfg
@@ -3621,6 +3621,7 @@
 
 job {
   id: "WebKit Win Builder"
+  # This may be obsolete (WebKit Win10 is triggered by Win Builder).
   acl_sets: "default"
   buildbucket: {
     server: "cr-buildbucket.appspot.com"
@@ -3631,7 +3632,7 @@
 
 job {
   id: "WebKit Win10"
-  # Triggered by "WebKit Win Builder"
+  # Triggered by "Win Builder"
   acl_sets: "triggered-by-parent-builders"
   buildbucket: {
     server: "cr-buildbucket.appspot.com"
diff --git a/ios/build/bots/chromium.fyi/ios12-beta-simulator.json b/ios/build/bots/chromium.fyi/ios12-beta-simulator.json
index 4ffcff38..722eac8 100644
--- a/ios/build/bots/chromium.fyi/ios12-beta-simulator.json
+++ b/ios/build/bots/chromium.fyi/ios12-beta-simulator.json
@@ -18,8 +18,8 @@
     {
       "include": "common_tests.json",
       "device type": "iPhone X",
-      "os": "12.0",
-      "xcode build version": "10a254a",
+      "os": "12.1",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -29,8 +29,8 @@
     {
       "include": "eg_cq_tests.json",
       "device type": "iPhone X",
-      "os": "12.0",
-      "xcode build version": "10a254a",
+      "os": "12.1",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -40,8 +40,8 @@
     {
       "include": "eg_cq_tests.json",
       "device type": "iPhone 6 Plus",
-      "os": "12.0",
-      "xcode build version": "10a254a",
+      "os": "12.1",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -51,8 +51,8 @@
     {
       "include": "eg_cq_tests.json",
       "device type": "iPhone 5s",
-      "os": "12.0",
-      "xcode build version": "10a254a",
+      "os": "12.1",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -62,8 +62,8 @@
     {
       "include": "eg_cq_tests.json",
       "device type": "iPad Pro",
-      "os": "12.0",
-      "xcode build version": "10a254a",
+      "os": "12.1",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -73,8 +73,8 @@
     {
       "include": "eg_tests.json",
       "device type": "iPhone X",
-      "os": "12.0",
-      "xcode build version": "10a254a",
+      "os": "12.1",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -84,8 +84,8 @@
     {
       "include": "eg_tests.json",
       "device type": "iPhone 6 Plus",
-      "os": "12.0",
-      "xcode build version": "10a254a",
+      "os": "12.1",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -95,8 +95,8 @@
     {
       "include": "eg_tests.json",
       "device type": "iPad Air",
-      "os": "12.0",
-      "xcode build version": "10a254a",
+      "os": "12.1",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -106,8 +106,8 @@
     {
       "include": "eg_tests.json",
       "device type": "iPhone 5s",
-      "os": "12.0",
-      "xcode build version": "10a254a",
+      "os": "12.1",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -117,8 +117,8 @@
     {
       "include": "screen_size_dependent_tests.json",
       "device type": "iPhone 6s Plus",
-      "os": "12.0",
-      "xcode build version": "10a254a",
+      "os": "12.1",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -128,8 +128,8 @@
     {
       "include": "screen_size_dependent_tests.json",
       "device type": "iPhone X",
-      "os": "12.0",
-      "xcode build version": "10a254a",
+      "os": "12.1",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -139,8 +139,8 @@
     {
       "include": "screen_size_dependent_tests.json",
       "device type": "iPhone 5s",
-      "os": "12.0",
-      "xcode build version": "10a254a",
+      "os": "12.1",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -150,8 +150,8 @@
     {
       "include": "screen_size_dependent_tests.json",
       "device type": "iPad Air 2",
-      "os": "12.0",
-      "xcode build version": "10a254a",
+      "os": "12.1",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
diff --git a/ios/build/bots/chromium.fyi/ios12-sdk-device.json b/ios/build/bots/chromium.fyi/ios12-sdk-device.json
index 1d2ce7f..1192c5ff 100644
--- a/ios/build/bots/chromium.fyi/ios12-sdk-device.json
+++ b/ios/build/bots/chromium.fyi/ios12-sdk-device.json
@@ -4,7 +4,7 @@
     "Build is performed with gn+ninja.",
     "If modified, please change chromium.fyi/ios-device-goma-canary-clobber.json too."
   ],
-  "xcode build version": "10a254a",
+  "xcode build version": "10o23u",
   "gn_args": [
     "additional_target_cpus=[ \"arm64\" ]",
     "goma_dir=\"$(goma_dir)\"",
diff --git a/ios/build/bots/chromium.fyi/ios12-sdk-simulator.json b/ios/build/bots/chromium.fyi/ios12-sdk-simulator.json
index 63d5266..0344be2 100644
--- a/ios/build/bots/chromium.fyi/ios12-sdk-simulator.json
+++ b/ios/build/bots/chromium.fyi/ios12-sdk-simulator.json
@@ -1,10 +1,10 @@
 {
   "comments": [
-    "Runs tests on 64-bit iOS 10.3 and 11.4 and 12.0 tests",
+    "Runs tests on 64-bit iOS 10.3 and 11.4 and 12.1 tests",
     "on iPad, iPhone, @3x, and @2x on main and CQ ios12-beta-simulator.",
     "Note: Xcode 10 requires Mac OS 10.13.4 or higher, hence 'host os'."
   ],
-  "xcode build version": "10a254a",
+  "xcode build version": "10o23u",
   "gn_args": [
     "additional_target_cpus=[\"x86\"]",
     "goma_dir=\"$(goma_dir)\"",
@@ -23,7 +23,8 @@
     {
       "include": "eg_cq_tests.json",
       "device type": "iPhone X",
-      "os": "12.0",
+      "os": "12.1",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -33,7 +34,8 @@
     {
       "include": "eg_tests.json",
       "device type": "iPhone X",
-      "os": "12.0",
+      "os": "12.1",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -43,7 +45,8 @@
     {
       "include": "eg_tests.json",
       "device type": "iPad Air 2",
-      "os": "12.0",
+      "os": "12.1",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -53,7 +56,8 @@
     {
       "include": "eg_tests.json",
       "device type": "iPhone 7",
-      "os": "12.0",
+      "os": "12.1",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -64,6 +68,7 @@
       "include": "eg_tests.json",
       "device type": "iPhone 6s Plus",
       "os": "10.3",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -74,6 +79,7 @@
       "include": "eg_tests.json",
       "device type": "iPhone 7",
       "os": "11.4",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -84,6 +90,7 @@
       "include": "eg_tests.json",
       "device type": "iPad Air 2",
       "os": "11.4",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -104,6 +111,7 @@
       "include": "eg_tests.json",
       "device type": "iPhone X",
       "os": "11.4",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -114,6 +122,7 @@
       "include": "eg_cq_tests.json",
       "device type": "iPad Air 2",
       "os": "10.3",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -124,6 +133,7 @@
       "include": "eg_cq_tests.json",
       "device type": "iPhone X",
       "os": "11.4",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -133,7 +143,8 @@
     {
       "include": "screen_size_dependent_tests.json",
       "device type": "iPhone 6s Plus",
-      "os": "12.0",
+      "os": "12.1",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -143,7 +154,8 @@
     {
       "include": "screen_size_dependent_tests.json",
       "device type": "iPhone 6s",
-      "os": "12.0",
+      "os": "12.1",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -153,7 +165,8 @@
     {
       "include": "screen_size_dependent_tests.json",
       "device type": "iPad Air 2",
-      "os": "12.0",
+      "os": "12.1",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -164,6 +177,7 @@
       "include": "screen_size_dependent_tests.json",
       "device type": "iPad Air 2",
       "os": "11.4",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -174,6 +188,7 @@
       "include": "screen_size_dependent_tests.json",
       "device type": "iPad Air 2",
       "os": "10.3",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -184,6 +199,7 @@
       "include": "common_tests.json",
       "device type": "iPhone 6s",
       "os": "11.4",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -193,7 +209,8 @@
     {
       "include": "eg_cq_tests.json",
       "device type": "iPhone 6s",
-      "os": "12.0",
+      "os": "12.1",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -204,6 +221,7 @@
       "include": "eg_cq_tests.json",
       "device type": "iPhone 6s",
       "os": "11.4",
+      "xcode build version": "10o23u",
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
diff --git a/ios/build/bots/chromium.fyi/ios12-sdk-xcode-clang.json b/ios/build/bots/chromium.fyi/ios12-sdk-xcode-clang.json
index 1435e15..61acdf96 100644
--- a/ios/build/bots/chromium.fyi/ios12-sdk-xcode-clang.json
+++ b/ios/build/bots/chromium.fyi/ios12-sdk-xcode-clang.json
@@ -4,7 +4,7 @@
     "the final GM, but disabled for the other betas to reduce swirl for the",
     "goma team. Build is performed with gn+ninja."
   ],
-  "xcode build version": "10l213o",
+  "xcode build version": "10o23u",
   "use xcode build version": true,
   "gn_args": [
     "additional_target_cpus=[\"x86\"]",
diff --git a/ios/chrome/browser/ui/tab_grid/grid/grid_consumer.h b/ios/chrome/browser/ui/tab_grid/grid/grid_consumer.h
index 7aa0136..f5ce985 100644
--- a/ios/chrome/browser/ui/tab_grid/grid/grid_consumer.h
+++ b/ios/chrome/browser/ui/tab_grid/grid/grid_consumer.h
@@ -40,6 +40,7 @@
 
 // Tells the consumer to replace the item with ID |itemID| with |item|.
 // It's an error if |item|'s ID duplicates any other item's ID besides |itemID|.
+// The consumer should ignore this call if |itemID| has not yet been inserted.
 - (void)replaceItemID:(NSString*)itemID withItem:(GridItem*)item;
 
 // Tells the consumer to move the item with id |itemID| to |toIndex|. Note that
diff --git a/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.mm b/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.mm
index 8fe1155..38fcb45 100644
--- a/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.mm
+++ b/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.mm
@@ -425,6 +425,8 @@
 }
 
 - (void)replaceItemID:(NSString*)itemID withItem:(GridItem*)item {
+  if ([self indexOfItemWithID:itemID] == NSNotFound)
+    return;
   // Consistency check: |item|'s ID is either |itemID| or not in |items|.
   DCHECK([item.identifier isEqualToString:itemID] ||
          [self indexOfItemWithID:item.identifier] == NSNotFound);
diff --git a/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller_unittest.mm b/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller_unittest.mm
index c1ef752..fa98e8f 100644
--- a/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller_unittest.mm
@@ -132,7 +132,7 @@
   EXPECT_EQ(2U, delegate_.itemCount);
 }
 
-// Tests that an item is replaced.
+// Tests that an item is replaced with a new identifier.
 TEST_F(GridViewControllerTest, ReplaceItem) {
   // Previously: The grid had 2 items and selectedIndex was 0. The delegate had
   // an itemCount of 2.
@@ -142,6 +142,29 @@
   EXPECT_EQ(2U, delegate_.itemCount);
 }
 
+// Tests that an item is replaced with same identifier.
+TEST_F(GridViewControllerTest, ReplaceItemSameIdentifier) {
+  // Previously: The grid had 2 items and selectedIndex was 0. The delegate had
+  // an itemCount of 2.
+  GridItem* item = [[GridItem alloc] initWithIdentifier:@"A"];
+  item.title = @"NEW-ITEM-TITLE";
+  [view_controller_ replaceItemID:@"A" withItem:item];
+  EXPECT_NSEQ(@"A", view_controller_.items[0].identifier);
+  EXPECT_NSEQ(@"NEW-ITEM-TITLE", view_controller_.items[0].title);
+  EXPECT_EQ(2U, delegate_.itemCount);
+}
+
+// Tests that an item is not replaced if it doesn't exist.
+TEST_F(GridViewControllerTest, ReplaceItemNotFound) {
+  // Previously: The grid had 2 items and selectedIndex was 0. The delegate had
+  // an itemCount of 2.
+  GridItem* item = [[GridItem alloc] initWithIdentifier:@"NOT-FOUND"];
+  [view_controller_ replaceItemID:@"NOT-FOUND" withItem:item];
+  EXPECT_NSNE(@"NOT-FOUND", view_controller_.items[0].identifier);
+  EXPECT_NSNE(@"NOT-FOUND", view_controller_.items[1].identifier);
+  EXPECT_EQ(2U, delegate_.itemCount);
+}
+
 // Tests that the selected item is moved.
 TEST_F(GridViewControllerTest, MoveSelectedItem) {
   // Previously: The grid had 2 items and selectedIndex was 0. The delegate had
diff --git a/ios/chrome/browser/ui/tab_grid/tab_grid_mediator.mm b/ios/chrome/browser/ui/tab_grid/tab_grid_mediator.mm
index dc58a4cf..c423ef8 100644
--- a/ios/chrome/browser/ui/tab_grid/tab_grid_mediator.mm
+++ b/ios/chrome/browser/ui/tab_grid/tab_grid_mediator.mm
@@ -250,6 +250,9 @@
   [self.appearanceCache removeObjectForKey:identifier];
   web::WebState* webState = GetWebStateWithId(self.webStateList, identifier);
   if (webState) {
+    // It is possible to observe an updated snapshot for a WebState before
+    // observing that the WebState has been added to the WebStateList. It is the
+    // consumer's responsibility to ignore any updates before inserts.
     [self.consumer replaceItemID:identifier withItem:CreateItem(webState)];
   }
 }
diff --git a/ios/web/web_state/web_frame_web_state_observer_inttest.mm b/ios/web/web_state/web_frame_web_state_observer_inttest.mm
index 947bec5..d8a2d63 100644
--- a/ios/web/web_state/web_frame_web_state_observer_inttest.mm
+++ b/ios/web/web_state/web_frame_web_state_observer_inttest.mm
@@ -5,6 +5,9 @@
 #import "ios/web/public/test/web_test_with_web_state.h"
 
 #include "base/ios/ios_util.h"
+#include "base/test/scoped_feature_list.h"
+#include "ios/web/public/features.h"
+#import "ios/web/public/web_state/web_frame.h"
 #include "ios/web/public/web_state/web_frame_util.h"
 #import "ios/web/public/web_state/web_state.h"
 #import "ios/web/public/web_state/web_state_observer.h"
@@ -15,7 +18,17 @@
 #error "This file requires ARC support."
 #endif
 
+using testing::Truly;
+
 namespace {
+
+// WebFrameWebStateObserverInttest is parameterized on this enum to test both
+// LegacyNavigationManager and WKBasedNavigationManager.
+enum class NavigationManagerChoice {
+  LEGACY,
+  WK_BASED,
+};
+
 // Mocks WebStateObserver navigation callbacks.
 class WebStateObserverMock : public web::WebStateObserver {
  public:
@@ -31,6 +44,11 @@
   DISALLOW_COPY_AND_ASSIGN(WebStateObserverMock);
 };
 
+// A predicate that returns true if |frame| is a main frame.
+bool IsMainFrame(web::WebFrame* frame) {
+  return frame->IsMainFrame();
+}
+
 // Verifies that the web frame passed to the observer is the main frame.
 ACTION_P(VerifyMainWebFrame, web_state) {
   EXPECT_EQ(web_state, arg0);
@@ -51,10 +69,28 @@
 
 namespace web {
 
-typedef WebTestWithWebState WebFrameWebStateObserverInttest;
+class WebFrameWebStateObserverInttest
+    : public WebTestWithWebState,
+      public ::testing::WithParamInterface<NavigationManagerChoice> {
+ protected:
+  void SetUp() override {
+    if (GetParam() == NavigationManagerChoice::LEGACY) {
+      scoped_feature_list_.InitAndDisableFeature(
+          web::features::kSlimNavigationManager);
+    } else {
+      scoped_feature_list_.InitAndEnableFeature(
+          web::features::kSlimNavigationManager);
+    }
+
+    WebTestWithWebState::SetUp();
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
 
 // Web frame events should be registered on HTTP navigation.
-TEST_F(WebFrameWebStateObserverInttest, SingleWebFrameHTTP) {
+TEST_P(WebFrameWebStateObserverInttest, SingleWebFrameHTTP) {
   testing::StrictMock<WebStateObserverMock> observer;
   web_state()->AddObserver(&observer);
   EXPECT_CALL(observer, WebFrameDidBecomeAvailable(web_state(), testing::_))
@@ -69,7 +105,7 @@
 }
 
 // Web frame events should be registered on HTTPS navigation.
-TEST_F(WebFrameWebStateObserverInttest, SingleWebFrameHTTPS) {
+TEST_P(WebFrameWebStateObserverInttest, SingleWebFrameHTTPS) {
   testing::StrictMock<WebStateObserverMock> observer;
   web_state()->AddObserver(&observer);
   EXPECT_CALL(observer, WebFrameDidBecomeAvailable(web_state(), testing::_))
@@ -84,24 +120,42 @@
 }
 
 // Web frame event should be registered on HTTPS navigation with iframe.
-TEST_F(WebFrameWebStateObserverInttest, TwoWebFrameHTTPS) {
+TEST_P(WebFrameWebStateObserverInttest, TwoWebFrameHTTPS) {
   testing::StrictMock<WebStateObserverMock> observer;
   web_state()->AddObserver(&observer);
-  EXPECT_CALL(observer, WebFrameDidBecomeAvailable(web_state(), testing::_))
-      .Times(2)
-      .WillOnce(VerifyChildWebFrame(web_state()))
+
+  // The order in which the main and child frames become available is not
+  // guaranteed due to the async nature of messaging. The following expectations
+  // use separate matchers to identify main and child frames so that they can
+  // be matched in any order.
+  EXPECT_CALL(observer,
+              WebFrameDidBecomeAvailable(web_state(), Truly(IsMainFrame)))
       .WillOnce(VerifyMainWebFrame(web_state()));
+  EXPECT_CALL(observer,
+              WebFrameDidBecomeAvailable(web_state(), Not(Truly(IsMainFrame))))
+      .WillOnce(VerifyChildWebFrame(web_state()));
   LoadHtml(@"<p><iframe/></p>", GURL("https://testurl1"));
-  EXPECT_CALL(observer, WebFrameDidBecomeAvailable(web_state(), testing::_))
-      .Times(2)
-      .WillOnce(VerifyChildWebFrame(web_state()))
+
+  EXPECT_CALL(observer,
+              WebFrameDidBecomeAvailable(web_state(), Truly(IsMainFrame)))
       .WillOnce(VerifyMainWebFrame(web_state()));
-  EXPECT_CALL(observer, WebFrameWillBecomeUnavailable(web_state(), testing::_))
-      .Times(2)
-      .WillOnce(VerifyMainWebFrame(web_state()))
+  EXPECT_CALL(observer,
+              WebFrameDidBecomeAvailable(web_state(), Not(Truly(IsMainFrame))))
+      .WillOnce(VerifyChildWebFrame(web_state()));
+  EXPECT_CALL(observer,
+              WebFrameWillBecomeUnavailable(web_state(), Truly(IsMainFrame)))
+      .WillOnce(VerifyMainWebFrame(web_state()));
+  EXPECT_CALL(observer, WebFrameWillBecomeUnavailable(web_state(),
+                                                      Not(Truly(IsMainFrame))))
       .WillOnce(VerifyChildWebFrame(web_state()));
   LoadHtml(@"<p><iframe/></p>", GURL("https://testurl2"));
+
   web_state()->RemoveObserver(&observer);
 }
 
+INSTANTIATE_TEST_CASE_P(ProgrammaticWebFrameWebStateObserverInttest,
+                        WebFrameWebStateObserverInttest,
+                        ::testing::Values(NavigationManagerChoice::LEGACY,
+                                          NavigationManagerChoice::WK_BASED));
+
 }  // namespace web
diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc
index 253f3fd..0496c2d 100644
--- a/media/blink/webmediaplayer_impl.cc
+++ b/media/blink/webmediaplayer_impl.cc
@@ -1152,7 +1152,8 @@
   }
   video_renderer_.Paint(
       video_frame, canvas, gfx::RectF(gfx_rect), flags,
-      pipeline_metadata_.video_decoder_config.video_rotation(), context_3d);
+      pipeline_metadata_.video_decoder_config.video_rotation(), context_3d,
+      context_provider_->ContextSupport());
 }
 
 bool WebMediaPlayerImpl::DidGetOpaqueResponseFromServiceWorker() const {
@@ -1243,8 +1244,9 @@
                            context_provider_->GrContext());
   }
   return video_renderer_.CopyVideoFrameTexturesToGLTexture(
-      context_3d, gl, video_frame.get(), target, texture, internal_format,
-      format, type, level, premultiply_alpha, flip_y);
+      context_3d, context_provider_->ContextSupport(), gl, video_frame.get(),
+      target, texture, internal_format, format, type, level, premultiply_alpha,
+      flip_y);
 }
 
 // static
diff --git a/media/cast/sender/external_video_encoder.cc b/media/cast/sender/external_video_encoder.cc
index b8df553..5e11390 100644
--- a/media/cast/sender/external_video_encoder.cc
+++ b/media/cast/sender/external_video_encoder.cc
@@ -168,51 +168,57 @@
         requested_bit_rate_));
 
     if (!encoder_active_) {
-      ExitEncodingWithErrors();
+      AbortLatestEncodeAttemptDueToErrors();
       return;
     }
 
-    scoped_refptr<media::VideoFrame> frame = video_frame;
-    if (video_frame->coded_size() != frame_coded_size_) {
-      DCHECK_GE(frame_coded_size_.width(), video_frame->visible_rect().width());
-      DCHECK_GE(frame_coded_size_.height(),
-                video_frame->visible_rect().height());
-
-      if (free_input_buffer_index_.empty()) {
-        if (!allocate_input_buffer_in_progress_ &&
-            input_buffers_.size() < max_allowed_input_buffers_) {
-          allocate_input_buffer_in_progress_ = true;
-          create_video_encode_memory_cb_.Run(
-              media::VideoFrame::AllocationSize(media::PIXEL_FORMAT_I420,
-                                                frame_coded_size_),
-              base::Bind(&VEAClientImpl::OnCreateInputSharedMemory, this));
-        }
-        ExitEncodingWithErrors();
-        return;
+    // If there are no free input buffers in the pool, request allocation of
+    // another one. Since that's an asynchronous process, simply abort encoding
+    // this frame and hope that the input buffer is ready for the next frame(s).
+    if (free_input_buffer_index_.empty()) {
+      if (!allocate_input_buffer_in_progress_ &&
+          input_buffers_.size() < max_allowed_input_buffers_) {
+        allocate_input_buffer_in_progress_ = true;
+        create_video_encode_memory_cb_.Run(
+            media::VideoFrame::AllocationSize(media::PIXEL_FORMAT_I420,
+                                              frame_coded_size_),
+            base::Bind(&VEAClientImpl::OnCreateInputSharedMemory, this));
       }
-
-      int index = free_input_buffer_index_.back();
-      base::SharedMemory* input_buffer = input_buffers_[index].get();
-      frame = VideoFrame::WrapExternalSharedMemory(
-          video_frame->format(), frame_coded_size_, video_frame->visible_rect(),
-          video_frame->visible_rect().size(),
-          static_cast<uint8_t*>(input_buffer->memory()),
-          input_buffer->mapped_size(), input_buffer->handle(), 0,
-          video_frame->timestamp());
-      if (!frame || !media::I420CopyWithPadding(*video_frame, frame.get())) {
-        LOG(DFATAL) << "Error: ExternalVideoEncoder: copy failed.";
-        ExitEncodingWithErrors();
-        return;
-      }
-
-      frame->AddDestructionObserver(media::BindToCurrentLoop(base::Bind(
-          &ExternalVideoEncoder::VEAClientImpl::ReturnInputBufferToPool, this,
-          index)));
-      free_input_buffer_index_.pop_back();
+      AbortLatestEncodeAttemptDueToErrors();
+      return;
     }
 
+    // Copy the |video_frame| into the input buffer provided by the VEA
+    // implementation, and with the exact row stride required. Note that, even
+    // if |video_frame|'s stride matches VEA's requirement, |video_frame|'s
+    // memory backing (heap, base::ReadOnlySharedMemoryRegion, etc.) could be
+    // something VEA can't handle (as of this writing, it uses the legacy shmem
+    // API). http://crbug.com/888153
+    //
+    // TODO(crbug.com/888829): Revisit whether we can remove this memcpy, if VEA
+    // can accept other "memory backing" methods.
+    const int index = free_input_buffer_index_.back();
+    base::SharedMemory* const input_buffer = input_buffers_[index].get();
+    scoped_refptr<media::VideoFrame> frame =
+        VideoFrame::WrapExternalSharedMemory(
+            video_frame->format(), frame_coded_size_,
+            video_frame->visible_rect(), video_frame->visible_rect().size(),
+            static_cast<uint8_t*>(input_buffer->memory()),
+            input_buffer->mapped_size(), input_buffer->handle(), 0,
+            video_frame->timestamp());
+    if (!frame || !media::I420CopyWithPadding(*video_frame, frame.get())) {
+      LOG(DFATAL) << "Error: ExternalVideoEncoder: copy failed.";
+      AbortLatestEncodeAttemptDueToErrors();
+      return;
+    }
+
+    frame->AddDestructionObserver(media::BindToCurrentLoop(base::Bind(
+        &ExternalVideoEncoder::VEAClientImpl::ReturnInputBufferToPool, this,
+        index)));
+    free_input_buffer_index_.pop_back();
+
     // BitstreamBufferReady will be called once the encoder is done.
-    video_encode_accelerator_->Encode(frame, key_frame_requested);
+    video_encode_accelerator_->Encode(std::move(frame), key_frame_requested);
   }
 
  protected:
@@ -414,7 +420,7 @@
     DCHECK(task_runner_->RunsTasksInCurrentSequence());
 
     while (!in_progress_frame_encodes_.empty())
-      ExitEncodingWithErrors();
+      AbortLatestEncodeAttemptDueToErrors();
 
     // According to the media::VideoEncodeAccelerator interface, Destroy()
     // should be called instead of invoking its private destructor.
@@ -464,9 +470,9 @@
     allocate_input_buffer_in_progress_ = false;
   }
 
-  // This is called when copy errors occur in encoding process when there is
-  // need to copy the VideoFrames to match the required coded size for encoder.
-  void ExitEncodingWithErrors() {
+  // This is called when an error occurs while preparing a VideoFrame for
+  // encode, or to abort a frame encode when shutting down.
+  void AbortLatestEncodeAttemptDueToErrors() {
     DCHECK(task_runner_->RunsTasksInCurrentSequence());
 
     std::unique_ptr<SenderEncodedFrame> no_result(nullptr);
@@ -633,7 +639,11 @@
 
 ExternalVideoEncoder::~ExternalVideoEncoder() {
   DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
+  DestroyClientSoon();
+}
 
+void ExternalVideoEncoder::DestroyClientSoon() {
+  DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
   // Ensure |client_| is destroyed from the encoder task runner by dropping the
   // reference to it within an encoder task.
   if (client_) {
@@ -719,10 +729,39 @@
       return;
   }
 
+  // Create a callback that wraps the StatusChangeCallback. It monitors when a
+  // fatal error occurs and schedules destruction of the VEAClientImpl.
+  StatusChangeCallback wrapped_status_change_cb = base::Bind(
+      [](base::WeakPtr<ExternalVideoEncoder> self,
+         const StatusChangeCallback& status_change_cb,
+         OperationalStatus status) {
+        if (self.get()) {
+          switch (status) {
+            case STATUS_UNINITIALIZED:
+            case STATUS_INITIALIZED:
+            case STATUS_CODEC_REINIT_PENDING:
+              break;
+
+            case STATUS_INVALID_CONFIGURATION:
+            case STATUS_UNSUPPORTED_CODEC:
+            case STATUS_CODEC_INIT_FAILED:
+            case STATUS_CODEC_RUNTIME_ERROR:
+              // Something bad happened. Destroy the client to: 1) fail-out any
+              // currently in-progress frame encodes; and 2) prevent future
+              // EncodeVideoFrame() calls from queuing frames indefinitely.
+              self->DestroyClientSoon();
+              break;
+          }
+        }
+        status_change_cb.Run(status);
+      },
+      weak_factory_.GetWeakPtr(), status_change_cb);
+
   DCHECK(!client_);
   client_ = new VEAClientImpl(cast_environment_, encoder_task_runner,
                               std::move(vea), video_config.max_frame_rate,
-                              status_change_cb, create_video_encode_memory_cb_);
+                              std::move(wrapped_status_change_cb),
+                              create_video_encode_memory_cb_);
   client_->task_runner()->PostTask(FROM_HERE,
                                    base::Bind(&VEAClientImpl::Initialize,
                                               client_,
diff --git a/media/cast/sender/external_video_encoder.h b/media/cast/sender/external_video_encoder.h
index fb926f5e..44002c2 100644
--- a/media/cast/sender/external_video_encoder.h
+++ b/media/cast/sender/external_video_encoder.h
@@ -52,6 +52,10 @@
  private:
   class VEAClientImpl;
 
+  // Called from the destructor (or earlier on error), to schedule destruction
+  // of |client_| via the encoder task runner.
+  void DestroyClientSoon();
+
   // Method invoked by the CreateVideoEncodeAcceleratorCallback to construct a
   // VEAClientImpl to own and interface with a new |vea|.  Upon return,
   // |client_| holds a reference to the new VEAClientImpl.
diff --git a/media/cast/sender/video_encoder_unittest.cc b/media/cast/sender/video_encoder_unittest.cc
index 8fea266..a65ee05 100644
--- a/media/cast/sender/video_encoder_unittest.cc
+++ b/media/cast/sender/video_encoder_unittest.cc
@@ -11,8 +11,11 @@
 #include <vector>
 
 #include "base/bind.h"
+#include "base/callback_helpers.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "media/base/fake_single_thread_task_runner.h"
 #include "media/base/video_frame.h"
@@ -36,13 +39,14 @@
  protected:
   VideoEncoderTest()
       : task_runner_(new FakeSingleThreadTaskRunner(&testing_clock_)),
+        thread_task_runner_override_reverter_(
+            base::ThreadTaskRunnerHandle::OverrideForTesting(task_runner_)),
         cast_environment_(new CastEnvironment(&testing_clock_,
                                               task_runner_,
                                               task_runner_,
                                               task_runner_)),
         video_config_(GetDefaultVideoSenderConfig()),
-        operational_status_(STATUS_UNINITIALIZED),
-        count_frames_delivered_(0) {
+        operational_status_(STATUS_UNINITIALIZED) {
     testing_clock_.Advance(base::TimeTicks::Now() - base::TimeTicks());
     first_frame_time_ = testing_clock_.NowTicks();
   }
@@ -134,16 +138,6 @@
     testing_clock_.Advance(frame_duration);
   }
 
-  int count_frames_delivered() const {
-    return count_frames_delivered_;
-  }
-
-  void WaitForAllFramesToBeDelivered(int total_expected) {
-    video_encoder_->EmitFrames();
-    while (count_frames_delivered_ < total_expected)
-      RunTasksAndAdvanceClock();
-  }
-
   // Creates a new VideoFrame of the given |size|, filled with a test pattern.
   // When available, it attempts to use the VideoFrameFactory provided by the
   // encoder.
@@ -164,22 +158,6 @@
     return frame;
   }
 
-  // Requests encoding the |video_frame| and has the resulting frame delivered
-  // via a callback that checks for expected results.  Returns false if the
-  // encoder rejected the request.
-  bool EncodeAndCheckDelivery(
-      const scoped_refptr<media::VideoFrame>& video_frame,
-      FrameId frame_id,
-      FrameId reference_frame_id) {
-    return video_encoder_->EncodeVideoFrame(
-        video_frame, Now(),
-        base::Bind(&VideoEncoderTest::DeliverEncodedVideoFrame,
-                   base::Unretained(this), frame_id, reference_frame_id,
-                   RtpTimeTicks::FromTimeDelta(video_frame->timestamp(),
-                                               kVideoFrequency),
-                   Now()));
-  }
-
   // If the implementation of |video_encoder_| is ExternalVideoEncoder, check
   // that the VEA factory has responded (by running the callbacks) a specific
   // number of times.  Otherwise, check that the VEA factory is inactive.
@@ -212,59 +190,9 @@
       video_frame_factory_ = video_encoder_->CreateVideoFrameFactory();
   }
 
-  // Checks that |encoded_frame| matches expected values.  This is the method
-  // bound in the callback returned from EncodeAndCheckDelivery().
-  void DeliverEncodedVideoFrame(
-      FrameId expected_frame_id,
-      FrameId expected_last_referenced_frame_id,
-      RtpTimeTicks expected_rtp_timestamp,
-      const base::TimeTicks& expected_reference_time,
-      std::unique_ptr<SenderEncodedFrame> encoded_frame) {
-    EXPECT_TRUE(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
-
-    EXPECT_EQ(expected_frame_id, encoded_frame->frame_id);
-    EXPECT_EQ(expected_rtp_timestamp, encoded_frame->rtp_timestamp);
-    EXPECT_EQ(expected_reference_time, encoded_frame->reference_time);
-
-    // The platform encoders are "black boxes" and may choose to vend key frames
-    // and/or empty data at any time.  The software encoders, however, should
-    // strictly adhere to expected behavior.
-    if (is_testing_platform_encoder()) {
-      const bool expected_key_frame =
-          expected_frame_id == expected_last_referenced_frame_id;
-      const bool have_key_frame =
-          encoded_frame->dependency == EncodedFrame::KEY;
-      EXPECT_EQ(have_key_frame,
-                encoded_frame->frame_id == encoded_frame->referenced_frame_id);
-      LOG_IF(WARNING, expected_key_frame != have_key_frame)
-          << "Platform encoder chose to emit a "
-          << (have_key_frame ? "key" : "delta")
-          << " frame instead of the expected kind @ frame_id="
-          << encoded_frame->frame_id;
-      LOG_IF(WARNING, encoded_frame->data.empty())
-          << "Platform encoder returned an empty frame @ frame_id="
-          << encoded_frame->frame_id;
-    } else {
-      if (expected_frame_id != expected_last_referenced_frame_id) {
-        EXPECT_EQ(EncodedFrame::DEPENDENT, encoded_frame->dependency);
-      } else if (video_config_.video_codec_params
-                     .max_number_of_video_buffers_used == 1) {
-        EXPECT_EQ(EncodedFrame::KEY, encoded_frame->dependency);
-      }
-      EXPECT_EQ(expected_last_referenced_frame_id,
-                encoded_frame->referenced_frame_id);
-      EXPECT_FALSE(encoded_frame->data.empty());
-      ASSERT_TRUE(std::isfinite(encoded_frame->encoder_utilization));
-      EXPECT_LE(0.0, encoded_frame->encoder_utilization);
-      ASSERT_TRUE(std::isfinite(encoded_frame->lossy_utilization));
-      EXPECT_LE(0.0, encoded_frame->lossy_utilization);
-    }
-
-    ++count_frames_delivered_;
-  }
-
   base::SimpleTestTickClock testing_clock_;
   const scoped_refptr<FakeSingleThreadTaskRunner> task_runner_;
+  base::ScopedClosureRunner thread_task_runner_override_reverter_;
   const scoped_refptr<CastEnvironment> cast_environment_;
   FrameSenderConfig video_config_;
   std::unique_ptr<FakeVideoEncodeAcceleratorFactory> vea_factory_;
@@ -273,62 +201,13 @@
   std::unique_ptr<VideoEncoder> video_encoder_;
   std::unique_ptr<VideoFrameFactory> video_frame_factory_;
 
-  int count_frames_delivered_;
-
   DISALLOW_COPY_AND_ASSIGN(VideoEncoderTest);
 };
 
-// A simple test to encode three frames of video, expecting to see one key frame
-// followed by two delta frames.
-// Fails consistently on official builds: crbug.com/612496
-#ifdef OFFICIAL_BUILD
-#define MAYBE_GeneratesKeyFrameThenOnlyDeltaFrames \
-  DISABLED_GeneratesKeyFrameThenOnlyDeltaFrames
-#else
-#define MAYBE_GeneratesKeyFrameThenOnlyDeltaFrames \
-  GeneratesKeyFrameThenOnlyDeltaFrames
-#endif
-TEST_P(VideoEncoderTest, MAYBE_GeneratesKeyFrameThenOnlyDeltaFrames) {
-  CreateEncoder();
-  SetVEAFactoryAutoRespond(true);
-
-  EXPECT_EQ(0, count_frames_delivered());
-  ExpectVEAResponsesForExternalVideoEncoder(0, 0);
-
-  FrameId frame_id = FrameId::first();
-  FrameId reference_frame_id = FrameId::first();
-  const gfx::Size frame_size(1280, 720);
-
-  // Some encoders drop one or more frames initially while the encoder
-  // initializes. Then, for all encoders, expect one key frame is delivered.
-  bool accepted_first_frame = false;
-  do {
-    accepted_first_frame = EncodeAndCheckDelivery(
-        CreateTestVideoFrame(frame_size), frame_id, reference_frame_id);
-    if (!encoder_has_resize_delay())
-      EXPECT_TRUE(accepted_first_frame);
-    RunTasksAndAdvanceClock();
-  } while (!accepted_first_frame);
-  ExpectVEAResponsesForExternalVideoEncoder(1, 3);
-
-  // Expect the remaining frames are encoded as delta frames.
-  for (++frame_id; frame_id < FrameId::first() + 3;
-       ++frame_id, ++reference_frame_id) {
-    EXPECT_TRUE(EncodeAndCheckDelivery(CreateTestVideoFrame(frame_size),
-                                       frame_id,
-                                       reference_frame_id));
-    RunTasksAndAdvanceClock();
-  }
-
-  WaitForAllFramesToBeDelivered(3);
-  ExpectVEAResponsesForExternalVideoEncoder(1, 3);
-}
-
-// Tests that the encoder continues to output EncodedFrames as the frame size
-// changes.  See media/cast/receiver/video_decoder_unittest.cc for a complete
-// encode/decode cycle of varied frame sizes that actually checks the frame
-// content.
-// Fails consistently on official builds: crbug.com/612496
+// Tests that the encoder outputs encoded frames, and also responds to frame
+// size changes. See media/cast/receiver/video_decoder_unittest.cc for a
+// complete encode/decode cycle of varied frame sizes that actually checks the
+// frame content. Fails consistently on official builds: crbug.com/612496
 #ifdef OFFICIAL_BUILD
 #define MAYBE_EncodesVariedFrameSizes DISABLED_EncodesVariedFrameSizes
 #else
@@ -338,7 +217,6 @@
   CreateEncoder();
   SetVEAFactoryAutoRespond(true);
 
-  EXPECT_EQ(0, count_frames_delivered());
   ExpectVEAResponsesForExternalVideoEncoder(0, 0);
 
   std::vector<gfx::Size> frame_sizes;
@@ -354,45 +232,98 @@
   frame_sizes.push_back(gfx::Size(34, 20));    // Grow the other dimension.
   frame_sizes.push_back(gfx::Size(192, 108));  // Grow both dimensions again.
 
-  FrameId frame_id = FrameId::first();
+  int count_frames_accepted = 0;
+  using EncodedFrames = std::vector<std::unique_ptr<SenderEncodedFrame>>;
+  EncodedFrames encoded_frames;
+  base::WeakPtrFactory<EncodedFrames> encoded_frames_weak_factory(
+      &encoded_frames);
 
-  // Encode one frame at each size. For encoders with a resize delay, except no
-  // frames to be delivered since each frame size change will sprun
-  // re-initialization of the underlying encoder. Otherwise expect all key
-  // frames to come out.
+  // Encode several frames at each size. For encoders with a resize delay,
+  // expect the first one or more frames are dropped while the encoder
+  // re-inits. For all encoders, expect one key frame followed by all delta
+  // frames.
   for (const auto& frame_size : frame_sizes) {
-    EXPECT_EQ(!encoder_has_resize_delay(),
-              EncodeAndCheckDelivery(CreateTestVideoFrame(frame_size), frame_id,
-                                     frame_id));
-    RunTasksAndAdvanceClock();
-    if (!encoder_has_resize_delay())
-      ++frame_id;
-  }
-
-  // Encode three frames at each size. For encoders with a resize delay, expect
-  // the first one or more frames are dropped while the encoder re-inits. Then,
-  // for all encoders, expect one key frame followed by all delta frames.
-  for (const auto& frame_size : frame_sizes) {
-    bool accepted_first_frame = false;
-    do {
-      accepted_first_frame = EncodeAndCheckDelivery(
-          CreateTestVideoFrame(frame_size), frame_id, frame_id);
-      if (!encoder_has_resize_delay())
-        EXPECT_TRUE(accepted_first_frame);
-      RunTasksAndAdvanceClock();
-    } while (!accepted_first_frame);
-    ++frame_id;
-    for (int i = 1; i < 3; ++i, ++frame_id) {
-      EXPECT_TRUE(EncodeAndCheckDelivery(CreateTestVideoFrame(frame_size),
-                                         frame_id,
-                                         frame_id - 1));
+    // Encode frames until there are four consecutive frames successfully
+    // encoded.
+    while (encoded_frames.size() <= 4 ||
+           !(encoded_frames[encoded_frames.size() - 1] &&
+             encoded_frames[encoded_frames.size() - 2] &&
+             encoded_frames[encoded_frames.size() - 3] &&
+             encoded_frames[encoded_frames.size() - 4])) {
+      auto video_frame = CreateTestVideoFrame(frame_size);
+      const base::TimeTicks reference_time = Now();
+      const bool accepted_request = video_encoder()->EncodeVideoFrame(
+          std::move(video_frame), reference_time,
+          base::BindRepeating(
+              [](base::WeakPtr<EncodedFrames> encoded_frames,
+                 RtpTimeTicks expected_rtp_timestamp,
+                 base::TimeTicks expected_reference_time,
+                 std::unique_ptr<SenderEncodedFrame> encoded_frame) {
+                if (!encoded_frames) {
+                  return;
+                }
+                if (encoded_frame) {
+                  EXPECT_EQ(expected_rtp_timestamp,
+                            encoded_frame->rtp_timestamp);
+                  EXPECT_EQ(expected_reference_time,
+                            encoded_frame->reference_time);
+                }
+                encoded_frames->emplace_back(std::move(encoded_frame));
+              },
+              encoded_frames_weak_factory.GetWeakPtr(),
+              RtpTimeTicks::FromTimeDelta(video_frame->timestamp(),
+                                          kVideoFrequency),
+              reference_time));
+      if (accepted_request) {
+        ++count_frames_accepted;
+      }
+      if (!encoder_has_resize_delay()) {
+        EXPECT_TRUE(accepted_request);
+      }
       RunTasksAndAdvanceClock();
     }
   }
 
-  WaitForAllFramesToBeDelivered(3 * frame_sizes.size());
-  ExpectVEAResponsesForExternalVideoEncoder(
-      2 * frame_sizes.size(), 6 * frame_sizes.size());
+  // Flush the encoder and wait until all queued frames have been delivered.
+  // Then, shut it all down.
+  video_encoder()->EmitFrames();
+  while (encoded_frames.size() < static_cast<size_t>(count_frames_accepted))
+    RunTasksAndAdvanceClock();
+  DestroyEncoder();
+  RunTasksAndAdvanceClock();
+  encoded_frames_weak_factory.InvalidateWeakPtrs();
+
+  // Walk through the encoded frames and check that they have reasonable frame
+  // IDs, dependency relationships, etc. provided.
+  FrameId last_key_frame_id;
+  for (const std::unique_ptr<SenderEncodedFrame>& encoded_frame :
+       encoded_frames) {
+    if (!encoded_frame) {
+      continue;
+    }
+
+    if (encoded_frame->dependency == EncodedFrame::KEY) {
+      EXPECT_EQ(encoded_frame->frame_id, encoded_frame->referenced_frame_id);
+      last_key_frame_id = encoded_frame->frame_id;
+    } else {
+      EXPECT_EQ(EncodedFrame::DEPENDENT, encoded_frame->dependency);
+      EXPECT_GT(encoded_frame->frame_id, encoded_frame->referenced_frame_id);
+      // There must always be a KEY frame before any DEPENDENT ones.
+      ASSERT_FALSE(last_key_frame_id.is_null());
+      EXPECT_GE(encoded_frame->referenced_frame_id, last_key_frame_id);
+    }
+
+    EXPECT_FALSE(encoded_frame->data.empty());
+
+    // The utilization metrics are computed for all but the Mac Video Toolbox
+    // encoder.
+    if (is_testing_software_vp8_encoder()) {
+      ASSERT_TRUE(std::isfinite(encoded_frame->encoder_utilization));
+      EXPECT_LE(0.0, encoded_frame->encoder_utilization);
+      ASSERT_TRUE(std::isfinite(encoded_frame->lossy_utilization));
+      EXPECT_LE(0.0, encoded_frame->lossy_utilization);
+    }
+  }
 }
 
 // Verify that everything goes well even if ExternalVideoEncoder is destroyed
@@ -410,8 +341,10 @@
   CreateEncoder();
 
   // Send a frame to spawn creation of the ExternalVideoEncoder instance.
-  EncodeAndCheckDelivery(CreateTestVideoFrame(gfx::Size(128, 72)),
-                         FrameId::first(), FrameId::first());
+  video_encoder()->EncodeVideoFrame(
+      CreateTestVideoFrame(gfx::Size(128, 72)), Now(),
+      base::BindRepeating(
+          [](std::unique_ptr<SenderEncodedFrame> encoded_frame) {}));
 
   // Destroy the encoder, and confirm the VEA Factory did not respond yet.
   DestroyEncoder();
diff --git a/media/cast/sender/video_sender_unittest.cc b/media/cast/sender/video_sender_unittest.cc
index 32e7b5f..1eac449e 100644
--- a/media/cast/sender/video_sender_unittest.cc
+++ b/media/cast/sender/video_sender_unittest.cc
@@ -277,7 +277,7 @@
   // VideoSender created an encoder for 1280x720 frames, in order to provide the
   // INITIALIZED status.
   EXPECT_EQ(1, vea_factory_.vea_response_count());
-  EXPECT_EQ(3, vea_factory_.shm_response_count());
+  EXPECT_LT(0, vea_factory_.shm_response_count());
 
   scoped_refptr<media::VideoFrame> video_frame = GetNewVideoFrame();
 
@@ -288,13 +288,13 @@
     // VideoSender re-created the encoder for the 320x240 frames we're
     // providing.
     EXPECT_EQ(1, vea_factory_.vea_response_count());
-    EXPECT_EQ(3, vea_factory_.shm_response_count());
+    EXPECT_LT(0, vea_factory_.shm_response_count());
   }
 
   video_sender_.reset(NULL);
   task_runner_->RunTasks();
   EXPECT_EQ(1, vea_factory_.vea_response_count());
-  EXPECT_EQ(3, vea_factory_.shm_response_count());
+  EXPECT_LT(0, vea_factory_.shm_response_count());
 }
 
 TEST_F(VideoSenderTest, ExternalEncoderInitFails) {
diff --git a/media/renderers/paint_canvas_video_renderer.cc b/media/renderers/paint_canvas_video_renderer.cc
index 8d12b5f..afbb91f 100644
--- a/media/renderers/paint_canvas_video_renderer.cc
+++ b/media/renderers/paint_canvas_video_renderer.cc
@@ -14,6 +14,7 @@
 #include "cc/paint/paint_image.h"
 #include "cc/paint/paint_image_builder.h"
 #include "gpu/GLES2/gl2extchromium.h"
+#include "gpu/command_buffer/client/context_support.h"
 #include "gpu/command_buffer/client/gles2_interface.h"
 #include "gpu/command_buffer/common/capabilities.h"
 #include "gpu/command_buffer/common/mailbox_holder.h"
@@ -275,6 +276,35 @@
   }
 }
 
+void OnQueryDone(scoped_refptr<VideoFrame> video_frame,
+                 gpu::gles2::GLES2Interface* gl,
+                 unsigned query_id) {
+  gl->DeleteQueriesEXT(1, &query_id);
+  // |video_frame| is dropped here.
+}
+
+void SynchronizeVideoFrameRead(scoped_refptr<VideoFrame> video_frame,
+                               gpu::gles2::GLES2Interface* gl,
+                               gpu::ContextSupport* context_support) {
+  DCHECK(gl);
+  DCHECK(context_support);
+
+  SyncTokenClientImpl client(gl);
+  video_frame->UpdateReleaseSyncToken(&client);
+
+  if (video_frame->metadata()->IsTrue(
+          VideoFrameMetadata::READ_LOCK_FENCES_ENABLED)) {
+    // |video_frame| must be kept alive during read operations.
+    unsigned query_id = 0;
+    gl->GenQueriesEXT(1, &query_id);
+    DCHECK(query_id);
+    gl->BeginQueryEXT(GL_COMMANDS_COMPLETED_CHROMIUM, query_id);
+    gl->EndQueryEXT(GL_COMMANDS_COMPLETED_CHROMIUM);
+    context_support->SignalQuery(
+        query_id, base::BindOnce(&OnQueryDone, video_frame, gl, query_id));
+  }
+}
+
 }  // anonymous namespace
 
 // Generates an RGB image from a VideoFrame. Convert YUV to RGB plain on GPU.
@@ -416,7 +446,8 @@
     const gfx::RectF& dest_rect,
     cc::PaintFlags& flags,
     VideoRotation video_rotation,
-    const Context3D& context_3d) {
+    const Context3D& context_3d,
+    gpu::ContextSupport* context_support) {
   DCHECK(thread_checker_.CalledOnValidThread());
   if (flags.getAlpha() == 0) {
     return;
@@ -509,22 +540,23 @@
   canvas->flush();
 
   if (video_frame->HasTextures()) {
-    DCHECK(gl);
-    SyncTokenClientImpl client(gl);
-    video_frame->UpdateReleaseSyncToken(&client);
+    // Synchronize |video_frame| with the read operations in UpdateLastImage(),
+    // which are triggered by canvas->flush().
+    SynchronizeVideoFrameRead(video_frame, gl, context_support);
   }
 }
 
 void PaintCanvasVideoRenderer::Copy(
     const scoped_refptr<VideoFrame>& video_frame,
     cc::PaintCanvas* canvas,
-    const Context3D& context_3d) {
+    const Context3D& context_3d,
+    gpu::ContextSupport* context_support) {
   cc::PaintFlags flags;
   flags.setBlendMode(SkBlendMode::kSrc);
   flags.setFilterQuality(kLow_SkFilterQuality);
   Paint(video_frame, canvas,
         gfx::RectF(gfx::SizeF(video_frame->visible_rect().size())), flags,
-        media::VIDEO_ROTATION_0, context_3d);
+        media::VIDEO_ROTATION_0, context_3d, context_support);
 }
 
 namespace {
@@ -901,14 +933,14 @@
                                     target, texture, internal_format, format,
                                     type, level, premultiply_alpha, flip_y);
   gl->DeleteTextures(1, &source_texture);
-  gl->Flush();
-
-  SyncTokenClientImpl client(gl);
-  video_frame->UpdateReleaseSyncToken(&client);
+  gl->ShallowFlushCHROMIUM();
+  // The caller must call SynchronizeVideoFrameRead() after this operation, but
+  // we can't do that because we don't have the ContextSupport.
 }
 
 bool PaintCanvasVideoRenderer::CopyVideoFrameTexturesToGLTexture(
     const Context3D& context_3d,
+    gpu::ContextSupport* context_support,
     gpu::gles2::GLES2Interface* destination_gl,
     const scoped_refptr<VideoFrame>& video_frame,
     unsigned int target,
@@ -936,7 +968,11 @@
     if (!backend_texture.getGLTextureInfo(&texture_info))
       return false;
 
+    // Synchronize |video_frame| with the read operations in UpdateLastImage(),
+    // which are triggered by getBackendTexture().
     gpu::gles2::GLES2Interface* canvas_gl = context_3d.gl;
+    SynchronizeVideoFrameRead(video_frame, canvas_gl, context_support);
+
     gpu::MailboxHolder mailbox_holder;
     mailbox_holder.texture_target = texture_info.fTarget;
     canvas_gl->ProduceTextureDirectCHROMIUM(texture_info.fID,
@@ -964,13 +1000,30 @@
     gpu::SyncToken dest_sync_token;
     destination_gl->GenUnverifiedSyncTokenCHROMIUM(dest_sync_token.GetData());
     canvas_gl->WaitSyncTokenCHROMIUM(dest_sync_token.GetConstData());
-
-    SyncTokenClientImpl client(canvas_gl);
-    video_frame->UpdateReleaseSyncToken(&client);
   } else {
+    gpu::gles2::GLES2Interface* canvas_gl = context_3d.gl;
+
+    // Create a mailbox for the target texture.
+    gpu::MailboxHolder mailbox_holder;
+    mailbox_holder.texture_target = target;
+    destination_gl->ProduceTextureDirectCHROMIUM(texture,
+                                                 mailbox_holder.mailbox.name);
+    destination_gl->GenUnverifiedSyncTokenCHROMIUM(
+        mailbox_holder.sync_token.GetData());
+
+    // Make the target texture available to |canvas_gl|.
+    canvas_gl->WaitSyncTokenCHROMIUM(mailbox_holder.sync_token.GetConstData());
+    uint32_t intermediate_texture =
+        canvas_gl->CreateAndConsumeTextureCHROMIUM(mailbox_holder.mailbox.name);
+
+    // Copy the texture contents and discard the intermediate texture.
     CopyVideoFrameSingleTextureToGLTexture(
-        destination_gl, video_frame.get(), target, texture, internal_format,
-        format, type, level, premultiply_alpha, flip_y);
+        canvas_gl, video_frame.get(), target, intermediate_texture,
+        internal_format, format, type, level, premultiply_alpha, flip_y);
+    canvas_gl->DeleteTextures(1, &intermediate_texture);
+    canvas_gl->ShallowFlushCHROMIUM();
+
+    SynchronizeVideoFrameRead(video_frame, canvas_gl, context_support);
   }
 
   return true;
@@ -1208,8 +1261,9 @@
       return false;
     last_id_ = video_frame->unique_id();
   }
+
+  DCHECK(last_image_);
   last_image_deleting_timer_.Reset();
-  DCHECK(!!last_image_);
   return true;
 }
 
diff --git a/media/renderers/paint_canvas_video_renderer.h b/media/renderers/paint_canvas_video_renderer.h
index d732111..d4752f3 100644
--- a/media/renderers/paint_canvas_video_renderer.h
+++ b/media/renderers/paint_canvas_video_renderer.h
@@ -29,6 +29,7 @@
 
 namespace gpu {
 struct Capabilities;
+class ContextSupport;
 }
 
 namespace media {
@@ -39,25 +40,29 @@
   PaintCanvasVideoRenderer();
   ~PaintCanvasVideoRenderer();
 
-  // Paints |video_frame| on |canvas|, scaling and rotating the result to fit
-  // dimensions specified by |dest_rect|.
-  // If the format of |video_frame| is PIXEL_FORMAT_NATIVE_TEXTURE, |context_3d|
-  // must be provided.
+  // Paints |video_frame| translated and scaled to |dest_rect| on |canvas|.
   //
-  // Black will be painted on |canvas| if |video_frame| is null.
+  // If the format of |video_frame| is PIXEL_FORMAT_NATIVE_TEXTURE, |context_3d|
+  // and |context_support| must be provided.
+  //
+  // If |video_frame| is nullptr or an unsupported format, |dest_rect| will be
+  // painted black.
   void Paint(const scoped_refptr<VideoFrame>& video_frame,
              cc::PaintCanvas* canvas,
              const gfx::RectF& dest_rect,
              cc::PaintFlags& flags,
              VideoRotation video_rotation,
-             const Context3D& context_3d);
+             const Context3D& context_3d,
+             gpu::ContextSupport* context_support);
 
-  // Copy |video_frame| on |canvas|.
+  // Paints |video_frame| scaled to its visible size on |canvas|.
+  //
   // If the format of |video_frame| is PIXEL_FORMAT_NATIVE_TEXTURE, |context_3d|
-  // must be provided.
+  // and |context_support| must be provided.
   void Copy(const scoped_refptr<VideoFrame>& video_frame,
             cc::PaintCanvas* canvas,
-            const Context3D& context_3d);
+            const Context3D& context_3d,
+            gpu::ContextSupport* context_support);
 
   // Convert the contents of |video_frame| to raw RGB pixels. |rgb_pixels|
   // should point into a buffer large enough to hold as many 32 bit RGBA pixels
@@ -82,15 +87,12 @@
       bool premultiply_alpha,
       bool flip_y);
 
-  // Copy the contents of texture of |video_frame| to texture |texture| in
-  // context |destination_gl|.
-  // |level|, |internal_format|, |type| specify target texture |texture|.
+  // Copy the contents of |video_frame| to |texture| of |destination_gl|.
+  //
   // The format of |video_frame| must be VideoFrame::NATIVE_TEXTURE.
-  // |context_3d| has a GrContext that may be used during the copy.
-  // CorrectLastImageDimensions() ensures that the source texture will be
-  // cropped to |visible_rect|. Returns true on success.
   bool CopyVideoFrameTexturesToGLTexture(
       const Context3D& context_3d,
+      gpu::ContextSupport* context_support,
       gpu::gles2::GLES2Interface* destination_gl,
       const scoped_refptr<VideoFrame>& video_frame,
       unsigned int target,
@@ -168,8 +170,6 @@
   // never be painted again, so we can release the resource.
   void ResetCache();
 
-  void CorrectLastImageDimensions(const SkIRect& visible_rect);
-
   // Used for unit test.
   SkISize LastImageDimensionsForTesting();
 
@@ -179,6 +179,8 @@
   bool UpdateLastImage(const scoped_refptr<VideoFrame>& video_frame,
                        const Context3D& context_3d);
 
+  void CorrectLastImageDimensions(const SkIRect& visible_rect);
+
   // Last image used to draw to the canvas.
   cc::PaintImage last_image_;
 
diff --git a/media/renderers/paint_canvas_video_renderer_unittest.cc b/media/renderers/paint_canvas_video_renderer_unittest.cc
index 3bca993..bd321305 100644
--- a/media/renderers/paint_canvas_video_renderer_unittest.cc
+++ b/media/renderers/paint_canvas_video_renderer_unittest.cc
@@ -225,7 +225,7 @@
   cc::PaintFlags flags;
   flags.setFilterQuality(kLow_SkFilterQuality);
   renderer_.Paint(nullptr, canvas, kNaturalRect, flags, VIDEO_ROTATION_0,
-                  Context3D());
+                  Context3D(), nullptr);
 }
 
 void PaintCanvasVideoRendererTest::Paint(
@@ -260,13 +260,13 @@
   flags.setBlendMode(mode);
   flags.setFilterQuality(kLow_SkFilterQuality);
   renderer_.Paint(video_frame, canvas, dest_rect, flags, video_rotation,
-                  Context3D());
+                  Context3D(), nullptr);
 }
 
 void PaintCanvasVideoRendererTest::Copy(
     const scoped_refptr<VideoFrame>& video_frame,
     cc::PaintCanvas* canvas) {
-  renderer_.Copy(video_frame, canvas, Context3D());
+  renderer_.Copy(video_frame, canvas, Context3D(), nullptr);
 }
 
 TEST_F(PaintCanvasVideoRendererTest, NoFrame) {
@@ -564,7 +564,7 @@
   flags.setFilterQuality(kNone_SkFilterQuality);
   renderer_.Paint(video_frame, &canvas,
                   gfx::RectF(bitmap.width(), bitmap.height()), flags,
-                  VIDEO_ROTATION_0, Context3D());
+                  VIDEO_ROTATION_0, Context3D(), nullptr);
   for (int j = 0; j < bitmap.height(); j++) {
     for (int i = 0; i < bitmap.width(); i++) {
       const int value = i + j * bitmap.width();
@@ -657,43 +657,32 @@
   cc::PaintFlags flags;
   flags.setFilterQuality(kLow_SkFilterQuality);
   renderer_.Paint(video_frame, &canvas, kNaturalRect, flags, VIDEO_ROTATION_90,
-                  context_3d);
+                  context_3d, nullptr);
 }
 
 void EmptyCallback(const gpu::SyncToken& sync_token) {}
 
 TEST_F(PaintCanvasVideoRendererTest, CorrectFrameSizeToVisibleRect) {
-  int fWidth{16}, fHeight{16};
+  constexpr int fWidth{16}, fHeight{16};
   SkImageInfo imInfo =
       SkImageInfo::MakeN32(fWidth, fHeight, kOpaque_SkAlphaType);
 
-  sk_sp<const GrGLInterface> glInterface(GrGLCreateNullInterface());
-  sk_sp<GrContext> grContext = GrContext::MakeGL(std::move(glInterface));
+  cc::SkiaPaintCanvas canvas(AllocBitmap(kWidth, kHeight));
 
-  sk_sp<SkSurface> surface =
-      SkSurface::MakeRenderTarget(grContext.get(), SkBudgeted::kYes, imInfo);
-  cc::SkiaPaintCanvas canvas(surface->getCanvas());
-
-  TestGLES2Interface gles2;
-  Context3D context_3d(&gles2, grContext.get());
   gfx::Size coded_size(fWidth, fHeight);
   gfx::Size visible_size(fWidth / 2, fHeight / 2);
 
-  gpu::MailboxHolder mailbox_holders[VideoFrame::kMaxPlanes];
-  for (size_t i = 0; i < VideoFrame::kMaxPlanes; i++) {
-    mailbox_holders[i] = gpu::MailboxHolder(
-        gpu::Mailbox::Generate(), gpu::SyncToken(), GL_TEXTURE_RECTANGLE_ARB);
-  }
+  uint8_t memory[fWidth * fHeight * 2] = {0};
 
-  auto video_frame = VideoFrame::WrapNativeTextures(
-      PIXEL_FORMAT_I420, mailbox_holders, base::Bind(EmptyCallback), coded_size,
-      gfx::Rect(visible_size), visible_size,
+  auto video_frame = media::VideoFrame::WrapExternalData(
+      media::PIXEL_FORMAT_Y16, coded_size, gfx::Rect(visible_size),
+      visible_size, &memory[0], fWidth * fHeight * 2,
       base::TimeDelta::FromMilliseconds(4));
 
   gfx::RectF visible_rect(visible_size.width(), visible_size.height());
   cc::PaintFlags flags;
   renderer_.Paint(video_frame, &canvas, visible_rect, flags, VIDEO_ROTATION_0,
-                  context_3d);
+                  Context3D(), nullptr);
 
   EXPECT_EQ(fWidth / 2, renderer_.LastImageDimensionsForTesting().width());
   EXPECT_EQ(fWidth / 2, renderer_.LastImageDimensionsForTesting().height());
diff --git a/media/renderers/video_resource_updater.cc b/media/renderers/video_resource_updater.cc
index 952c9a7..5b4abce 100644
--- a/media/renderers/video_resource_updater.cc
+++ b/media/renderers/video_resource_updater.cc
@@ -866,7 +866,7 @@
 
         // This is software path, so canvas and video_frame are always backed
         // by software.
-        video_renderer_->Copy(video_frame, &canvas, Context3D());
+        video_renderer_->Copy(video_frame, &canvas, Context3D(), nullptr);
       } else {
         HardwarePlaneResource* hardware_resource = plane_resource->AsHardware();
         size_t bytes_per_row = viz::ResourceSizes::CheckedWidthInBytes<size_t>(
diff --git a/net/dns/BUILD.gn b/net/dns/BUILD.gn
index a8e8bd1..8ea242f8 100644
--- a/net/dns/BUILD.gn
+++ b/net/dns/BUILD.gn
@@ -506,6 +506,18 @@
   dict = "//net/data/fuzzer_dictionaries/net_dns_record_fuzzer.dict"
 }
 
+fuzzer_test("net_dns_query_parse_fuzzer") {
+  sources = [
+    "dns_query_parse_fuzzer.cc",
+  ]
+  deps = [
+    "//base",
+    "//net",
+    "//net:net_fuzzer_test_support",
+  ]
+  dict = "//net/data/fuzzer_dictionaries/net_dns_record_fuzzer.dict"
+}
+
 fuzzer_test("net_host_resolver_impl_fuzzer") {
   sources = [
     "host_resolver_impl_fuzzer.cc",
diff --git a/net/dns/dns_protocol.h b/net/dns/dns_protocol.h
index 1493167..b022b91 100644
--- a/net/dns/dns_protocol.h
+++ b/net/dns/dns_protocol.h
@@ -76,12 +76,12 @@
 
 // On-the-wire header. All uint16_t are in network order.
 struct NET_EXPORT Header {
-  uint16_t id;
-  uint16_t flags;
-  uint16_t qdcount;
-  uint16_t ancount;
-  uint16_t nscount;
-  uint16_t arcount;
+  uint16_t id = 0;
+  uint16_t flags = 0;
+  uint16_t qdcount = 0;
+  uint16_t ancount = 0;
+  uint16_t nscount = 0;
+  uint16_t arcount = 0;
 };
 
 #pragma pack(pop)
@@ -141,6 +141,7 @@
 //
 // https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-12
 static const uint16_t kFlagResponse = 0x8000;
+static const uint16_t kFlagAA = 0x400;  // Authoritative Answer - response flag.
 static const uint16_t kFlagRD = 0x100;  // Recursion Desired - query flag.
 static const uint16_t kFlagTC = 0x200;  // Truncated - server flag.
 
diff --git a/net/dns/dns_query.cc b/net/dns/dns_query.cc
index 0229d1f..a5e9726 100644
--- a/net/dns/dns_query.cc
+++ b/net/dns/dns_query.cc
@@ -75,12 +75,52 @@
   }
 }
 
+DnsQuery::DnsQuery(scoped_refptr<IOBufferWithSize> buffer)
+    : io_buffer_(std::move(buffer)) {}
+
 DnsQuery::~DnsQuery() = default;
 
 std::unique_ptr<DnsQuery> DnsQuery::CloneWithNewId(uint16_t id) const {
   return base::WrapUnique(new DnsQuery(*this, id));
 }
 
+bool DnsQuery::Parse() {
+  if (io_buffer_ == nullptr || io_buffer_->data() == nullptr) {
+    return false;
+  }
+  // We should only parse the query once if the query is constructed from a raw
+  // buffer. If we have constructed the query from data or the query is already
+  // parsed after constructed from a raw buffer, |header_| is not null.
+  DCHECK(header_ == nullptr);
+  base::BigEndianReader reader(io_buffer_->data(), io_buffer_->size());
+  dns_protocol::Header header;
+  if (!ReadHeader(&reader, &header)) {
+    return false;
+  }
+  if (header.flags & dns_protocol::kFlagResponse) {
+    return false;
+  }
+  if (header.qdcount > 1) {
+    VLOG(1) << "Not supporting parsing a DNS query with multiple questions.";
+    return false;
+  }
+  std::string qname;
+  if (!ReadName(&reader, &qname)) {
+    return false;
+  }
+  uint16_t qtype;
+  uint16_t qclass;
+  if (!reader.ReadU16(&qtype) || !reader.ReadU16(&qclass) ||
+      qclass != dns_protocol::kClassIN) {
+    return false;
+  }
+  // |io_buffer_| now contains the raw packet of a valid DNS query, we just
+  // need to properly initialize |qname_size_| and |header_|.
+  qname_size_ = qname.size();
+  header_ = reinterpret_cast<dns_protocol::Header*>(io_buffer_->data());
+  return true;
+}
+
 uint16_t DnsQuery::id() const {
   return base::NetToHost16(header_->id);
 }
@@ -113,4 +153,35 @@
   header_->id = base::HostToNet16(id);
 }
 
+bool DnsQuery::ReadHeader(base::BigEndianReader* reader,
+                          dns_protocol::Header* header) {
+  return (
+      reader->ReadU16(&header->id) && reader->ReadU16(&header->flags) &&
+      reader->ReadU16(&header->qdcount) && reader->ReadU16(&header->ancount) &&
+      reader->ReadU16(&header->nscount) && reader->ReadU16(&header->arcount));
+}
+
+bool DnsQuery::ReadName(base::BigEndianReader* reader, std::string* out) {
+  DCHECK(out != nullptr);
+  out->clear();
+  out->reserve(dns_protocol::kMaxNameLength);
+  uint8_t label_length;
+  if (!reader->ReadU8(&label_length)) {
+    return false;
+  }
+  out->append(reinterpret_cast<char*>(&label_length), 1);
+  while (label_length) {
+    base::StringPiece label;
+    if (!reader->ReadPiece(&label, label_length)) {
+      return false;
+    }
+    out->append(label.data(), label.size());
+    if (!reader->ReadU8(&label_length)) {
+      return false;
+    }
+    out->append(reinterpret_cast<char*>(&label_length), 1);
+  }
+  return true;
+}
+
 }  // namespace net
diff --git a/net/dns/dns_query.h b/net/dns/dns_query.h
index b68772e..c01909e 100644
--- a/net/dns/dns_query.h
+++ b/net/dns/dns_query.h
@@ -15,13 +15,17 @@
 #include "base/strings/string_piece.h"
 #include "net/base/net_export.h"
 
+namespace base {
+class BigEndianReader;
+}  // namespace base
+
 namespace net {
 
 class OptRecordRdata;
 
 namespace dns_protocol {
 struct Header;
-}
+}  // namespace dns_protocol
 
 class IOBufferWithSize;
 
@@ -36,11 +40,22 @@
            const base::StringPiece& qname,
            uint16_t qtype,
            const OptRecordRdata* opt_rdata = nullptr);
+
+  // Constructs an empty query from a raw packet in |buffer|. If the raw packet
+  // represents a valid DNS query in the wire format (RFC 1035), Parse() will
+  // populate the empty query.
+  DnsQuery(scoped_refptr<IOBufferWithSize> buffer);
+
   ~DnsQuery();
 
   // Clones |this| verbatim, with ID field of the header set to |id|.
   std::unique_ptr<DnsQuery> CloneWithNewId(uint16_t id) const;
 
+  // Returns true and populates the query if the internally stored raw packet
+  // can be parsed. This should only be called when DnsQuery is constructed from
+  // the raw buffer.
+  bool Parse();
+
   // DnsQuery field accessors.
   uint16_t id() const;
   base::StringPiece qname() const;
@@ -50,7 +65,8 @@
   // response.
   base::StringPiece question() const;
 
-  // IOBuffer accessor to be used for writing out the query.
+  // IOBuffer accessor to be used for writing out the query. The buffer has
+  // the same byte layout as the DNS query wire format.
   IOBufferWithSize* io_buffer() const { return io_buffer_.get(); }
 
   void set_flags(uint16_t flags);
@@ -58,6 +74,12 @@
  private:
   DnsQuery(const DnsQuery& orig, uint16_t id);
 
+  bool ReadHeader(base::BigEndianReader* reader, dns_protocol::Header* out);
+  // After read, |out| is in the DNS format, e.g.
+  // "\x03""www""\x08""chromium""\x03""com""\x00". Use DNSDomainToString to
+  // convert to the dotted format "www.chromium.com" with no trailing dot.
+  bool ReadName(base::BigEndianReader* reader, std::string* out);
+
   // Returns the size of the question section.
   size_t question_size() const {
     // QNAME + QTYPE + QCLASS
@@ -66,13 +88,13 @@
 
   // Size of the DNS name (*NOT* hostname) we are trying to resolve; used
   // to calculate offsets.
-  size_t qname_size_;
+  size_t qname_size_ = 0;
 
   // Contains query bytes to be consumed by higher level Write() call.
   scoped_refptr<IOBufferWithSize> io_buffer_;
 
   // Pointer to the dns header section.
-  dns_protocol::Header* header_;
+  dns_protocol::Header* header_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(DnsQuery);
 };
diff --git a/net/dns/dns_query_parse_fuzzer.cc b/net/dns/dns_query_parse_fuzzer.cc
new file mode 100644
index 0000000..dceb703
--- /dev/null
+++ b/net/dns/dns_query_parse_fuzzer.cc
@@ -0,0 +1,20 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+
+#include "net/base/io_buffer.h"
+#include "net/dns/dns_query.h"
+
+// Entry point for LibFuzzer.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  auto packet = base::MakeRefCounted<net::IOBufferWithSize>(size);
+  memcpy(packet->data(), data, size);
+  auto out = std::make_unique<net::DnsQuery>(packet.get());
+  out->Parse();
+  return 0;
+}
diff --git a/net/dns/dns_query_unittest.cc b/net/dns/dns_query_unittest.cc
index 7ee93008..0638bb1 100644
--- a/net/dns/dns_query_unittest.cc
+++ b/net/dns/dns_query_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "net/dns/dns_query.h"
 
+#include "base/stl_util.h"
 #include "net/base/io_buffer.h"
 #include "net/dns/dns_protocol.h"
 #include "net/dns/record_rdata.h"
@@ -20,9 +21,26 @@
   return std::make_tuple(buf->data(), buf->size());
 }
 
+bool ParseAndCreateDnsQueryFromRawPacket(const uint8_t* data,
+                                         size_t length,
+                                         std::unique_ptr<DnsQuery>* out) {
+  auto packet = base::MakeRefCounted<IOBufferWithSize>(length);
+  memcpy(packet->data(), data, length);
+  out->reset(new DnsQuery(packet));
+  return (*out)->Parse();
+}
+
+// This includes \0 at the end.
+const char kQNameData[] =
+    "\x03"
+    "www"
+    "\x07"
+    "example"
+    "\x03"
+    "com";
+
 TEST(DnsQueryTest, Constructor) {
   // This includes \0 at the end.
-  const char qname_data[] = "\x03""www""\x07""example""\x03""com";
   const uint8_t query_data[] = {
       // Header
       0xbe, 0xef, 0x01, 0x00,  // Flags -- set RD (recursion desired) bit.
@@ -38,7 +56,7 @@
       0x00, 0x01,  // QCLASS: IN class.
   };
 
-  base::StringPiece qname(qname_data, sizeof(qname_data));
+  base::StringPiece qname(kQNameData, sizeof(kQNameData));
   DnsQuery q1(0xbeef, qname, dns_protocol::kTypeA);
   EXPECT_EQ(dns_protocol::kTypeA, q1.qtype());
   EXPECT_THAT(AsTuple(q1.io_buffer()), ElementsAreArray(query_data));
@@ -50,9 +68,7 @@
 }
 
 TEST(DnsQueryTest, Clone) {
-  // This includes \0 at the end.
-  const char qname_data[] = "\x03""www""\x07""example""\x03""com";
-  base::StringPiece qname(qname_data, sizeof(qname_data));
+  base::StringPiece qname(kQNameData, sizeof(kQNameData));
 
   DnsQuery q1(0, qname, dns_protocol::kTypeA);
   EXPECT_EQ(0, q1.id());
@@ -64,14 +80,6 @@
 }
 
 TEST(DnsQueryTest, EDNS0) {
-  // This includes \0 at the end.
-  const char qname_data[] =
-      "\x03"
-      "www"
-      "\x07"
-      "example"
-      "\x03"
-      "com";
   const uint8_t query_data[] = {
       // Header
       0xbe, 0xef, 0x01, 0x00,  // Flags -- set RD (recursion desired) bit.
@@ -96,7 +104,7 @@
       0xDE, 0xAD, 0xBE, 0xEF   // OPT data
   };
 
-  base::StringPiece qname(qname_data, sizeof(qname_data));
+  base::StringPiece qname(kQNameData, sizeof(kQNameData));
   OptRecordRdata opt_rdata;
   opt_rdata.AddOpt(OptRecordRdata::Opt(255, "\xde\xad\xbe\xef"));
   DnsQuery q1(0xbeef, qname, dns_protocol::kTypeA, &opt_rdata);
@@ -109,6 +117,126 @@
   EXPECT_EQ(question, q1.question());
 }
 
+TEST(DnsQueryParseTest, SingleQuestionForTypeARecord) {
+  const uint8_t query_data[] = {
+      0x12, 0x34,  // ID
+      0x00, 0x00,  // flags
+      0x00, 0x01,  // number of questions
+      0x00, 0x00,  // number of answer rr
+      0x00, 0x00,  // number of name server rr
+      0x00, 0x00,  // number of additional rr
+      0x03, 'w',  'w', 'w', 0x07, 'e', 'x', 'a',
+      'm',  'p',  'l', 'e', 0x03, 'c', 'o', 'm',
+      0x00,        // null label
+      0x00, 0x01,  // type A Record
+      0x00, 0x01,  // class IN
+  };
+  std::unique_ptr<DnsQuery> query;
+  EXPECT_TRUE(ParseAndCreateDnsQueryFromRawPacket(query_data,
+                                                  sizeof(query_data), &query));
+  EXPECT_EQ(0x1234, query->id());
+  base::StringPiece qname(kQNameData, sizeof(kQNameData));
+  EXPECT_EQ(qname, query->qname());
+  EXPECT_EQ(dns_protocol::kTypeA, query->qtype());
+}
+
+TEST(DnsQueryParseTest, SingleQuestionForTypeAAAARecord) {
+  const uint8_t query_data[] = {
+      0x12, 0x34,  // ID
+      0x00, 0x00,  // flags
+      0x00, 0x01,  // number of questions
+      0x00, 0x00,  // number of answer rr
+      0x00, 0x00,  // number of name server rr
+      0x00, 0x00,  // number of additional rr
+      0x03, 'w',  'w', 'w', 0x07, 'e', 'x', 'a',
+      'm',  'p',  'l', 'e', 0x03, 'c', 'o', 'm',
+      0x00,        // null label
+      0x00, 0x1c,  // type AAAA Record
+      0x00, 0x01,  // class IN
+  };
+  std::unique_ptr<DnsQuery> query;
+  EXPECT_TRUE(ParseAndCreateDnsQueryFromRawPacket(query_data,
+                                                  sizeof(query_data), &query));
+  EXPECT_EQ(0x1234, query->id());
+  base::StringPiece qname(kQNameData, sizeof(kQNameData));
+  EXPECT_EQ(qname, query->qname());
+  EXPECT_EQ(dns_protocol::kTypeAAAA, query->qtype());
+}
+
+const uint8_t kQueryTruncatedQuestion[] = {
+    0x12, 0x34,  // ID
+    0x00, 0x00,  // flags
+    0x00, 0x02,  // number of questions
+    0x00, 0x00,  // number of answer rr
+    0x00, 0x00,  // number of name server rr
+    0x00, 0x00,  // number of additional rr
+    0x03, 'w',  'w', 'w', 0x07, 'e', 'x', 'a',
+    'm',  'p',  'l', 'e', 0x03, 'c', 'o', 'm',
+    0x00,        // null label
+    0x00, 0x01,  // type A Record
+    0x00,        // class IN, truncated
+};
+
+const uint8_t kQueryTwoQuestions[] = {
+    0x12, 0x34,  // ID
+    0x00, 0x00,  // flags
+    0x00, 0x02,  // number of questions
+    0x00, 0x00,  // number of answer rr
+    0x00, 0x00,  // number of name server rr
+    0x00, 0x00,  // number of additional rr
+    0x03, 'w',  'w', 'w', 0x07, 'e', 'x', 'a', 'm',  'p', 'l', 'e',
+    0x03, 'c',  'o', 'm',
+    0x00,        // null label
+    0x00, 0x01,  // type A Record
+    0x00, 0x01,  // class IN
+    0x07, 'e',  'x', 'a', 'm',  'p', 'l', 'e', 0x03, 'o', 'r', 'g',
+    0x00,        // null label
+    0x00, 0x1c,  // type AAAA Record
+    0x00, 0x01,  // class IN
+};
+
+const uint8_t kQueryInvalidDNSDomainName1[] = {
+    0x12, 0x34,            // ID
+    0x00, 0x00,            // flags
+    0x00, 0x01,            // number of questions
+    0x00, 0x00,            // number of answer rr
+    0x00, 0x00,            // number of name server rr
+    0x00, 0x00,            // number of additional rr
+    0x02, 'w',  'w', 'w',  // wrong label length
+    0x07, 'e',  'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', 'o', 'm',
+    0x00,        // null label
+    0x00, 0x01,  // type A Record
+    0x00, 0x01,  // class IN
+};
+
+const uint8_t kQueryInvalidDNSDomainName2[] = {
+    0x12, 0x34,  // ID
+    0x00, 0x00,  // flags
+    0x00, 0x01,  // number of questions
+    0x00, 0x00,  // number of answer rr
+    0x00, 0x00,  // number of name server rr
+    0x00, 0x00,  // number of additional rr
+    0xc0, 0x02,  // illegal name pointer
+    0x00, 0x01,  // type A Record
+    0x00, 0x01,  // class IN
+};
+
+TEST(DnsQueryParseTest, FailsInvalidQueries) {
+  const struct TestCase {
+    const uint8_t* data;
+    size_t size;
+  } testcases[] = {
+      {kQueryTruncatedQuestion, base::size(kQueryTruncatedQuestion)},
+      {kQueryTwoQuestions, base::size(kQueryTwoQuestions)},
+      {kQueryInvalidDNSDomainName1, base::size(kQueryInvalidDNSDomainName1)},
+      {kQueryInvalidDNSDomainName2, base::size(kQueryInvalidDNSDomainName2)}};
+  std::unique_ptr<DnsQuery> query;
+  for (const auto& testcase : testcases) {
+    EXPECT_FALSE(ParseAndCreateDnsQueryFromRawPacket(testcase.data,
+                                                     testcase.size, &query));
+  }
+}
+
 }  // namespace
 
 }  // namespace net
diff --git a/net/dns/dns_response.cc b/net/dns/dns_response.cc
index c1fa3de..809ca0fb6 100644
--- a/net/dns/dns_response.cc
+++ b/net/dns/dns_response.cc
@@ -5,6 +5,8 @@
 #include "net/dns/dns_response.h"
 
 #include <limits>
+#include <numeric>
+#include <vector>
 
 #include "base/big_endian.h"
 #include "base/strings/string_util.h"
@@ -16,6 +18,7 @@
 #include "net/dns/dns_protocol.h"
 #include "net/dns/dns_query.h"
 #include "net/dns/dns_util.h"
+#include "net/dns/record_rdata.h"
 
 namespace net {
 
@@ -25,10 +28,16 @@
 
 const uint8_t kRcodeMask = 0xf;
 
+// RFC 1035, Section 4.1.3.
+// TYPE (2 bytes) + CLASS (2 bytes) + TTL (4 bytes) + RDLENGTH (2 bytes)
+const size_t kResourceRecordSizeInBytesWithoutNameAndRData = 10;
+
 }  // namespace
 
 DnsResourceRecord::DnsResourceRecord() = default;
 
+DnsResourceRecord::DnsResourceRecord(const DnsResourceRecord& other) = default;
+
 DnsResourceRecord::~DnsResourceRecord() = default;
 
 DnsRecordParser::DnsRecordParser() : packet_(NULL), length_(0), cur_(0) {
@@ -150,12 +159,74 @@
   return true;
 }
 
+DnsResponse::DnsResponse(uint16_t id,
+                         bool is_authoritative,
+                         const std::vector<DnsResourceRecord>& answers,
+                         const base::Optional<DnsQuery>& query) {
+  bool has_query = query.has_value();
+  dns_protocol::Header header;
+  header.id = id;
+  bool success = true;
+  if (has_query) {
+    success &= (id == query.value().id());
+    DCHECK(success);
+    // DnsQuery only supports a single question.
+    header.qdcount = 1;
+  }
+  header.flags |= dns_protocol::kFlagResponse;
+  if (is_authoritative) {
+    header.flags |= dns_protocol::kFlagAA;
+  }
+  header.ancount = answers.size();
+
+  // Response starts with the header and the question section (if any).
+  size_t response_size =
+      has_query ? query.value().io_buffer()->size() : sizeof(header);
+  // Add the size of all answers.
+  response_size = std::accumulate(
+      answers.begin(), answers.end(), response_size,
+      [](size_t cur_size, const DnsResourceRecord& answer) {
+        bool has_final_dot = answer.name.back() == '.';
+        // Depending on if answer.name in the dotted format has the final dot
+        // for the root domain or not, the corresponding DNS domain name format
+        // to be written to rdata is 1 byte (with dot) or 2 bytes larger in
+        // size. See RFC 1035, Section 3.1 and DNSDomainFromDot.
+        return cur_size + answer.name.size() + (has_final_dot ? 1 : 2) +
+               kResourceRecordSizeInBytesWithoutNameAndRData +
+               answer.rdata.size();
+      });
+
+  io_buffer_ = base::MakeRefCounted<IOBuffer>(response_size);
+  io_buffer_size_ = response_size;
+  base::BigEndianWriter writer(io_buffer_->data(), io_buffer_size_);
+  success &= WriteHeader(&writer, header);
+  DCHECK(success);
+  if (has_query) {
+    success &= WriteQuestion(&writer, query.value());
+    DCHECK(success);
+  }
+  for (const auto& answer : answers) {
+    success &= WriteAnswer(&writer, answer, query);
+    DCHECK(success);
+  }
+  if (!success) {
+    io_buffer_.reset();
+    io_buffer_size_ = 0;
+    return;
+  }
+  if (has_query) {
+    InitParse(io_buffer_size_, query.value());
+  } else {
+    InitParseWithoutQuery(io_buffer_size_);
+  }
+}
+
 DnsResponse::DnsResponse()
     : io_buffer_(base::MakeRefCounted<IOBuffer>(dns_protocol::kMaxUDPSize + 1)),
       io_buffer_size_(dns_protocol::kMaxUDPSize + 1) {}
 
-DnsResponse::DnsResponse(IOBuffer* buffer, size_t size)
-    : io_buffer_(buffer), io_buffer_size_(size) {}
+DnsResponse::DnsResponse(scoped_refptr<IOBuffer> buffer, size_t size)
+    : io_buffer_(std::move(buffer)), io_buffer_size_(size) {}
 
 DnsResponse::DnsResponse(size_t length)
     : io_buffer_(base::MakeRefCounted<IOBuffer>(length)),
@@ -174,7 +245,7 @@
 bool DnsResponse::InitParse(size_t nbytes, const DnsQuery& query) {
   // Response includes query, it should be at least that size.
   if (nbytes < static_cast<size_t>(query.io_buffer()->size()) ||
-      nbytes >= io_buffer_size_) {
+      nbytes > io_buffer_size_) {
     return false;
   }
 
@@ -200,7 +271,7 @@
 }
 
 bool DnsResponse::InitParseWithoutQuery(size_t nbytes) {
-  if (nbytes < kHeaderSize || nbytes >= io_buffer_size_) {
+  if (nbytes < kHeaderSize || nbytes > io_buffer_size_) {
     return false;
   }
 
@@ -350,4 +421,40 @@
   return DNS_PARSE_OK;
 }
 
+bool DnsResponse::WriteHeader(base::BigEndianWriter* writer,
+                              const dns_protocol::Header& header) {
+  return writer->WriteU16(header.id) && writer->WriteU16(header.flags) &&
+         writer->WriteU16(header.qdcount) && writer->WriteU16(header.ancount) &&
+         writer->WriteU16(header.nscount) && writer->WriteU16(header.arcount);
+}
+
+bool DnsResponse::WriteQuestion(base::BigEndianWriter* writer,
+                                const DnsQuery& query) {
+  const base::StringPiece& question = query.question();
+  return writer->WriteBytes(question.data(), question.size());
+}
+
+bool DnsResponse::WriteAnswer(base::BigEndianWriter* writer,
+                              const DnsResourceRecord& answer,
+                              const base::Optional<DnsQuery>& query) {
+  if (query.has_value() && answer.type != query.value().qtype()) {
+    VLOG(1) << "Mismatched answer resource record type and qtype.";
+    return false;
+  }
+  if (!RecordRdata::HasValidSize(answer.rdata, answer.type)) {
+    VLOG(1) << "Invalid RDATA size for an answer.";
+    return false;
+  }
+  std::string domain_name;
+  if (!DNSDomainFromDot(answer.name, &domain_name)) {
+    VLOG(1) << "Invalid dotted name.";
+    return false;
+  }
+  return writer->WriteBytes(domain_name.data(), domain_name.size()) &&
+         writer->WriteU16(answer.type) && writer->WriteU16(answer.klass) &&
+         writer->WriteU32(answer.ttl) &&
+         writer->WriteU16(answer.rdata.size()) &&
+         writer->WriteBytes(answer.rdata.data(), answer.rdata.size());
+}
+
 }  // namespace net
diff --git a/net/dns/dns_response.h b/net/dns/dns_response.h
index ee533c93..1f8aefbf 100644
--- a/net/dns/dns_response.h
+++ b/net/dns/dns_response.h
@@ -12,10 +12,15 @@
 
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/optional.h"
 #include "base/strings/string_piece.h"
 #include "base/time/time.h"
 #include "net/base/net_export.h"
 
+namespace base {
+class BigEndianWriter;
+}  // namespace base
+
 namespace net {
 
 class AddressList;
@@ -24,18 +29,19 @@
 
 namespace dns_protocol {
 struct Header;
-}
+}  // namespace dns_protocol
 
 // Structure representing a Resource Record as specified in RFC 1035, Section
 // 4.1.3.
 struct NET_EXPORT_PRIVATE DnsResourceRecord {
   DnsResourceRecord();
+  explicit DnsResourceRecord(const DnsResourceRecord& other);
   ~DnsResourceRecord();
 
   std::string name;  // in dotted form
-  uint16_t type;
-  uint16_t klass;
-  uint32_t ttl;
+  uint16_t type = 0;
+  uint16_t klass = 0;
+  uint32_t ttl = 0;
   base::StringPiece rdata;  // points to the original response buffer
 };
 
@@ -105,11 +111,18 @@
   // largest possible response, to detect malformed responses.
   DnsResponse();
 
+  // Constructs a response message from |answers| and the originating |query|.
+  // After the successful construction, and the parser is also initialized.
+  DnsResponse(uint16_t id,
+              bool is_authoritative,
+              const std::vector<DnsResourceRecord>& answers,
+              const base::Optional<DnsQuery>& query);
+
   // Constructs a response buffer of given length. Used for TCP transactions.
   explicit DnsResponse(size_t length);
 
-  // Constructs a response taking ownership of the passed buffer.
-  DnsResponse(IOBuffer* buffer, size_t size);
+  // Constructs a response from the passed buffer.
+  DnsResponse(scoped_refptr<IOBuffer> buffer, size_t size);
 
   // Constructs a response from |data|. Used for testing purposes only!
   DnsResponse(const void* data, size_t length, size_t answer_offset);
@@ -124,14 +137,17 @@
   size_t io_buffer_size() const { return io_buffer_size_; }
 
   // Assuming the internal buffer holds |nbytes| bytes, returns true iff the
-  // packet matches the |query| id and question.
+  // packet matches the |query| id and question. This should only be called if
+  // the response is constructed from a raw buffer.
   bool InitParse(size_t nbytes, const DnsQuery& query);
 
   // Assuming the internal buffer holds |nbytes| bytes, initialize the parser
-  // without matching it against an existing query.
+  // without matching it against an existing query. This should only be called
+  // if the response is constructed from a raw buffer.
   bool InitParseWithoutQuery(size_t nbytes);
 
-  // Returns true if response is valid, that is, after successful InitParse.
+  // Returns true if response is valid, that is, after successful InitParse, or
+  // after successful construction of a new response from data.
   bool IsValid() const;
 
   // All of the methods below are valid only if the response is valid.
@@ -160,6 +176,13 @@
   Result ParseToAddressList(AddressList* addr_list, base::TimeDelta* ttl) const;
 
  private:
+  bool WriteHeader(base::BigEndianWriter* writer,
+                   const dns_protocol::Header& header);
+  bool WriteQuestion(base::BigEndianWriter* writer, const DnsQuery& query);
+  bool WriteAnswer(base::BigEndianWriter* wirter,
+                   const DnsResourceRecord& answer,
+                   const base::Optional<DnsQuery>& query);
+
   // Convenience for header access.
   const dns_protocol::Header* header() const;
 
diff --git a/net/dns/dns_response_unittest.cc b/net/dns/dns_response_unittest.cc
index af3f1d9..163f28c 100644
--- a/net/dns/dns_response_unittest.cc
+++ b/net/dns/dns_response_unittest.cc
@@ -4,12 +4,14 @@
 
 #include "net/dns/dns_response.h"
 
+#include "base/optional.h"
 #include "base/time/time.h"
 #include "net/base/address_list.h"
 #include "net/base/io_buffer.h"
 #include "net/dns/dns_protocol.h"
 #include "net/dns/dns_query.h"
 #include "net/dns/dns_test_util.h"
+#include "net/dns/dns_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace net {
@@ -569,6 +571,227 @@
   }
 }
 
+TEST(DnsResponseWriteTest, SingleARecordAnswer) {
+  const char response_data[] = {
+      0x12, 0x34,  // ID
+      0x84, 0x00,  // flags, response with authoritative answer
+      0x00, 0x00,  // number of questions
+      0x00, 0x01,  // number of answer rr
+      0x00, 0x00,  // number of name server rr
+      0x00, 0x00,  // number of additional rr
+      0x03, 'w',  'w',  'w',  0x07, 'e', 'x', 'a',
+      'm',  'p',  'l',  'e',  0x03, 'c', 'o', 'm',
+      0x00,                    // null label
+      0x00, 0x01,              // type A Record
+      0x00, 0x01,              // class IN
+      0x00, 0x00, 0x00, 0x78,  // TTL, 120 seconds
+      0x00, 0x04,              // rdlength, 32 bits
+      0xc0, 0xa8, 0x00, 0x01,  // 192.168.0.1
+  };
+  net::DnsResourceRecord answer;
+  answer.name = "www.example.com";
+  answer.type = dns_protocol::kTypeA;
+  answer.klass = dns_protocol::kClassIN;
+  answer.ttl = 120;  // 120 seconds.
+  answer.rdata = base::StringPiece("\xc0\xa8\x00\x01", 4);
+  std::vector<DnsResourceRecord> answers(1, answer);
+  DnsResponse response(0x1234 /* response_id */, true /* is_authoritative*/,
+                       answers, base::nullopt);
+  ASSERT_NE(nullptr, response.io_buffer());
+  EXPECT_TRUE(response.IsValid());
+  std::string expected_response(response_data, sizeof(response_data));
+  std::string actual_response(response.io_buffer()->data(),
+                              response.io_buffer_size());
+  EXPECT_EQ(expected_response, actual_response);
+}
+
+TEST(DnsResponseWriteTest, SingleARecordAnswerWithFinalDotInName) {
+  const char response_data[] = {
+      0x12, 0x34,  // ID
+      0x84, 0x00,  // flags, response with authoritative answer
+      0x00, 0x00,  // number of questions
+      0x00, 0x01,  // number of answer rr
+      0x00, 0x00,  // number of name server rr
+      0x00, 0x00,  // number of additional rr
+      0x03, 'w',  'w',  'w',  0x07, 'e', 'x', 'a',
+      'm',  'p',  'l',  'e',  0x03, 'c', 'o', 'm',
+      0x00,                    // null label
+      0x00, 0x01,              // type A Record
+      0x00, 0x01,              // class IN
+      0x00, 0x00, 0x00, 0x78,  // TTL, 120 seconds
+      0x00, 0x04,              // rdlength, 32 bits
+      0xc0, 0xa8, 0x00, 0x01,  // 192.168.0.1
+  };
+  net::DnsResourceRecord answer;
+  answer.name = "www.example.com.";  // FQDN with the final dot.
+  answer.type = dns_protocol::kTypeA;
+  answer.klass = dns_protocol::kClassIN;
+  answer.ttl = 120;  // 120 seconds.
+  answer.rdata = base::StringPiece("\xc0\xa8\x00\x01", 4);
+  std::vector<DnsResourceRecord> answers(1, answer);
+  DnsResponse response(0x1234 /* response_id */, true /* is_authoritative*/,
+                       answers, base::nullopt);
+  ASSERT_NE(nullptr, response.io_buffer());
+  EXPECT_TRUE(response.IsValid());
+  std::string expected_response(response_data, sizeof(response_data));
+  std::string actual_response(response.io_buffer()->data(),
+                              response.io_buffer_size());
+  EXPECT_EQ(expected_response, actual_response);
+}
+
+TEST(DnsResponseWriteTest, SingleARecordAnswerWithQuestion) {
+  const char response_data[] = {
+      0x12, 0x34,  // ID
+      0x84, 0x00,  // flags, response with authoritative answer
+      0x00, 0x01,  // number of questions
+      0x00, 0x01,  // number of answer rr
+      0x00, 0x00,  // number of name server rr
+      0x00, 0x00,  // number of additional rr
+      0x03, 'w',  'w',  'w',  0x07, 'e', 'x', 'a',
+      'm',  'p',  'l',  'e',  0x03, 'c', 'o', 'm',
+      0x00,        // null label
+      0x00, 0x01,  // type A Record
+      0x00, 0x01,  // class IN
+      0x03, 'w',  'w',  'w',  0x07, 'e', 'x', 'a',
+      'm',  'p',  'l',  'e',  0x03, 'c', 'o', 'm',
+      0x00,                    // null label
+      0x00, 0x01,              // type A Record
+      0x00, 0x01,              // class IN
+      0x00, 0x00, 0x00, 0x78,  // TTL, 120 seconds
+      0x00, 0x04,              // rdlength, 32 bits
+      0xc0, 0xa8, 0x00, 0x01,  // 192.168.0.1
+  };
+  std::string dotted_name("www.example.com");
+  std::string dns_name;
+  ASSERT_TRUE(DNSDomainFromDot(dotted_name, &dns_name));
+  base::Optional<DnsQuery> query;
+  query.emplace(0x1234 /* id */, dns_name, dns_protocol::kTypeA);
+  net::DnsResourceRecord answer;
+  answer.name = dotted_name;
+  answer.type = dns_protocol::kTypeA;
+  answer.klass = dns_protocol::kClassIN;
+  answer.ttl = 120;  // 120 seconds.
+  answer.rdata = base::StringPiece("\xc0\xa8\x00\x01", 4);
+  std::vector<DnsResourceRecord> answers(1, answer);
+  DnsResponse response(0x1234 /* id */, true /* is_authoritative*/, answers,
+                       query);
+  ASSERT_NE(nullptr, response.io_buffer());
+  EXPECT_TRUE(response.IsValid());
+  std::string expected_response(response_data, sizeof(response_data));
+  std::string actual_response(response.io_buffer()->data(),
+                              response.io_buffer_size());
+  EXPECT_EQ(expected_response, actual_response);
+}
+
+TEST(DnsResponseWriteTest, SingleQuadARecordAnswer) {
+  const char response_data[] = {
+      0x12, 0x34,  // ID
+      0x84, 0x00,  // flags, response with authoritative answer
+      0x00, 0x00,  // number of questions
+      0x00, 0x01,  // number of answer rr
+      0x00, 0x00,  // number of name server rr
+      0x00, 0x00,  // number of additional rr
+      0x03, 'w',  'w',  'w',  0x07, 'e',  'x',  'a',
+      'm',  'p',  'l',  'e',  0x03, 'c',  'o',  'm',
+      0x00,                                            // null label
+      0x00, 0x1c,                                      // type AAAA Record
+      0x00, 0x01,                                      // class IN
+      0x00, 0x00, 0x00, 0x78,                          // TTL, 120 seconds
+      0x00, 0x10,                                      // rdlength, 128 bits
+      0xfd, 0x12, 0x34, 0x56, 0x78, 0x9a, 0x00, 0x01,  // fd12:3456:789a:1::1
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+  };
+  net::DnsResourceRecord answer;
+  answer.name = "www.example.com";
+  answer.type = dns_protocol::kTypeAAAA;
+  answer.klass = dns_protocol::kClassIN;
+  answer.ttl = 120;  // 120 seconds.
+  answer.rdata = base::StringPiece(
+      "\xfd\x12\x34\x56\x78\x9a\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01", 16);
+  std::vector<DnsResourceRecord> answers(1, answer);
+  DnsResponse response(0x1234 /* id */, true /* is_authoritative*/, answers,
+                       base::nullopt);
+  ASSERT_NE(nullptr, response.io_buffer());
+  EXPECT_TRUE(response.IsValid());
+  std::string expected_response(response_data, sizeof(response_data));
+  std::string actual_response(response.io_buffer()->data(),
+                              response.io_buffer_size());
+  EXPECT_EQ(expected_response, actual_response);
+}
+
+TEST(DnsResponseWriteTest, TwoAnswersWithAAndQuadARecords) {
+  const char response_data[] = {
+      0x12, 0x34,  // ID
+      0x84, 0x00,  // flags, response with authoritative answer
+      0x00, 0x00,  // number of questions
+      0x00, 0x02,  // number of answer rr
+      0x00, 0x00,  // number of name server rr
+      0x00, 0x00,  // number of additional rr
+      0x03, 'w',  'w',  'w',  0x07, 'e',  'x',  'a',  'm',  'p', 'l', 'e',
+      0x03, 'c',  'o',  'm',
+      0x00,                    // null label
+      0x00, 0x01,              // type A Record
+      0x00, 0x01,              // class IN
+      0x00, 0x00, 0x00, 0x78,  // TTL, 120 seconds
+      0x00, 0x04,              // rdlength, 32 bits
+      0xc0, 0xa8, 0x00, 0x01,  // 192.168.0.1
+      0x07, 'e',  'x',  'a',  'm',  'p',  'l',  'e',  0x03, 'o', 'r', 'g',
+      0x00,                                            // null label
+      0x00, 0x1c,                                      // type AAAA Record
+      0x00, 0x01,                                      // class IN
+      0x00, 0x00, 0x00, 0x3c,                          // TTL, 60 seconds
+      0x00, 0x10,                                      // rdlength, 128 bits
+      0xfd, 0x12, 0x34, 0x56, 0x78, 0x9a, 0x00, 0x01,  // fd12:3456:789a:1::1
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+  };
+  net::DnsResourceRecord answer1;
+  answer1.name = "www.example.com";
+  answer1.type = dns_protocol::kTypeA;
+  answer1.klass = dns_protocol::kClassIN;
+  answer1.ttl = 120;  // 120 seconds.
+  answer1.rdata = base::StringPiece("\xc0\xa8\x00\x01", 4);
+  net::DnsResourceRecord answer2;
+  answer2.name = "example.org";
+  answer2.type = dns_protocol::kTypeAAAA;
+  answer2.klass = dns_protocol::kClassIN;
+  answer2.ttl = 60;
+  answer2.rdata = base::StringPiece(
+      "\xfd\x12\x34\x56\x78\x9a\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01", 16);
+  std::vector<DnsResourceRecord> answers(2);
+  answers[0] = answer1;
+  answers[1] = answer2;
+  DnsResponse response(0x1234 /* id */, true /* is_authoritative*/, answers,
+                       base::nullopt);
+  ASSERT_NE(nullptr, response.io_buffer());
+  EXPECT_TRUE(response.IsValid());
+  std::string expected_response(response_data, sizeof(response_data));
+  std::string actual_response(response.io_buffer()->data(),
+                              response.io_buffer_size());
+  EXPECT_EQ(expected_response, actual_response);
+}
+
+TEST(DnsResponseWriteTest, WrittenResponseCanBeParsed) {
+  net::DnsResourceRecord answer;
+  answer.name = "www.example.com";
+  answer.type = dns_protocol::kTypeA;
+  answer.klass = dns_protocol::kClassIN;
+  answer.ttl = 120;  // 120 seconds.
+  answer.rdata = base::StringPiece("\xc0\xa8\x00\x01", 4);
+  std::vector<DnsResourceRecord> answers(1, answer);
+  DnsResponse response(0x1234 /* response_id */, true /* is_authoritative*/,
+                       answers, base::nullopt);
+  ASSERT_NE(nullptr, response.io_buffer());
+  EXPECT_TRUE(response.IsValid());
+  auto parser = response.Parser();
+  net::DnsResourceRecord parsed_record;
+  EXPECT_TRUE(parser.ReadRecord(&parsed_record));
+  EXPECT_EQ(answer.name, parsed_record.name);
+  EXPECT_EQ(answer.type, parsed_record.type);
+  EXPECT_EQ(answer.klass, parsed_record.klass);
+  EXPECT_EQ(answer.ttl, parsed_record.ttl);
+  EXPECT_EQ(answer.rdata, parsed_record.rdata);
+}
+
 }  // namespace
 
 }  // namespace net
diff --git a/net/dns/dns_transaction.cc b/net/dns/dns_transaction.cc
index a88449f..228a35b 100644
--- a/net/dns/dns_transaction.cc
+++ b/net/dns/dns_transaction.cc
@@ -524,7 +524,7 @@
     buffer_->set_offset(0);
     if (size == 0u)
       return ERR_DNS_MALFORMED_RESPONSE;
-    response_ = std::make_unique<DnsResponse>(buffer_.get(), size + 1);
+    response_ = std::make_unique<DnsResponse>(buffer_, size + 1);
     if (!response_->InitParse(size, *query_))
       return ERR_DNS_MALFORMED_RESPONSE;
     if (response_->rcode() == dns_protocol::kRcodeNXDOMAIN)
diff --git a/net/dns/record_rdata.cc b/net/dns/record_rdata.cc
index c8f86048..86991b2 100644
--- a/net/dns/record_rdata.cc
+++ b/net/dns/record_rdata.cc
@@ -16,6 +16,26 @@
 
 RecordRdata::RecordRdata() = default;
 
+bool RecordRdata::HasValidSize(const base::StringPiece& data, uint16_t type) {
+  switch (type) {
+    case dns_protocol::kTypeSRV:
+      return data.size() >= kSrvRecordMinimumSize;
+    case dns_protocol::kTypeA:
+      return data.size() == IPAddress::kIPv4AddressSize;
+    case dns_protocol::kTypeAAAA:
+      return data.size() == IPAddress::kIPv6AddressSize;
+    case dns_protocol::kTypeCNAME:
+    case dns_protocol::kTypePTR:
+    case dns_protocol::kTypeTXT:
+    case dns_protocol::kTypeNSEC:
+    case dns_protocol::kTypeOPT:
+      return true;
+    default:
+      VLOG(1) << "Unsupported RDATA type.";
+      return false;
+  }
+}
+
 SrvRecordRdata::SrvRecordRdata() : priority_(0), weight_(0), port_(0) {
 }
 
@@ -25,7 +45,7 @@
 std::unique_ptr<SrvRecordRdata> SrvRecordRdata::Create(
     const base::StringPiece& data,
     const DnsRecordParser& parser) {
-  if (data.size() < kSrvRecordMinimumSize)
+  if (!HasValidSize(data, kType))
     return std::unique_ptr<SrvRecordRdata>();
 
   std::unique_ptr<SrvRecordRdata> rdata(new SrvRecordRdata);
@@ -64,7 +84,7 @@
 std::unique_ptr<ARecordRdata> ARecordRdata::Create(
     const base::StringPiece& data,
     const DnsRecordParser& parser) {
-  if (data.size() != IPAddress::kIPv4AddressSize)
+  if (!HasValidSize(data, kType))
     return std::unique_ptr<ARecordRdata>();
 
   std::unique_ptr<ARecordRdata> rdata(new ARecordRdata);
@@ -91,7 +111,7 @@
 std::unique_ptr<AAAARecordRdata> AAAARecordRdata::Create(
     const base::StringPiece& data,
     const DnsRecordParser& parser) {
-  if (data.size() != IPAddress::kIPv6AddressSize)
+  if (!HasValidSize(data, kType))
     return std::unique_ptr<AAAARecordRdata>();
 
   std::unique_ptr<AAAARecordRdata> rdata(new AAAARecordRdata);
diff --git a/net/dns/record_rdata.h b/net/dns/record_rdata.h
index dab5bf0b..4e886a1 100644
--- a/net/dns/record_rdata.h
+++ b/net/dns/record_rdata.h
@@ -30,6 +30,10 @@
  public:
   virtual ~RecordRdata() {}
 
+  // Return true if |data| represents RDATA in the wire format with a valid size
+  // for the give |type|.
+  static bool HasValidSize(const base::StringPiece& data, uint16_t type);
+
   virtual bool IsEqual(const RecordRdata* other) const = 0;
   virtual uint16_t Type() const = 0;
 
diff --git a/net/socket/udp_socket_unittest.cc b/net/socket/udp_socket_unittest.cc
index e51998c..79c32f7 100644
--- a/net/socket/udp_socket_unittest.cc
+++ b/net/socket/udp_socket_unittest.cc
@@ -54,6 +54,18 @@
 
 namespace {
 
+// Creates an address from ip address and port and writes it to |*address|.
+bool CreateUDPAddress(const std::string& ip_str,
+                      uint16_t port,
+                      IPEndPoint* address) {
+  IPAddress ip_address;
+  if (!ip_address.AssignFromIPLiteral(ip_str))
+    return false;
+
+  *address = IPEndPoint(ip_address, port);
+  return true;
+}
+
 class UDPSocketTest : public PlatformTest, public WithScopedTaskEnvironment {
  public:
   UDPSocketTest() : buffer_(base::MakeRefCounted<IOBufferWithSize>(kMaxRead)) {}
@@ -114,14 +126,15 @@
     WriteSocket(socket, msg);
   }
 
-  // Creates an address from ip address and port and writes it to |*address|.
-  void CreateUDPAddress(const std::string& ip_str,
-                        uint16_t port,
-                        IPEndPoint* address) {
-    IPAddress ip_address;
-    if (!ip_address.AssignFromIPLiteral(ip_str))
-      return;
-    *address = IPEndPoint(ip_address, port);
+  // And again for a bare socket
+  int SendToSocket(UDPSocket* socket,
+                   std::string msg,
+                   const IPEndPoint& address) {
+    scoped_refptr<StringIOBuffer> io_buffer = new StringIOBuffer(msg);
+    TestCompletionCallback callback;
+    int rv = socket->SendTo(io_buffer.get(), io_buffer->size(), address,
+                            callback.callback());
+    return callback.GetResult(rv);
   }
 
   // Run unit test for a connection test.
@@ -313,9 +326,9 @@
   std::string first_message("first message"), second_message("second message");
 
   IPEndPoint broadcast_address;
-  CreateUDPAddress("255.255.255.255", kPort, &broadcast_address);
+  ASSERT_TRUE(CreateUDPAddress("255.255.255.255", kPort, &broadcast_address));
   IPEndPoint listen_address;
-  CreateUDPAddress("0.0.0.0", kPort, &listen_address);
+  ASSERT_TRUE(CreateUDPAddress("0.0.0.0", kPort, &listen_address));
 
   TestNetLog server1_log, server2_log;
   std::unique_ptr<UDPServerSocket> server1(
@@ -584,7 +597,7 @@
 TEST_F(UDPSocketTest, ServerSetDoNotFragment) {
   for (std::string ip : {"127.0.0.1", "::1"}) {
     IPEndPoint bind_address;
-    CreateUDPAddress(ip, 0, &bind_address);
+    ASSERT_TRUE(CreateUDPAddress(ip, 0, &bind_address));
     UDPServerSocket server(nullptr, NetLogSource());
     int rv = server.Listen(bind_address);
     // May fail on IPv6 is IPv6 is not configure
@@ -633,7 +646,7 @@
   const char kGroup[] = "237.132.100.17";
 
   IPEndPoint bind_address;
-  CreateUDPAddress("0.0.0.0", kPort, &bind_address);
+  ASSERT_TRUE(CreateUDPAddress("0.0.0.0", kPort, &bind_address));
   IPAddress group_ip;
   EXPECT_TRUE(group_ip.AssignFromIPLiteral(kGroup));
 
@@ -665,7 +678,7 @@
 TEST_F(UDPSocketTest, MulticastOptions) {
   const uint16_t kPort = 9999;
   IPEndPoint bind_address;
-  CreateUDPAddress("0.0.0.0", kPort, &bind_address);
+  ASSERT_TRUE(CreateUDPAddress("0.0.0.0", kPort, &bind_address));
 
   UDPSocket socket(DatagramSocket::DEFAULT_BIND, nullptr, NetLogSource());
   // Before binding.
@@ -693,7 +706,7 @@
   IPEndPoint bind_address;
   UDPSocket client(DatagramSocket::DEFAULT_BIND, nullptr, NetLogSource());
   // We need a real IP, but we won't actually send anything to it.
-  CreateUDPAddress("8.8.8.8", 9999, &bind_address);
+  ASSERT_TRUE(CreateUDPAddress("8.8.8.8", 9999, &bind_address));
   int rv = client.Open(bind_address.GetFamily());
   EXPECT_THAT(rv, IsOk());
 
@@ -771,120 +784,255 @@
 namespace {
 
 const HANDLE kFakeHandle = (HANDLE)19;
-const QOS_FLOWID kFakeFlowId = (QOS_FLOWID)27;
+const QOS_FLOWID kFakeFlowId1 = (QOS_FLOWID)27;
+const QOS_FLOWID kFakeFlowId2 = (QOS_FLOWID)38;
 
-BOOL WINAPI FakeQOSCreateHandleFAIL(PQOS_VERSION version, PHANDLE handle) {
-  EXPECT_EQ(0, version->MinorVersion);
-  EXPECT_EQ(1, version->MajorVersion);
-  SetLastError(ERROR_OPEN_FAILED);
-  return false;
+class TestUDPSocketWin : public UDPSocketWin {
+ public:
+  TestUDPSocketWin(QwaveAPI& qos,
+                   DatagramSocket::BindType bind_type,
+                   net::NetLog* net_log,
+                   const net::NetLogSource& source)
+      : UDPSocketWin(bind_type, net_log, source), qos_(qos) {}
+
+  // Overriding GetQwaveAPI causes the test class to use the injected mock
+  // QwaveAPI instance instead of the singleton.  Ensure close is called in the
+  // child destructor before our mock CloseHandle is uninstalled.
+  ~TestUDPSocketWin() override { UDPSocketWin::Close(); }
+
+  QwaveAPI& GetQwaveAPI() override { return qos_; }
+
+ private:
+  QwaveAPI& qos_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestUDPSocketWin);
+};
+
+class MockQwaveAPI : public QwaveAPI {
+ public:
+  MOCK_METHOD2(CreateHandle, BOOL(PQOS_VERSION version, PHANDLE handle));
+  MOCK_METHOD1(CloseHandle, BOOL(HANDLE handle));
+
+  MOCK_METHOD6(AddSocketToFlow,
+               BOOL(HANDLE handle,
+                    SOCKET socket,
+                    PSOCKADDR addr,
+                    QOS_TRAFFIC_TYPE traffic_type,
+                    DWORD flags,
+                    PQOS_FLOWID flow_id));
+
+  MOCK_METHOD4(
+      RemoveSocketFromFlow,
+      BOOL(HANDLE handle, SOCKET socket, QOS_FLOWID flow_id, DWORD reserved));
+  MOCK_METHOD7(SetFlow,
+               BOOL(HANDLE handle,
+                    QOS_FLOWID flow_id,
+                    QOS_SET_FLOW op,
+                    ULONG size,
+                    PVOID data,
+                    DWORD reserved,
+                    LPOVERLAPPED overlapped));
+};
+
+std::unique_ptr<UDPSocket> OpenedDscpTestClient(QwaveAPI& qos,
+                                                IPEndPoint bind_address) {
+  auto client = std::make_unique<TestUDPSocketWin>(
+      qos, DatagramSocket::DEFAULT_BIND, nullptr, NetLogSource());
+  int rv = client->Open(bind_address.GetFamily());
+  EXPECT_THAT(rv, IsOk());
+
+  return client;
 }
 
-BOOL WINAPI FakeQOSCreateHandle(PQOS_VERSION version, PHANDLE handle) {
-  EXPECT_EQ(0, version->MinorVersion);
-  EXPECT_EQ(1, version->MajorVersion);
-  *handle = kFakeHandle;
-  return true;
+std::unique_ptr<UDPSocket> ConnectedDscpTestClient(QwaveAPI& qos) {
+  IPEndPoint bind_address;
+  // We need a real IP, but we won't actually send anything to it.
+  EXPECT_TRUE(CreateUDPAddress("8.8.8.8", 9999, &bind_address));
+  auto client = OpenedDscpTestClient(qos, bind_address);
+  EXPECT_THAT(client->Connect(bind_address), IsOk());
+  return client;
 }
 
-BOOL WINAPI FakeQOSCloseHandle(HANDLE handle) {
-  EXPECT_EQ(kFakeHandle, handle);
-  return true;
-}
-
-QOS_TRAFFIC_TYPE g_expected_traffic_type;
-
-BOOL WINAPI FakeQOSAddSocketToFlow(HANDLE handle,
-                                   SOCKET socket,
-                                   PSOCKADDR addr,
-                                   QOS_TRAFFIC_TYPE traffic_type,
-                                   DWORD flags,
-                                   PQOS_FLOWID flow_id) {
-  EXPECT_EQ(kFakeHandle, handle);
-  EXPECT_EQ(NULL, addr);
-  EXPECT_EQ(static_cast<DWORD>(QOS_NON_ADAPTIVE_FLOW), flags);
-  EXPECT_EQ(0u, *flow_id);
-  *flow_id = kFakeFlowId;
-  return true;
-}
-
-BOOL WINAPI FakeQOSRemoveSocketFromFlow(HANDLE handle,
-                                        SOCKET socket,
-                                        QOS_FLOWID flowid,
-                                        DWORD reserved) {
-  EXPECT_EQ(kFakeHandle, handle);
-  EXPECT_EQ(0u, socket);
-  EXPECT_EQ(kFakeFlowId, flowid);
-  EXPECT_EQ(0u, reserved);
-  return true;
-}
-
-DWORD g_expected_dscp;
-
-BOOL WINAPI FakeQOSSetFlow(HANDLE handle,
-                           QOS_FLOWID flow_id,
-                           QOS_SET_FLOW op,
-                           ULONG size,
-                           PVOID data,
-                           DWORD reserved,
-                           LPOVERLAPPED overlapped) {
-  EXPECT_EQ(kFakeHandle, handle);
-  EXPECT_EQ(QOSSetOutgoingDSCPValue, op);
-  EXPECT_EQ(sizeof(DWORD), size);
-  EXPECT_EQ(g_expected_dscp, *reinterpret_cast<DWORD*>(data));
-  EXPECT_EQ(kFakeFlowId, flow_id);
-  EXPECT_EQ(0u, reserved);
-  EXPECT_EQ(NULL, overlapped);
-  return true;
+std::unique_ptr<UDPSocket> UnconnectedDscpTestClient(QwaveAPI& qos) {
+  IPEndPoint bind_address;
+  EXPECT_TRUE(CreateUDPAddress("0.0.0.0", 9999, &bind_address));
+  auto client = OpenedDscpTestClient(qos, bind_address);
+  EXPECT_THAT(client->Bind(bind_address), IsOk());
+  return client;
 }
 
 }  // namespace
 
-// Mock out the Qwave functions and make sure they are
-// called correctly. Must be in net namespace for friendship
-// reasons.
-TEST_F(UDPSocketTest, SetDSCPFake) {
-  // Setup the server to listen.
-  IPEndPoint bind_address;
-  // We need a real IP, but we won't actually send anything to it.
-  CreateUDPAddress("8.8.8.8", 9999, &bind_address);
-  UDPSocket client(DatagramSocket::DEFAULT_BIND, nullptr, NetLogSource());
-  int rv = client.SetDiffServCodePoint(DSCP_AF41);
-  EXPECT_THAT(rv, IsError(ERR_SOCKET_NOT_CONNECTED));
+using ::testing::_;
+using ::testing::Return;
+using ::testing::SetArgPointee;
 
-  rv = client.Open(bind_address.GetFamily());
-  EXPECT_THAT(rv, IsOk());
+TEST_F(UDPSocketTest, SetDSCPNoopIfPassedNoChange) {
+  MockQwaveAPI qos;
+  std::unique_ptr<UDPSocket> client = ConnectedDscpTestClient(qos);
+  EXPECT_THAT(client->SetDiffServCodePoint(DSCP_NO_CHANGE), IsOk());
+}
 
-  rv = client.Connect(bind_address);
-  EXPECT_THAT(rv, IsOk());
+TEST_F(UDPSocketTest, SetDSCPFailsIfQOSHandleCanNotBeCreated) {
+  MockQwaveAPI qos;
+  std::unique_ptr<UDPSocket> client = ConnectedDscpTestClient(qos);
 
-  QwaveAPI& qos(QwaveAPI::Get());
-  qos.create_handle_func_ = FakeQOSCreateHandleFAIL;
-  qos.close_handle_func_ = FakeQOSCloseHandle;
-  qos.add_socket_to_flow_func_ = FakeQOSAddSocketToFlow;
-  qos.remove_socket_from_flow_func_ = FakeQOSRemoveSocketFromFlow;
-  qos.set_flow_func_ = FakeQOSSetFlow;
-  qos.qwave_supported_ = true;
+  EXPECT_CALL(qos, CreateHandle(_, _)).WillOnce(Return(false));
+  EXPECT_EQ(ERROR_NOT_SUPPORTED, client->SetDiffServCodePoint(DSCP_AF41));
+}
 
-  EXPECT_THAT(client.SetDiffServCodePoint(DSCP_NO_CHANGE), IsOk());
-  EXPECT_EQ(ERROR_NOT_SUPPORTED, client.SetDiffServCodePoint(DSCP_AF41));
-  qos.create_handle_func_ = FakeQOSCreateHandle;
-  g_expected_dscp = DSCP_AF41;
-  g_expected_traffic_type = QOSTrafficTypeAudioVideo;
-  EXPECT_THAT(client.SetDiffServCodePoint(DSCP_AF41), IsOk());
-  g_expected_dscp = DSCP_DEFAULT;
-  g_expected_traffic_type = QOSTrafficTypeBestEffort;
-  EXPECT_THAT(client.SetDiffServCodePoint(DSCP_DEFAULT), IsOk());
-  g_expected_dscp = DSCP_CS2;
-  g_expected_traffic_type = QOSTrafficTypeExcellentEffort;
-  EXPECT_THAT(client.SetDiffServCodePoint(DSCP_CS2), IsOk());
-  g_expected_dscp = DSCP_CS3;
-  g_expected_traffic_type = QOSTrafficTypeExcellentEffort;
-  EXPECT_THAT(client.SetDiffServCodePoint(DSCP_NO_CHANGE), IsOk());
-  g_expected_dscp = DSCP_DEFAULT;
-  g_expected_traffic_type = QOSTrafficTypeBestEffort;
-  EXPECT_THAT(client.SetDiffServCodePoint(DSCP_DEFAULT), IsOk());
-  client.Close();
+MATCHER_P(DscpPointee, dscp, "") {
+  return *(DWORD*)arg == (DWORD)dscp;
+}
+
+TEST_F(UDPSocketTest, SetDSCPCallsQwaveFunctions) {
+  MockQwaveAPI qos;
+  std::unique_ptr<UDPSocket> client = ConnectedDscpTestClient(qos);
+
+  EXPECT_CALL(qos, CreateHandle(_, _))
+      .WillOnce(DoAll(SetArgPointee<1>(kFakeHandle), Return(true)));
+  // AddSocketToFlow also sets flow_id, but we don't use that here
+  EXPECT_CALL(qos, AddSocketToFlow(_, _, _, QOSTrafficTypeAudioVideo, _, _))
+      .WillOnce(Return(true));
+  EXPECT_CALL(qos, SetFlow(_, _, QOSSetOutgoingDSCPValue, _,
+                           DscpPointee(DSCP_AF41), _, _));
+  EXPECT_THAT(client->SetDiffServCodePoint(DSCP_AF41), IsOk());
+  EXPECT_CALL(qos, CloseHandle(kFakeHandle));
+}
+
+TEST_F(UDPSocketTest, SecondSetDSCPCallsQwaveFunctions) {
+  MockQwaveAPI qos;
+  std::unique_ptr<UDPSocket> client = ConnectedDscpTestClient(qos);
+
+  EXPECT_CALL(qos, CreateHandle(_, _))
+      .WillOnce(DoAll(SetArgPointee<1>(kFakeHandle), Return(true)));
+
+  EXPECT_CALL(qos, AddSocketToFlow(_, _, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<5>(kFakeFlowId1), Return(true)));
+  EXPECT_CALL(qos, SetFlow(_, _, _, _, _, _, _));
+  EXPECT_THAT(client->SetDiffServCodePoint(DSCP_AF41), IsOk());
+
+  // New dscp value should reset the flow.
+  EXPECT_CALL(qos, RemoveSocketFromFlow(_, _, _, _));
+  EXPECT_CALL(qos, AddSocketToFlow(_, _, _, QOSTrafficTypeBestEffort, _, _))
+      .WillOnce(DoAll(SetArgPointee<5>(kFakeFlowId2), Return(true)));
+  EXPECT_CALL(qos, SetFlow(_, _, QOSSetOutgoingDSCPValue, _,
+                           DscpPointee(DSCP_DEFAULT), _, _));
+  EXPECT_THAT(client->SetDiffServCodePoint(DSCP_DEFAULT), IsOk());
+
+  // Called from DscpManager destructor.
+  EXPECT_CALL(qos, RemoveSocketFromFlow(_, _, _, _));
+  EXPECT_CALL(qos, CloseHandle(kFakeHandle));
+}
+
+// TODO(zstein): Mocking out DscpManager might be simpler here
+// (just verify that DscpManager::Set and DscpManager::PrepareForSend are
+// called).
+TEST_F(UDPSocketTest, SendToCallsQwaveApis) {
+  MockQwaveAPI qos;
+  std::unique_ptr<UDPSocket> client = UnconnectedDscpTestClient(qos);
+
+  EXPECT_CALL(qos, CreateHandle(_, _))
+      .WillOnce(DoAll(SetArgPointee<1>(kFakeHandle), Return(true)));
+  EXPECT_THAT(client->SetDiffServCodePoint(DSCP_AF41), IsOk());
+
+  EXPECT_CALL(qos, AddSocketToFlow(_, _, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<5>(kFakeFlowId1), Return(true)));
+  EXPECT_CALL(qos, SetFlow(_, _, _, _, _, _, _));
+
+  std::string simple_message("hello world");
+  IPEndPoint server_address(IPAddress::IPv4Localhost(), 9438);
+  int rv = SendToSocket(client.get(), simple_message, server_address);
+  EXPECT_EQ(simple_message.length(), static_cast<size_t>(rv));
+
+  // TODO(zstein): Move to second test case (Qwave APIs called once per address)
+  rv = SendToSocket(client.get(), simple_message, server_address);
+  EXPECT_EQ(simple_message.length(), static_cast<size_t>(rv));
+
+  // TODO(zstein): Move to third test case (Qwave APIs called for each
+  // destination address).
+  EXPECT_CALL(qos, AddSocketToFlow(_, _, _, _, _, _)).WillOnce(Return(true));
+  IPEndPoint server_address2(IPAddress::IPv4Localhost(), 9439);
+
+  rv = SendToSocket(client.get(), simple_message, server_address2);
+  EXPECT_EQ(simple_message.length(), static_cast<size_t>(rv));
+
+  // Called from DscpManager destructor.
+  EXPECT_CALL(qos, RemoveSocketFromFlow(_, _, _, _));
+  EXPECT_CALL(qos, CloseHandle(kFakeHandle));
+}
+
+class DscpManagerTest : public testing::Test {
+ protected:
+  DscpManagerTest() : dscp_manager_(qos_, INVALID_SOCKET, (HANDLE)0) {
+    CreateUDPAddress("1.2.3.4", 9001, &address1_);
+    CreateUDPAddress("1234:5678:90ab:cdef:1234:5678:90ab:cdef", 9002,
+                     &address2_);
+  }
+
+  MockQwaveAPI qos_;
+  DscpManager dscp_manager_;
+
+  IPEndPoint address1_;
+  IPEndPoint address2_;
+};
+
+TEST_F(DscpManagerTest, PrepareForSendIsNoopIfNoSet) {
+  dscp_manager_.PrepareForSend(address1_);
+}
+
+TEST_F(DscpManagerTest, PrepareForSendCallsQwaveApisAfterSet) {
+  dscp_manager_.Set(DSCP_CS2);
+
+  // AddSocketToFlow should be called for each address.
+  EXPECT_CALL(qos_, AddSocketToFlow(_, _, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<5>(kFakeFlowId1), Return(true)))
+      .WillOnce(Return(true));
+  // SetFlow should only be called when the flow is first created.
+  EXPECT_CALL(qos_, SetFlow(_, _, _, _, _, _, _));
+  dscp_manager_.PrepareForSend(address1_);
+  EXPECT_CALL(qos_, SetFlow(_, _, _, _, _, _, _)).Times(0);
+  dscp_manager_.PrepareForSend(address2_);
+
+  // Called from DscpManager destructor.
+  EXPECT_CALL(qos_, RemoveSocketFromFlow(_, _, _, _));
+}
+
+TEST_F(DscpManagerTest, PrepareForSendCallsQwaveApisOncePerAddress) {
+  dscp_manager_.Set(DSCP_CS2);
+
+  EXPECT_CALL(qos_, AddSocketToFlow(_, _, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<5>(kFakeFlowId1), Return(true)));
+  EXPECT_CALL(qos_, SetFlow(_, _, _, _, _, _, _));
+  dscp_manager_.PrepareForSend(address1_);
+  EXPECT_CALL(qos_, AddSocketToFlow(_, _, _, _, _, _)).Times(0);
+  EXPECT_CALL(qos_, SetFlow(_, _, _, _, _, _, _)).Times(0);
+  dscp_manager_.PrepareForSend(address1_);
+
+  // Called from DscpManager destructor.
+  EXPECT_CALL(qos_, RemoveSocketFromFlow(_, _, _, _));
+}
+
+TEST_F(DscpManagerTest, SetDestroysExistingFlowAndResetsPrepareState) {
+  dscp_manager_.Set(DSCP_CS2);
+  EXPECT_CALL(qos_, AddSocketToFlow(_, _, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<5>(kFakeFlowId1), Return(true)));
+  EXPECT_CALL(qos_, SetFlow(_, _, _, _, _, _, _));
+  dscp_manager_.PrepareForSend(address1_);
+
+  // Calling Set should destroy the existing flow.
+  // TODO(zstein): Verify that RemoveSocketFromFlow with no address
+  // destroys the flow for all destinations.
+  EXPECT_CALL(qos_, RemoveSocketFromFlow(_, NULL, kFakeFlowId1, _));
+  dscp_manager_.Set(DSCP_CS5);
+
+  EXPECT_CALL(qos_, AddSocketToFlow(_, _, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<5>(kFakeFlowId2), Return(true)));
+  EXPECT_CALL(qos_, SetFlow(_, _, _, _, _, _, _));
+  dscp_manager_.PrepareForSend(address1_);
+
+  // Called from DscpManager destructor.
+  EXPECT_CALL(qos_, RemoveSocketFromFlow(_, _, kFakeFlowId2, _));
 }
 #endif
 
diff --git a/net/socket/udp_socket_win.cc b/net/socket/udp_socket_win.cc
index 945d9e8b..9f48f4d 100644
--- a/net/socket/udp_socket_win.cc
+++ b/net/socket/udp_socket_win.cc
@@ -56,7 +56,7 @@
   void WatchForWrite();
 
   // The UDPSocketWin is going away.
-  void Detach() { socket_ = NULL; }
+  void Detach() { socket_ = nullptr; }
 
   // The separate OVERLAPPED variables for asynchronous operation.
   OVERLAPPED read_overlapped_;
@@ -260,7 +260,6 @@
       recv_from_address_(nullptr),
       net_log_(NetLogWithSource::Make(net_log, NetLogSourceType::UDP_SOCKET)),
       qos_handle_(nullptr),
-      qos_flow_id_(0),
       event_pending_(this) {
   EnsureWinsockInit();
   net_log_.BeginEvent(NetLogEventType::SOCKET_ALIVE,
@@ -296,12 +295,15 @@
   if (socket_ == INVALID_SOCKET)
     return;
 
-  if (qos_handle_)
-    QwaveAPI::Get().CloseHandle(qos_handle_);
+  if (qos_handle_) {
+    GetQwaveAPI().CloseHandle(qos_handle_);
+    dscp_manager_ = nullptr;
+    qos_handle_ = NULL;
+  }
 
   // Zero out any pending read/write callback state.
   read_callback_.Reset();
-  recv_from_address_ = NULL;
+  recv_from_address_ = nullptr;
   write_callback_.Reset();
 
   base::TimeTicks start_time = base::TimeTicks::Now();
@@ -325,7 +327,7 @@
 
   if (core_) {
     core_->Detach();
-    core_ = NULL;
+    core_ = nullptr;
   }
 }
 
@@ -378,7 +380,7 @@
 int UDPSocketWin::Read(IOBuffer* buf,
                        int buf_len,
                        CompletionOnceCallback callback) {
-  return RecvFrom(buf, buf_len, NULL, std::move(callback));
+  return RecvFrom(buf, buf_len, nullptr, std::move(callback));
 }
 
 int UDPSocketWin::RecvFrom(IOBuffer* buf,
@@ -415,6 +417,13 @@
                          int buf_len,
                          const IPEndPoint& address,
                          CompletionOnceCallback callback) {
+  if (dscp_manager_) {
+    // Alert DscpManager in case this is a new remote address.  Failure to
+    // apply Dscp code is never fatal.
+    int rv = dscp_manager_->PrepareForSend(address);
+    if (rv != OK)
+      net_log_.AddEventWithNetErrorCode(NetLogEventType::UDP_SEND_ERROR, rv);
+  }
   return SendToOrWrite(buf, buf_len, &address, std::move(callback));
 }
 
@@ -481,6 +490,10 @@
     return MapSystemError(WSAGetLastError());
 
   remote_address_.reset(new IPEndPoint(address));
+
+  if (dscp_manager_)
+    dscp_manager_->PrepareForSend(*remote_address_.get());
+
   return rv;
 }
 
@@ -611,7 +624,7 @@
   int result = ok ? num_bytes : MapSystemError(WSAGetLastError());
   // Convert address.
   IPEndPoint address;
-  IPEndPoint* address_to_log = NULL;
+  IPEndPoint* address_to_log = nullptr;
   if (result >= 0) {
     if (address.FromSockAddr(core_->recv_addr_storage_.addr,
                              core_->recv_addr_storage_.addr_len)) {
@@ -623,8 +636,8 @@
     }
   }
   LogRead(result, core_->read_iobuffer_->data(), address_to_log);
-  core_->read_iobuffer_ = NULL;
-  recv_from_address_ = NULL;
+  core_->read_iobuffer_ = nullptr;
+  recv_from_address_ = nullptr;
   DoReadCallback(result);
 }
 
@@ -637,7 +650,7 @@
   LogWrite(result, core_->write_iobuffer_->data(), send_to_address_.get());
 
   send_to_address_.reset();
-  core_->write_iobuffer_ = NULL;
+  core_->write_iobuffer_ = nullptr;
   DoWriteCallback(result);
 }
 
@@ -691,9 +704,9 @@
                                        recv_from_address_);
   if (rv == ERR_IO_PENDING)
     return;
-  read_iobuffer_ = NULL;
+  read_iobuffer_ = nullptr;
   read_iobuffer_len_ = 0;
-  recv_from_address_ = NULL;
+  recv_from_address_ = nullptr;
   DoReadCallback(rv);
 }
 
@@ -702,7 +715,7 @@
                                      send_to_address_.get());
   if (rv == ERR_IO_PENDING)
     return;
-  write_iobuffer_ = NULL;
+  write_iobuffer_ = nullptr;
   write_iobuffer_len_ = 0;
   send_to_address_.reset();
   DoWriteCallback(rv);
@@ -767,13 +780,13 @@
   CHECK_NE(INVALID_SOCKET, socket_);
   AssertEventNotSignaled(core_->read_overlapped_.hEvent);
   int rv = WSARecvFrom(socket_, &read_buffer, 1, &num, &flags, storage.addr,
-                       &storage.addr_len, &core_->read_overlapped_, NULL);
+                       &storage.addr_len, &core_->read_overlapped_, nullptr);
   if (rv == 0) {
     if (ResetEventIfSignaled(core_->read_overlapped_.hEvent)) {
       int result = num;
       // Convert address.
       IPEndPoint address_storage;
-      IPEndPoint* address_to_log = NULL;
+      IPEndPoint* address_to_log = nullptr;
       if (result >= 0) {
         if (address_storage.FromSockAddr(core_->recv_addr_storage_.addr,
                                          core_->recv_addr_storage_.addr_len)) {
@@ -791,7 +804,7 @@
     int os_error = WSAGetLastError();
     if (os_error != WSA_IO_PENDING) {
       int result = MapSystemError(os_error);
-      LogRead(result, NULL, NULL);
+      LogRead(result, nullptr, nullptr);
       return result;
     }
   }
@@ -808,12 +821,12 @@
   struct sockaddr* addr = storage.addr;
   // Convert address.
   if (!address) {
-    addr = NULL;
+    addr = nullptr;
     storage.addr_len = 0;
   } else {
     if (!address->ToSockAddr(addr, &storage.addr_len)) {
       int result = ERR_ADDRESS_INVALID;
-      LogWrite(result, NULL, NULL);
+      LogWrite(result, nullptr, nullptr);
       return result;
     }
   }
@@ -825,8 +838,8 @@
   DWORD flags = 0;
   DWORD num;
   AssertEventNotSignaled(core_->write_overlapped_.hEvent);
-  int rv = WSASendTo(socket_, &write_buffer, 1, &num, flags,
-                     addr, storage.addr_len, &core_->write_overlapped_, NULL);
+  int rv = WSASendTo(socket_, &write_buffer, 1, &num, flags, addr,
+                     storage.addr_len, &core_->write_overlapped_, nullptr);
   if (rv == 0) {
     if (ResetEventIfSignaled(core_->write_overlapped_.hEvent)) {
       int result = num;
@@ -837,7 +850,7 @@
     int os_error = WSAGetLastError();
     if (os_error != WSA_IO_PENDING) {
       int result = MapSystemError(os_error);
-      LogWrite(result, NULL, NULL);
+      LogWrite(result, nullptr, nullptr);
       return result;
     }
   }
@@ -866,11 +879,11 @@
       return ERR_IO_PENDING;
     }
     rv = MapSystemError(os_error);
-    LogRead(rv, NULL, NULL);
+    LogRead(rv, nullptr, nullptr);
     return rv;
   }
   IPEndPoint address_storage;
-  IPEndPoint* address_to_log = NULL;
+  IPEndPoint* address_to_log = nullptr;
   if (rv >= 0) {
     if (address_storage.FromSockAddr(storage.addr, storage.addr_len)) {
       if (address)
@@ -894,11 +907,11 @@
   if (address) {
     if (!address->ToSockAddr(addr, &storage.addr_len)) {
       int result = ERR_ADDRESS_INVALID;
-      LogWrite(result, NULL, NULL);
+      LogWrite(result, nullptr, nullptr);
       return result;
     }
   } else {
-    addr = NULL;
+    addr = nullptr;
     storage.addr_len = 0;
   }
 
@@ -912,7 +925,7 @@
       return ERR_IO_PENDING;
     }
     rv = MapSystemError(os_error);
-    LogWrite(rv, NULL, NULL);
+    LogWrite(rv, nullptr, nullptr);
     return rv;
   }
   LogWrite(rv, buf->data(), address);
@@ -1002,6 +1015,10 @@
   return DoBind(IPEndPoint(address, 0));
 }
 
+QwaveAPI& UDPSocketWin::GetQwaveAPI() {
+  return QwaveAPI::Get();
+}
+
 int UDPSocketWin::JoinGroup(const IPAddress& group_address) const {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   if (!is_connected())
@@ -1111,28 +1128,7 @@
   return OK;
 }
 
-int UDPSocketWin::SetDiffServCodePoint(DiffServCodePoint dscp) {
-  if (dscp == DSCP_NO_CHANGE) {
-    return OK;
-  }
-
-  if (!is_connected())
-    return ERR_SOCKET_NOT_CONNECTED;
-
-  QwaveAPI& qos(QwaveAPI::Get());
-
-  if (!qos.qwave_supported())
-    return ERROR_NOT_SUPPORTED;
-
-  if (qos_handle_ == NULL) {
-    QOS_VERSION version;
-    version.MajorVersion = 1;
-    version.MinorVersion = 0;
-    qos.CreateHandle(&version, &qos_handle_);
-    if (qos_handle_ == NULL)
-      return ERROR_NOT_SUPPORTED;
-  }
-
+QOS_TRAFFIC_TYPE DscpToTrafficType(DiffServCodePoint dscp) {
   QOS_TRAFFIC_TYPE traffic_type = QOSTrafficTypeBestEffort;
   switch (dscp) {
     case DSCP_CS0:
@@ -1172,34 +1168,36 @@
       NOTREACHED();
       break;
   }
-  if (qos_flow_id_ != 0) {
-    qos.RemoveSocketFromFlow(qos_handle_, NULL, qos_flow_id_, 0);
-    qos_flow_id_ = 0;
+  return traffic_type;
+}
+
+int UDPSocketWin::SetDiffServCodePoint(DiffServCodePoint dscp) {
+  if (dscp == DSCP_NO_CHANGE)
+    return OK;
+
+  if (!is_connected())
+    return ERR_SOCKET_NOT_CONNECTED;
+
+  QwaveAPI& qos(GetQwaveAPI());
+
+  if (!qos.qwave_supported())
+    return ERROR_NOT_SUPPORTED;
+
+  if (!qos_handle_) {
+    QOS_VERSION version;
+    version.MajorVersion = 1;
+    version.MinorVersion = 0;
+    qos.CreateHandle(&version, &qos_handle_);
+    if (!qos_handle_)
+      return ERROR_NOT_SUPPORTED;
   }
-  if (!qos.AddSocketToFlow(qos_handle_,
-                           socket_,
-                           NULL,
-                           traffic_type,
-                           QOS_NON_ADAPTIVE_FLOW,
-                           &qos_flow_id_)) {
-    DWORD err = GetLastError();
-    if (err == ERROR_DEVICE_REINITIALIZATION_NEEDED) {
-      qos.CloseHandle(qos_handle_);
-      qos_flow_id_ = 0;
-      qos_handle_ = 0;
-    }
-    return MapSystemError(err);
-  }
-  // This requires admin rights, and may fail, if so we ignore it
-  // as AddSocketToFlow should still do *approximately* the right thing.
-  DWORD buf = dscp;
-  qos.SetFlow(qos_handle_,
-              qos_flow_id_,
-              QOSSetOutgoingDSCPValue,
-              sizeof(buf),
-              &buf,
-              0,
-              NULL);
+
+  if (!dscp_manager_)
+    dscp_manager_ = std::make_unique<DscpManager>(qos, socket_, qos_handle_);
+
+  dscp_manager_->Set(dscp);
+  if (remote_address_)
+    return dscp_manager_->PrepareForSend(*remote_address_.get());
 
   return OK;
 }
@@ -1250,5 +1248,70 @@
   NOTIMPLEMENTED();
   return result;
 }
+DscpManager::DscpManager(QwaveAPI& qos, SOCKET socket, HANDLE qos_handle)
+    : qos_(qos), socket_(socket), qos_handle_(qos_handle) {}
+
+DscpManager::~DscpManager() {
+  if (flow_id_ != 0)
+    qos_.RemoveSocketFromFlow(qos_handle_, NULL, flow_id_, 0);
+}
+
+void DscpManager::Set(DiffServCodePoint dscp) {
+  if (dscp == DSCP_NO_CHANGE || dscp == dscp_value_)
+    return;
+
+  dscp_value_ = dscp;
+  // TODO(zstein): We could reuse the flow when the value changes
+  // by calling QOSSetFlow with the new traffic type and dscp value.
+  if (flow_id_ != 0) {
+    qos_.RemoveSocketFromFlow(qos_handle_, NULL, flow_id_, 0);
+    configured_.clear();
+    flow_id_ = 0;
+  }
+}
+
+int DscpManager::PrepareForSend(const IPEndPoint& remote_address) {
+  if (dscp_value_ == DSCP_NO_CHANGE) {
+    // No DSCP value has been set.
+    return OK;
+  }
+
+  if (configured_.find(remote_address) != configured_.end())
+    return OK;
+
+  SockaddrStorage storage;
+  if (!remote_address.ToSockAddr(storage.addr, &storage.addr_len))
+    return ERR_ADDRESS_INVALID;
+
+  // We won't try again if we get an error.
+  configured_.emplace(remote_address);
+
+  // We don't need to call SetFlow if we already have a qos flow.
+  bool new_flow = flow_id_ == 0;
+
+  const QOS_TRAFFIC_TYPE traffic_type = DscpToTrafficType(dscp_value_);
+
+  if (!qos_.AddSocketToFlow(qos_handle_, socket_, storage.addr, traffic_type,
+                            QOS_NON_ADAPTIVE_FLOW, &flow_id_)) {
+    DWORD err = GetLastError();
+    if (err == ERROR_DEVICE_REINITIALIZATION_NEEDED) {
+      qos_.CloseHandle(qos_handle_);
+      flow_id_ = 0;
+      qos_handle_ = 0;
+      dscp_value_ = DSCP_NO_CHANGE;
+    }
+    return MapSystemError(err);
+  }
+
+  if (new_flow) {
+    DWORD buf = dscp_value_;
+    // This requires admin rights, and may fail, if so we ignore it
+    // as AddSocketToFlow should still do *approximately* the right thing.
+    qos_.SetFlow(qos_handle_, flow_id_, QOSSetOutgoingDSCPValue, sizeof(buf),
+                 &buf, 0, nullptr);
+  }
+
+  return OK;
+}
 
 }  // namespace net
diff --git a/net/socket/udp_socket_win.h b/net/socket/udp_socket_win.h
index 81cd245f..8240fc86 100644
--- a/net/socket/udp_socket_win.h
+++ b/net/socket/udp_socket_win.h
@@ -37,6 +37,110 @@
 struct NetLogSource;
 class SocketTag;
 
+// QWAVE (Quality Windows Audio/Video Experience) is the latest windows
+// library for setting packet priorities (and other things). Unfortunately,
+// Microsoft has decided that setting the DSCP bits with setsockopt() no
+// longer works, so we have to use this API instead.
+// This class is meant to be used as a singleton. It exposes a few dynamically
+// loaded functions and a bool called "qwave_supported".
+class NET_EXPORT QwaveAPI {
+  typedef BOOL(WINAPI* CreateHandleFn)(PQOS_VERSION, PHANDLE);
+  typedef BOOL(WINAPI* CloseHandleFn)(HANDLE);
+  typedef BOOL(WINAPI* AddSocketToFlowFn)(HANDLE,
+                                          SOCKET,
+                                          PSOCKADDR,
+                                          QOS_TRAFFIC_TYPE,
+                                          DWORD,
+                                          PQOS_FLOWID);
+  typedef BOOL(WINAPI* RemoveSocketFromFlowFn)(HANDLE,
+                                               SOCKET,
+                                               QOS_FLOWID,
+                                               DWORD);
+  typedef BOOL(WINAPI* SetFlowFn)(HANDLE,
+                                  QOS_FLOWID,
+                                  QOS_SET_FLOW,
+                                  ULONG,
+                                  PVOID,
+                                  DWORD,
+                                  LPOVERLAPPED);
+
+ public:
+  QwaveAPI();
+  virtual ~QwaveAPI() = default;
+
+  static QwaveAPI& Get();
+
+  virtual bool qwave_supported() const;
+  virtual BOOL CreateHandle(PQOS_VERSION version, PHANDLE handle);
+  virtual BOOL CloseHandle(HANDLE handle);
+  virtual BOOL AddSocketToFlow(HANDLE handle,
+                               SOCKET socket,
+                               PSOCKADDR addr,
+                               QOS_TRAFFIC_TYPE traffic_type,
+                               DWORD flags,
+                               PQOS_FLOWID flow_id);
+  virtual BOOL RemoveSocketFromFlow(HANDLE handle,
+                                    SOCKET socket,
+                                    QOS_FLOWID flow_id,
+                                    DWORD reserved);
+  virtual BOOL SetFlow(HANDLE handle,
+                       QOS_FLOWID flow_id,
+                       QOS_SET_FLOW op,
+                       ULONG size,
+                       PVOID data,
+                       DWORD reserved,
+                       LPOVERLAPPED overlapped);
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(UDPSocketTest, SetDSCPFake);
+
+  bool qwave_supported_;
+  CreateHandleFn create_handle_func_;
+  CloseHandleFn close_handle_func_;
+  AddSocketToFlowFn add_socket_to_flow_func_;
+  RemoveSocketFromFlowFn remove_socket_from_flow_func_;
+  SetFlowFn set_flow_func_;
+
+  DISALLOW_COPY_AND_ASSIGN(QwaveAPI);
+};
+
+//-----------------------------------------------------------------------------
+
+// Helper for maintaining the state that (unlike a blanket socket option), DSCP
+// values are set per-remote endpoint instead of just per-socket on Windows.
+// The implementation creates a single QWAVE 'flow' for the socket, and adds
+// all encountered remote addresses to that flow.  Flows are the minimum
+// manageable unit within the QWAVE API.  See
+// https://docs.microsoft.com/en-us/previous-versions/windows/desktop/api/qos2/
+// for Microsoft's documentation.
+class NET_EXPORT DscpManager {
+ public:
+  DscpManager(QwaveAPI& qos, SOCKET socket, HANDLE qos_handle);
+  ~DscpManager();
+
+  // Remembers the latest |dscp| so PrepareToSend can add remote addresses to
+  // the qos flow. Destroys the old flow if it exists and |dscp| changes.
+  void Set(DiffServCodePoint dscp);
+
+  // Constructs a qos flow for the latest set DSCP value if we don't already
+  // have one. Adds |remote_address| to the qos flow if it hasn't been added
+  // already. Does nothing if no DSCP value has been Set.
+  int PrepareForSend(const IPEndPoint& remote_address);
+
+ private:
+  QwaveAPI& qos_;
+  SOCKET socket_;
+  HANDLE qos_handle_;
+
+  DiffServCodePoint dscp_value_ = DSCP_NO_CHANGE;
+  // The remote addresses currently in the flow.
+  std::set<IPEndPoint> configured_;
+  // 0 means no flow has been constructed.
+  QOS_FLOWID flow_id_ = 0;
+};
+
+//-----------------------------------------------------------------------------
+
 class NET_EXPORT UDPSocketWin : public base::win::ObjectWatcher::Delegate {
  public:
   UDPSocketWin(DatagramSocket::BindType bind_type,
@@ -285,6 +389,11 @@
   // Binds to a random port on |address|.
   int RandomBind(const IPAddress& address);
 
+  // This is provided to allow QwaveAPI mocking in tests. |UDPSocketWin| method
+  // implementations should call |GetQwaveAPI()| instead of |QwaveAPI::Get()|
+  // directly.
+  virtual QwaveAPI& GetQwaveAPI();
+
   SOCKET socket_;
   int addr_family_;
   bool is_connected_;
@@ -346,7 +455,9 @@
 
   // QWAVE data. Used to set DSCP bits on outgoing packets.
   HANDLE qos_handle_;
-  QOS_FLOWID qos_flow_id_;
+
+  // Maintains remote addresses for QWAVE qos management.
+  std::unique_ptr<DscpManager> dscp_manager_;
 
   THREAD_CHECKER(thread_checker_);
 
@@ -359,60 +470,6 @@
 
 //-----------------------------------------------------------------------------
 
-// QWAVE (Quality Windows Audio/Video Experience) is the latest windows
-// library for setting packet priorities (and other things). Unfortunately,
-// Microsoft has decided that setting the DSCP bits with setsockopt() no
-// longer works, so we have to use this API instead.
-// This class is meant to be used as a singleton. It exposes a few dynamically
-// loaded functions and a bool called "qwave_supported".
-class NET_EXPORT QwaveAPI {
-  typedef BOOL (WINAPI *CreateHandleFn)(PQOS_VERSION, PHANDLE);
-  typedef BOOL (WINAPI *CloseHandleFn)(HANDLE);
-  typedef BOOL (WINAPI *AddSocketToFlowFn)(
-      HANDLE, SOCKET, PSOCKADDR, QOS_TRAFFIC_TYPE, DWORD, PQOS_FLOWID);
-  typedef BOOL (WINAPI *RemoveSocketFromFlowFn)(
-      HANDLE, SOCKET, QOS_FLOWID, DWORD);
-  typedef BOOL (WINAPI *SetFlowFn)(
-      HANDLE, QOS_FLOWID, QOS_SET_FLOW, ULONG, PVOID, DWORD, LPOVERLAPPED);
-
- public:
-  QwaveAPI();
-
-  static QwaveAPI& Get();
-
-  bool qwave_supported() const;
-  BOOL CreateHandle(PQOS_VERSION version, PHANDLE handle);
-  BOOL CloseHandle(HANDLE handle);
-  BOOL AddSocketToFlow(HANDLE handle,
-                       SOCKET socket,
-                       PSOCKADDR addr,
-                       QOS_TRAFFIC_TYPE traffic_type,
-                       DWORD flags,
-                       PQOS_FLOWID flow_id);
-  BOOL RemoveSocketFromFlow(HANDLE handle,
-                            SOCKET socket,
-                            QOS_FLOWID flow_id,
-                            DWORD reserved);
-  BOOL SetFlow(HANDLE handle,
-               QOS_FLOWID flow_id,
-               QOS_SET_FLOW op,
-               ULONG size,
-               PVOID data,
-               DWORD reserved,
-               LPOVERLAPPED overlapped);
-
- private:
-  FRIEND_TEST_ALL_PREFIXES(UDPSocketTest, SetDSCPFake);
-
-  bool qwave_supported_;
-  CreateHandleFn create_handle_func_;
-  CloseHandleFn close_handle_func_;
-  AddSocketToFlowFn add_socket_to_flow_func_;
-  RemoveSocketFromFlowFn remove_socket_from_flow_func_;
-  SetFlowFn set_flow_func_;
-
-  DISALLOW_COPY_AND_ASSIGN(QwaveAPI);
-};
 
 
 }  // namespace net
diff --git a/pdf/pdfium/pdfium_print_unittest.cc b/pdf/pdfium/pdfium_print_unittest.cc
index 2cb4372..f033a08 100644
--- a/pdf/pdfium/pdfium_print_unittest.cc
+++ b/pdf/pdfium/pdfium_print_unittest.cc
@@ -5,29 +5,133 @@
 #include "pdf/pdfium/pdfium_print.h"
 
 #include "base/stl_util.h"
+#include "pdf/pdfium/pdfium_engine.h"
+#include "pdf/pdfium/pdfium_engine_exports.h"
+#include "pdf/pdfium/pdfium_test_base.h"
+#include "pdf/test/test_client.h"
 #include "ppapi/c/dev/ppp_printing_dev.h"
+#include "ppapi/c/private/ppp_pdf.h"
 #include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
 
 namespace chrome_pdf {
 
+using PDFiumPrintTest = PDFiumTestBase;
 using testing::ElementsAre;
 
-TEST(PDFiumPrintTest, GetPageNumbersFromPrintPageNumberRange) {
+namespace {
+
+constexpr PP_Size kUSLetterSize = {612, 792};
+constexpr PP_Rect kUSLetterRect = {{0, 0}, kUSLetterSize};
+
+struct SizeDouble {
+  double width;
+  double height;
+};
+
+using ExpectedDimensions = std::vector<SizeDouble>;
+
+void CheckPdfDimensions(const std::vector<uint8_t>& pdf_data,
+                        const ExpectedDimensions& expected_dimensions) {
+  PDFiumEngineExports exports;
+  int page_count;
+  ASSERT_TRUE(exports.GetPDFDocInfo(pdf_data, &page_count, nullptr));
+  ASSERT_GT(page_count, 0);
+  ASSERT_EQ(expected_dimensions.size(), static_cast<size_t>(page_count));
+
+  for (int i = 0; i < page_count; ++i) {
+    double width;
+    double height;
+    ASSERT_TRUE(exports.GetPDFPageSizeByIndex(pdf_data, i, &width, &height));
+    EXPECT_DOUBLE_EQ(expected_dimensions[i].width, width);
+    EXPECT_DOUBLE_EQ(expected_dimensions[i].height, height);
+  }
+}
+
+}  // namespace
+
+TEST_F(PDFiumPrintTest, GetPageNumbersFromPrintPageNumberRange) {
   std::vector<uint32_t> page_numbers;
 
   {
-    PP_PrintPageNumberRange_Dev page_ranges[] = {{0, 2}};
+    const PP_PrintPageNumberRange_Dev page_ranges[] = {{0, 2}};
     page_numbers = PDFiumPrint::GetPageNumbersFromPrintPageNumberRange(
         &page_ranges[0], base::size(page_ranges));
     EXPECT_THAT(page_numbers, ElementsAre(0, 1, 2));
   }
   {
-    PP_PrintPageNumberRange_Dev page_ranges[] = {{0, 0}, {2, 2}, {4, 5}};
+    const PP_PrintPageNumberRange_Dev page_ranges[] = {{0, 0}, {2, 2}, {4, 5}};
     page_numbers = PDFiumPrint::GetPageNumbersFromPrintPageNumberRange(
         &page_ranges[0], base::size(page_ranges));
     EXPECT_THAT(page_numbers, ElementsAre(0, 2, 4, 5));
   }
 }
 
+TEST_F(PDFiumPrintTest, Basic) {
+  SetDocumentForTest(FILE_PATH_LITERAL("hello_world2.pdf"));
+  pp::URLLoader dummy_loader;
+  TestClient client;
+  PDFiumEngine engine(&client, true);
+  ASSERT_TRUE(engine.New("https://chromium.org/dummy.pdf", ""));
+  ASSERT_TRUE(engine.HandleDocumentLoad(dummy_loader));
+
+  PDFiumPrint print(&engine);
+
+  const PP_PrintSettings_Dev print_settings = {kUSLetterRect,
+                                               kUSLetterRect,
+                                               kUSLetterSize,
+                                               72,
+                                               PP_PRINTORIENTATION_NORMAL,
+                                               PP_PRINTSCALINGOPTION_NONE,
+                                               PP_FALSE,
+                                               PP_PRINTOUTPUTFORMAT_PDF};
+  const PP_PdfPrintSettings_Dev pdf_print_settings = {1, 100};
+
+  {
+    // Print 2 pages.
+    const ExpectedDimensions kExpectedDimensions = {{612.0, 792.0},
+                                                    {612.0, 792.0}};
+    const PP_PrintPageNumberRange_Dev page_ranges[] = {{0, 1}};
+    std::vector<uint8_t> pdf_data =
+        print.PrintPagesAsPdf(&page_ranges[0], base::size(page_ranges),
+                              print_settings, pdf_print_settings,
+                              /*raster=*/false);
+    CheckPdfDimensions(pdf_data, kExpectedDimensions);
+
+    pdf_data = print.PrintPagesAsPdf(&page_ranges[0], base::size(page_ranges),
+                                     print_settings, pdf_print_settings,
+                                     /*raster=*/true);
+    CheckPdfDimensions(pdf_data, kExpectedDimensions);
+  }
+  {
+    // Print 1 page.
+    const ExpectedDimensions kExpectedDimensions = {{612.0, 792.0}};
+    const PP_PrintPageNumberRange_Dev page_ranges[] = {{0, 0}};
+    std::vector<uint8_t> pdf_data =
+        print.PrintPagesAsPdf(&page_ranges[0], base::size(page_ranges),
+                              print_settings, pdf_print_settings,
+                              /*raster=*/false);
+    CheckPdfDimensions(pdf_data, kExpectedDimensions);
+
+    pdf_data = print.PrintPagesAsPdf(&page_ranges[0], base::size(page_ranges),
+                                     print_settings, pdf_print_settings,
+                                     /*raster=*/true);
+    CheckPdfDimensions(pdf_data, kExpectedDimensions);
+  }
+  {
+    // Print the other page.
+    const ExpectedDimensions kExpectedDimensions = {{612.0, 792.0}};
+    const PP_PrintPageNumberRange_Dev page_ranges[] = {{1, 1}};
+    std::vector<uint8_t> pdf_data =
+        print.PrintPagesAsPdf(&page_ranges[0], base::size(page_ranges),
+                              print_settings, pdf_print_settings,
+                              /*raster=*/false);
+    CheckPdfDimensions(pdf_data, kExpectedDimensions);
+
+    pdf_data = print.PrintPagesAsPdf(&page_ranges[0], base::size(page_ranges),
+                                     print_settings, pdf_print_settings,
+                                     /*raster=*/true);
+    CheckPdfDimensions(pdf_data, kExpectedDimensions);
+  }
+}
+
 }  // namespace chrome_pdf
diff --git a/pdf/test/test_client.cc b/pdf/test/test_client.cc
index c3f7118..6c3f79b 100644
--- a/pdf/test/test_client.cc
+++ b/pdf/test/test_client.cc
@@ -27,6 +27,13 @@
   return pp::URLLoader();
 }
 
+std::vector<PDFEngine::Client::SearchStringResult> TestClient::SearchString(
+    const base::char16* string,
+    const base::char16* term,
+    bool case_sensitive) {
+  return std::vector<SearchStringResult>();
+}
+
 pp::Instance* TestClient::GetPluginInstance() {
   return nullptr;
 }
diff --git a/pdf/test/test_client.h b/pdf/test/test_client.h
index 0540879..a269995 100644
--- a/pdf/test/test_client.h
+++ b/pdf/test/test_client.h
@@ -6,6 +6,7 @@
 #define PDF_TEST_TEST_CLIENT_H_
 
 #include <string>
+#include <vector>
 
 #include "pdf/pdf_engine.h"
 
@@ -22,6 +23,9 @@
                      const std::string& default_answer) override;
   std::string GetURL() override;
   pp::URLLoader CreateURLLoader() override;
+  std::vector<SearchStringResult> SearchString(const base::char16* string,
+                                               const base::char16* term,
+                                               bool case_sensitive) override;
   pp::Instance* GetPluginInstance() override;
   bool IsPrintPreview() override;
   uint32_t GetBackgroundColor() override;
diff --git a/remoting/proto/video_stats.proto b/remoting/proto/video_stats.proto
index 6bc4bb6..c02ef22 100644
--- a/remoting/proto/video_stats.proto
+++ b/remoting/proto/video_stats.proto
@@ -11,7 +11,7 @@
 
 package remoting;
 
-// Next Id: 13
+// Next Id: 14
 message FrameStatsMessage {
   // Frame ID.
   optional uint32 frame_id = 1;
@@ -49,5 +49,9 @@
   // The capturer Id to indicate the implementation of ScreenCapturer that
   // generates this frame.
   optional uint32 capturer_id = 12;
-}
 
+  // The last quantizer chosen by the encoder for this frame. Higher numbers
+  // represent lower-quality images. Values are between 0 and 63, as used by
+  // the rc_*_quantizer config parameters.
+  optional uint32 frame_quantizer = 13;
+}
diff --git a/remoting/protocol/BUILD.gn b/remoting/protocol/BUILD.gn
index 6b7a978..a770685 100644
--- a/remoting/protocol/BUILD.gn
+++ b/remoting/protocol/BUILD.gn
@@ -340,6 +340,7 @@
     "connection_tester.h",
     "content_description_unittest.cc",
     "data_channel_manager_unittest.cc",
+    "frame_stats_unittest.cc",
     "http_ice_config_request_unittest.cc",
     "ice_config_unittest.cc",
     "ice_transport_unittest.cc",
diff --git a/remoting/protocol/frame_stats.cc b/remoting/protocol/frame_stats.cc
index 2c73e90..4dfede2 100644
--- a/remoting/protocol/frame_stats.cc
+++ b/remoting/protocol/frame_stats.cc
@@ -95,6 +95,12 @@
   if (message.has_bandwidth_estimate_kbps()) {
     result.bandwidth_estimate_kbps = message.bandwidth_estimate_kbps();
   }
+  if (message.has_capturer_id()) {
+    result.capturer_id = message.capturer_id();
+  }
+  if (message.has_frame_quantizer()) {
+    result.frame_quantizer = message.frame_quantizer();
+  }
 
   return result;
 }
@@ -136,6 +142,9 @@
   if (capturer_id != webrtc::DesktopCapturerId::kUnknown) {
     message_out->set_capturer_id(capturer_id);
   }
+  if (frame_quantizer != -1) {
+    message_out->set_frame_quantizer(frame_quantizer);
+  }
 }
 
 FrameStats::FrameStats() = default;
diff --git a/remoting/protocol/frame_stats.h b/remoting/protocol/frame_stats.h
index dc29126..581ec2b 100644
--- a/remoting/protocol/frame_stats.h
+++ b/remoting/protocol/frame_stats.h
@@ -43,6 +43,7 @@
   base::TimeDelta rtt_estimate = base::TimeDelta::Max();
   int bandwidth_estimate_kbps = -1;
   uint32_t capturer_id = webrtc::DesktopCapturerId::kUnknown;
+  int frame_quantizer = -1;
 };
 
 struct ClientFrameStats {
diff --git a/remoting/protocol/frame_stats_unittest.cc b/remoting/protocol/frame_stats_unittest.cc
new file mode 100644
index 0000000..9659c08
--- /dev/null
+++ b/remoting/protocol/frame_stats_unittest.cc
@@ -0,0 +1,46 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/protocol/frame_stats.h"
+#include "remoting/proto/video_stats.pb.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace remoting {
+namespace protocol {
+
+class FrameStatsTest : public testing::Test {};
+
+TEST_F(FrameStatsTest, ToStatsMessageAndBack_RestoresFrameStats) {
+  HostFrameStats stats;
+  stats.frame_size = 10;
+  stats.capture_delay = base::TimeDelta::FromSeconds(11);
+  stats.encode_delay = base::TimeDelta::FromSeconds(12);
+  stats.capture_pending_delay = base::TimeDelta::FromSeconds(13);
+  stats.capture_overhead_delay = base::TimeDelta::FromSeconds(14);
+  stats.encode_pending_delay = base::TimeDelta::FromSeconds(15);
+  stats.send_pending_delay = base::TimeDelta::FromSeconds(16);
+  stats.rtt_estimate = base::TimeDelta::FromSeconds(17);
+  stats.bandwidth_estimate_kbps = 18;
+  stats.capturer_id = 19;
+  stats.frame_quantizer = 20;
+  FrameStatsMessage message;
+
+  stats.ToFrameStatsMessage(&message);
+  HostFrameStats newStats = HostFrameStats::FromFrameStatsMessage(message);
+
+  EXPECT_EQ(stats.frame_size, newStats.frame_size);
+  EXPECT_EQ(stats.capture_delay, newStats.capture_delay);
+  EXPECT_EQ(stats.encode_delay, newStats.encode_delay);
+  EXPECT_EQ(stats.capture_pending_delay, newStats.capture_pending_delay);
+  EXPECT_EQ(stats.capture_overhead_delay, newStats.capture_overhead_delay);
+  EXPECT_EQ(stats.encode_pending_delay, newStats.encode_pending_delay);
+  EXPECT_EQ(stats.send_pending_delay, newStats.send_pending_delay);
+  EXPECT_EQ(stats.rtt_estimate, newStats.rtt_estimate);
+  EXPECT_EQ(stats.bandwidth_estimate_kbps, newStats.bandwidth_estimate_kbps);
+  EXPECT_EQ(stats.capturer_id, newStats.capturer_id);
+  EXPECT_EQ(stats.frame_quantizer, newStats.frame_quantizer);
+}
+
+}  // namespace protocol
+}  // namespace remoting
diff --git a/remoting/protocol/webrtc_video_stream.cc b/remoting/protocol/webrtc_video_stream.cc
index d90432e..4c0b259 100644
--- a/remoting/protocol/webrtc_video_stream.cc
+++ b/remoting/protocol/webrtc_video_stream.cc
@@ -64,6 +64,7 @@
   base::TimeTicks encode_ended_time;
 
   uint32_t capturer_id = 0;
+  int frame_quantizer = -1;
 };
 
 WebrtcVideoStream::WebrtcVideoStream(const SessionOptions& session_options)
@@ -242,6 +243,7 @@
   DCHECK(thread_checker_.CalledOnValidThread());
 
   current_frame_stats_->encode_ended_time = base::TimeTicks::Now();
+  current_frame_stats_->frame_quantizer = frame->quantizer;
 
   HostFrameStats stats;
   scheduler_->OnFrameEncoded(frame.get(), &stats);
@@ -297,6 +299,8 @@
 
     stats.capturer_id = current_frame_stats_->capturer_id;
 
+    stats.frame_quantizer = current_frame_stats_->frame_quantizer;
+
     video_stats_dispatcher_.OnVideoFrameStats(result.frame_id, stats);
   }
 }
diff --git a/services/network/public/cpp/net_ipc_param_traits.cc b/services/network/public/cpp/net_ipc_param_traits.cc
index bf0ac82..8ca5e2e 100644
--- a/services/network/public/cpp/net_ipc_param_traits.cc
+++ b/services/network/public/cpp/net_ipc_param_traits.cc
@@ -485,36 +485,34 @@
 }
 
 void ParamTraits<url::Origin>::Write(base::Pickle* m, const url::Origin& p) {
-  WriteParam(m, p.unique());
-  WriteParam(m, p.scheme());
-  WriteParam(m, p.host());
-  WriteParam(m, p.port());
+  WriteParam(m, p.GetTupleOrPrecursorTupleIfOpaque().scheme());
+  WriteParam(m, p.GetTupleOrPrecursorTupleIfOpaque().host());
+  WriteParam(m, p.GetTupleOrPrecursorTupleIfOpaque().port());
+  WriteParam(m, p.GetNonceForSerialization());
 }
 
 bool ParamTraits<url::Origin>::Read(const base::Pickle* m,
                                     base::PickleIterator* iter,
                                     url::Origin* p) {
-  bool unique;
   std::string scheme;
   std::string host;
   uint16_t port;
-  if (!ReadParam(m, iter, &unique) || !ReadParam(m, iter, &scheme) ||
-      !ReadParam(m, iter, &host) || !ReadParam(m, iter, &port)) {
+  base::Optional<base::UnguessableToken> nonce_if_opaque;
+  if (!ReadParam(m, iter, &scheme) || !ReadParam(m, iter, &host) ||
+      !ReadParam(m, iter, &port) || !ReadParam(m, iter, &nonce_if_opaque)) {
     return false;
   }
 
-  if (unique) {
-    *p = url::Origin();
-  } else {
-    base::Optional<url::Origin> origin =
-        url::Origin::UnsafelyCreateTupleOriginWithoutNormalization(scheme, host,
-                                                                   port);
-    if (!origin.has_value())
-      return false;
+  base::Optional<url::Origin> creation_result =
+      nonce_if_opaque
+          ? url::Origin::UnsafelyCreateOpaqueOriginWithoutNormalization(
+                scheme, host, port, url::Origin::Nonce(*nonce_if_opaque))
+          : url::Origin::UnsafelyCreateTupleOriginWithoutNormalization(
+                scheme, host, port);
+  if (!creation_result)
+    return false;
 
-    *p = origin.value();
-  }
-
+  *p = std::move(creation_result.value());
   return true;
 }
 
diff --git a/testing/buildbot/chromium.webrtc.fyi.experimental.json b/testing/buildbot/chromium.webrtc.fyi.experimental.json
index a6b59d9..4951a90d 100644
--- a/testing/buildbot/chromium.webrtc.fyi.experimental.json
+++ b/testing/buildbot/chromium.webrtc.fyi.experimental.json
@@ -20,7 +20,14 @@
           "--gtest_filter=WebRtc*"
         ],
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "K",
+              "device_type": "hammerhead",
+              "os": "Android"
+            }
+          ]
         },
         "test": "content_browsertests"
       }
@@ -33,7 +40,14 @@
           "--gtest_filter=WebRtc*"
         ],
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ]
         },
         "test": "content_browsertests"
       }
@@ -79,7 +93,13 @@
         ],
         "name": "browser_tests_functional",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Ubuntu-14.04"
+            }
+          ]
         },
         "test": "browser_tests"
       },
@@ -88,7 +108,13 @@
           "--gtest_filter=WebRtc*"
         ],
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Ubuntu-14.04"
+            }
+          ]
         },
         "test": "content_browsertests"
       },
@@ -100,7 +126,13 @@
         ],
         "name": "content_browsertests_stress",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Ubuntu-14.04"
+            }
+          ]
         },
         "test": "content_browsertests"
       },
@@ -109,13 +141,25 @@
           "--test-launcher-filter-file=../../testing/buildbot/filters/webrtc.content_unittests.filter"
         ],
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Ubuntu-14.04"
+            }
+          ]
         },
         "test": "content_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Ubuntu-14.04"
+            }
+          ]
         },
         "test": "jingle_unittests"
       },
@@ -124,7 +168,13 @@
           "--gtest_filter=Webrtc*"
         ],
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Ubuntu-14.04"
+            }
+          ]
         },
         "test": "remoting_unittests"
       }
@@ -170,7 +220,13 @@
         ],
         "name": "browser_tests_functional",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "8086:0a2e",
+              "os": "Mac-10.12.6"
+            }
+          ]
         },
         "test": "browser_tests"
       },
@@ -179,7 +235,13 @@
           "--gtest_filter=WebRtc*"
         ],
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "8086:0a2e",
+              "os": "Mac-10.12.6"
+            }
+          ]
         },
         "test": "content_browsertests"
       },
@@ -191,7 +253,13 @@
         ],
         "name": "content_browsertests_stress",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "8086:0a2e",
+              "os": "Mac-10.12.6"
+            }
+          ]
         },
         "test": "content_browsertests"
       },
@@ -200,13 +268,25 @@
           "--test-launcher-filter-file=../../testing/buildbot/filters/webrtc.content_unittests.filter"
         ],
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "8086:0a2e",
+              "os": "Mac-10.12.6"
+            }
+          ]
         },
         "test": "content_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "8086:0a2e",
+              "os": "Mac-10.12.6"
+            }
+          ]
         },
         "test": "jingle_unittests"
       },
@@ -215,7 +295,13 @@
           "--gtest_filter=Webrtc*"
         ],
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "8086:0a2e",
+              "os": "Mac-10.12.6"
+            }
+          ]
         },
         "test": "remoting_unittests"
       }
@@ -261,7 +347,13 @@
         ],
         "name": "browser_tests_functional",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "browser_tests"
       },
@@ -270,7 +362,13 @@
           "--gtest_filter=WebRtc*"
         ],
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "content_browsertests"
       },
@@ -282,7 +380,13 @@
         ],
         "name": "content_browsertests_stress",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "content_browsertests"
       },
@@ -291,13 +395,25 @@
           "--test-launcher-filter-file=../../testing/buildbot/filters/webrtc.content_unittests.filter"
         ],
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "content_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "jingle_unittests"
       },
@@ -306,7 +422,13 @@
           "--gtest_filter=Webrtc*"
         ],
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
         },
         "test": "remoting_unittests"
       }
@@ -334,7 +456,13 @@
         ],
         "name": "browser_tests_functional",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-7-SP1"
+            }
+          ]
         },
         "test": "browser_tests"
       },
@@ -343,7 +471,13 @@
           "--gtest_filter=WebRtc*"
         ],
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-7-SP1"
+            }
+          ]
         },
         "test": "content_browsertests"
       },
@@ -355,7 +489,13 @@
         ],
         "name": "content_browsertests_stress",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-7-SP1"
+            }
+          ]
         },
         "test": "content_browsertests"
       },
@@ -364,13 +504,25 @@
           "--test-launcher-filter-file=../../testing/buildbot/filters/webrtc.content_unittests.filter"
         ],
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-7-SP1"
+            }
+          ]
         },
         "test": "content_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-7-SP1"
+            }
+          ]
         },
         "test": "jingle_unittests"
       },
@@ -379,7 +531,13 @@
           "--gtest_filter=Webrtc*"
         ],
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-7-SP1"
+            }
+          ]
         },
         "test": "remoting_unittests"
       }
@@ -407,7 +565,13 @@
         ],
         "name": "browser_tests_functional",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-8.1-SP0"
+            }
+          ]
         },
         "test": "browser_tests"
       },
@@ -416,7 +580,13 @@
           "--gtest_filter=WebRtc*"
         ],
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-8.1-SP0"
+            }
+          ]
         },
         "test": "content_browsertests"
       },
@@ -428,7 +598,13 @@
         ],
         "name": "content_browsertests_stress",
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-8.1-SP0"
+            }
+          ]
         },
         "test": "content_browsertests"
       },
@@ -437,13 +613,25 @@
           "--test-launcher-filter-file=../../testing/buildbot/filters/webrtc.content_unittests.filter"
         ],
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-8.1-SP0"
+            }
+          ]
         },
         "test": "content_unittests"
       },
       {
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-8.1-SP0"
+            }
+          ]
         },
         "test": "jingle_unittests"
       },
@@ -452,7 +640,13 @@
           "--gtest_filter=Webrtc*"
         ],
         "swarming": {
-          "can_use_on_swarming_builders": true
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-8.1-SP0"
+            }
+          ]
         },
         "test": "remoting_unittests"
       }
diff --git a/testing/buildbot/filters/chromeos.mash.fyi.browser_tests.filter b/testing/buildbot/filters/chromeos.mash.fyi.browser_tests.filter
index 83e81d97..959f1be 100644
--- a/testing/buildbot/filters/chromeos.mash.fyi.browser_tests.filter
+++ b/testing/buildbot/filters/chromeos.mash.fyi.browser_tests.filter
@@ -35,7 +35,6 @@
 
 # The browser frame is a work in progress.
 -BrowserNonClientFrameViewAshTest.AvatarDisplayOnTeleportedWindow/*
--BrowserNonClientFrameViewAshTest.HeaderHeightForSnappedBrowserInSplitView/*
 
 # Touch gestures don't work in webcontents. https://crbug.com/866991.
 -TopControlsSlideControllerTest.TestScrollingMaximizedPageBeforeGoingToTabletMode
@@ -45,8 +44,8 @@
 -TopControlsSlideControllerTest.TestFocusEditableElements
 -TopControlsSlideControllerTest.DisplayRotation
 
-# Direct access to ash window frames, tablet mode, overview mode, etc.
--NonHomeLauncherBrowserNonClientFrameViewAshTest.*
+# Uses Shell/SplitViewController.
+-NonHomeLauncherBrowserNonClientFrameViewAshTest.HeaderHeightForSnappedBrowserInSplitView/*
 
 # Fix immersive fullscreen mode in mash. https://crbug.com/844748.
 # Needs EventGenerator to work across window tree hosts. crbug.com/814675
diff --git a/testing/buildbot/filters/chromeos.single_process_mash.browser_tests.filter b/testing/buildbot/filters/chromeos.single_process_mash.browser_tests.filter
index e31a8f2..07a57dfb 100644
--- a/testing/buildbot/filters/chromeos.single_process_mash.browser_tests.filter
+++ b/testing/buildbot/filters/chromeos.single_process_mash.browser_tests.filter
@@ -29,9 +29,6 @@
 
 # The browser frame is a work in progress.
 -BrowserNonClientFrameViewAshTest.AvatarDisplayOnTeleportedWindow/*
--BrowserNonClientFrameViewAshTest.HeaderHeightForSnappedBrowserInSplitView/*
--BrowserNonClientFrameViewAshTest.ImmersiveModeTopViewInset/*
--BrowserNonClientFrameViewAshTest.TopViewInset/*
 
 # Touch gestures don't work in webcontents. https://crbug.com/866991.
 -TopControlsSlideControllerTest.DisplayRotation
@@ -41,8 +38,8 @@
 -TopControlsSlideControllerTest.TestScrollingPage
 -TopControlsSlideControllerTest.TestScrollingPageAndSwitchingToNTP
 
-# Direct access to ash window frames, tablet mode, overview mode, etc.
--NonHomeLauncherBrowserNonClientFrameViewAshTest.*
+# Crash in ash::SplitViewController
+-NonHomeLauncherBrowserNonClientFrameViewAshTest.HeaderHeightForSnappedBrowserInSplitView/*
 
 # Fix immersive fullscreen mode in mash. https://crbug.com/844748.
 # Needs EventGenerator to work across window tree hosts. crbug.com/814675
@@ -162,6 +159,10 @@
 # Ash native views tests.
 -ChromeNativeAppWindowViewsAuraAshBrowserTest.*
 
+# Flaky.  crbug.com/888188.
+-ChromeSessionManagerTest.LoginExistingUsers
+-ChromeSessionManagerTest.PRE_LoginExistingUsers
+
 # Context menu tests.
 -ContextMenuBrowserTest.ContextMenuEntriesAreDisabledInLockedFullscreen
 
diff --git a/testing/buildbot/filters/webui_polymer2_browser_tests.filter b/testing/buildbot/filters/webui_polymer2_browser_tests.filter
index d403ed3..37338943 100644
--- a/testing/buildbot/filters/webui_polymer2_browser_tests.filter
+++ b/testing/buildbot/filters/webui_polymer2_browser_tests.filter
@@ -57,10 +57,6 @@
 -DemoSetupTest.OnlineSetupFlowSuccess
 -DemoSetupTest.ShowOfflineSetupOptionOnNetworkList
 -EulaTest.LoadOffline
--TabIndex/FilesAppBrowserTest.Test/tabindexSaveFileDialogDownloads
--TabIndex/FilesAppBrowserTest.Test/tabindexSaveFileDialogDownloads_GuestMode
--TabIndex/FilesAppBrowserTest.Test/tabindexSaveFileDialogDrive
--TabIndex/FilesAppBrowserTest.Test/tabindexSaveFileDialogDrive_DriveFs
 
 # Passing tests
 #
@@ -310,6 +306,7 @@
 SigninSyncConfirmationTest.*
 SiteEngagementBrowserTest.*
 SysInternalsBrowserTest.*
+TabIndex/FilesAppBrowserTest.*
 TextDefaultsTest.*
 TtsAccessibilityTest.*
 UserManagerBrowserTest.*
diff --git a/testing/buildbot/swarming_mixins.pyl b/testing/buildbot/swarming_mixins.pyl
index 1d95385..0ca3ab2 100644
--- a/testing/buildbot/swarming_mixins.pyl
+++ b/testing/buildbot/swarming_mixins.pyl
@@ -25,16 +25,33 @@
       'cpu': 'armv7l-32',
     }
   },
+  'bullhead': {
+    'dimensions': {
+      'device_type': 'bullhead',
+      'os': 'Android',
+    }
+  },
   'gpu_pool': {
     'dimensions': {
       'pool': 'Chrome-GPU',
     }
   },
+  'hammerhead': {
+    'dimensions': {
+      'device_type': 'hammerhead',
+      'os': 'Android',
+    }
+  },
   'intel_iris_5100': {
     'dimensions': {
       'gpu': '8086:0a2e',
     }
   },
+  'kitkat': {
+    'dimensions': {
+      'device_os': 'K',
+    }
+  },
   'linux-trusty': {
     'dimensions': {
       'os': 'Ubuntu-14.04',
@@ -66,6 +83,11 @@
       'os': 'Mac-10.12.6',
     }
   },
+  'marshmallow': {
+    'dimensions': {
+      'device_os': 'MMB29Q',
+    }
+  },
   'no_gpu': {
     'dimensions': {
       'gpu': 'none',
@@ -86,6 +108,11 @@
       'os': 'Windows-7-SP1',
     }
   },
+  'win8': {
+    'dimensions': {
+      'os': 'Windows-8.1-SP0',
+    }
+  },
   'x86-32': {
     'dimensions': {
       'cpu': 'x86-32',
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 2cc9f8e..93b43a7 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -3549,11 +3549,19 @@
       'WebRTC Chromium FYI Android Builder (dbg)': {},
       'WebRTC Chromium FYI Android Builder ARM64 (dbg)': {},
       'WebRTC Chromium FYI Android Tests (dbg) (K Nexus5)': {
+        'swarming_mixins': [
+          'kitkat',
+          'hammerhead',
+        ],
         'test_suites': {
           'gtest_tests': 'webrtc_chromium_android_gtests',
         },
       },
       'WebRTC Chromium FYI Android Tests (dbg) (M Nexus5X)': {
+        'swarming_mixins': [
+          'marshmallow',
+          'bullhead',
+        ],
         'test_suites': {
           'gtest_tests': 'webrtc_chromium_android_gtests',
         },
@@ -3577,6 +3585,10 @@
         ],
       },
       'WebRTC Chromium FYI Linux Tester': {
+        'swarming_mixins': [
+          'x86-64',
+          'linux-trusty',
+        ],
         'test_suites': {
           'gtest_tests': 'webrtc_chromium_gtests',
         },
@@ -3600,6 +3612,9 @@
         ],
       },
       'WebRTC Chromium FYI Mac Tester': {
+        'swarming_mixins': [
+          'mac_mini_10.12',
+        ],
         'test_suites': {
           'gtest_tests': 'webrtc_chromium_gtests',
         },
@@ -3623,16 +3638,28 @@
         ],
       },
       'WebRTC Chromium FYI Win10 Tester': {
+        'swarming_mixins': [
+          'x86-64',
+          'win10',
+        ],
         'test_suites': {
           'gtest_tests': 'webrtc_chromium_gtests',
         },
       },
       'WebRTC Chromium FYI Win7 Tester': {
+        'swarming_mixins': [
+          'x86-64',
+          'win7',
+        ],
         'test_suites': {
           'gtest_tests': 'webrtc_chromium_gtests',
         },
       },
       'WebRTC Chromium FYI Win8 Tester': {
+        'swarming_mixins': [
+          'x86-64',
+          'win8',
+        ],
         'test_suites': {
           'gtest_tests': 'webrtc_chromium_gtests',
         },
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG b/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
index 9ba4931..07df34b 100644
--- a/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
+++ b/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
@@ -358,7 +358,6 @@
 crbug.com/835484 fast/css/outline-auto-empty-rects.html [ Failure ]
 crbug.com/835484 fast/css/outline-narrowLine.html [ Failure ]
 crbug.com/591099 fast/css3-text/css3-text-decoration/text-underline-position/text-underline-position-under.html [ Failure ]
-crbug.com/591099 fast/events/popup-allowed-from-gesture-initiated-event.html [ Failure ]
 crbug.com/591099 fast/events/touch/compositor-touch-hit-rects.html [ Failure ]
 crbug.com/591099 fast/events/wheel/mainthread-touchpad-fling-latching.html [ Pass ]
 crbug.com/591099 fast/events/wheel/wheel-scroll-latching-on-scrollbar.html [ Pass ]
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index d93c9a0..e9641cb 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -1947,6 +1947,8 @@
 
 crbug.com/498539 crbug.com/794869 crbug.com/798548 [ Win7 Mac Linux ] http/tests/devtools/elements/styles-4/styles-update-from-js.js [ Crash Pass Failure ]
 
+crbug.com/889952 fast/selectors/selection-window-inactive.html [ Pass Failure ]
+
 crbug.com/487281 [ Mac ] fast/forms/select/menulist-narrow-width.html [ Failure ]
 
 crbug.com/543110 [ Mac ] fast/text/international/text-shaping-arabic.html [ Failure ]
@@ -2975,7 +2977,8 @@
 crbug.com/626703 external/wpt/svg/rendering/order/z-index.svg [ Failure ]
 crbug.com/626703 external/wpt/web-animations/timing-model/timelines/update-and-send-events.html [ Timeout ]
 crbug.com/626703 external/wpt/console/console-timing-logging-manual.html [ Skip ]
-crbug.com/888042 external/wpt/css/css-scoping/slotted-specificity.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-scoping/host-specificity-002.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-scoping/slotted-specificity.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-text/white-space/break-spaces-001.html [ Failure ]
 crbug.com/626703 [ Mac10.12 Mac10.13 ] external/wpt/compat/webkit-text-fill-color-property-002.html [ Failure ]
 crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-column-row-gap-001.html [ Failure ]
@@ -4155,7 +4158,6 @@
 crbug.com/718155 payments/payment-request-in-iframe-nested-not-allowed.html [ Failure ]
 
 # Layout Tests on Swarming (Windows) - https://crbug.com/717347
-crbug.com/724392 [ Win ] fast/css/font-family-case-insensitive.html [ Failure Pass Timeout ]
 crbug.com/713094 [ Win ] fast/css/fontfaceset-check-platform-fonts.html [ Failure Pass ]
 
 # Image decode failures due to document destruction.
diff --git a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST_5.json b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST_5.json
index 1c00b43..ff307897 100644
--- a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST_5.json
+++ b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST_5.json
@@ -54607,6 +54607,18 @@
      {}
     ]
    ],
+   "css/css-scoping/host-specificity-003.html": [
+    [
+     "/css/css-scoping/host-specificity-003.html",
+     [
+      [
+       "/css/css-scoping/reference/green-box.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-scoping/host-specificity.html": [
     [
      "/css/css-scoping/host-specificity.html",
@@ -161527,6 +161539,11 @@
      {}
     ]
    ],
+   "html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-credentials.sub-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-external.js": [
     [
      {}
@@ -161942,6 +161959,11 @@
      {}
     ]
    ],
+   "html/semantics/scripting-1/the-script-element/module/resources/dynamic-import-credentials-iframe.sub.html": [
+    [
+     {}
+    ]
+   ],
    "html/semantics/scripting-1/the-script-element/module/resources/fast-module.js": [
     [
      {}
@@ -196906,6 +196928,12 @@
      {}
     ]
    ],
+   "css/css-flexbox/flex-minimum-height-flex-items-009.html": [
+    [
+     "/css/css-flexbox/flex-minimum-height-flex-items-009.html",
+     {}
+    ]
+   ],
    "css/css-flexbox/flexbox_first-letter.html": [
     [
      "/css/css-flexbox/flexbox_first-letter.html",
@@ -230554,6 +230582,12 @@
      {}
     ]
    ],
+   "html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-credentials.sub.html": [
+    [
+     "/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-credentials.sub.html",
+     {}
+    ]
+   ],
    "html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-fetch-error.sub.html": [
     [
      "/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-fetch-error.sub.html",
@@ -316781,6 +316815,10 @@
    "ead7a424b374fddb247046d2a36a37a11669baae",
    "reftest"
   ],
+  "css/css-flexbox/flex-minimum-height-flex-items-009.html": [
+   "718386af02069fa1a3fff0ee5aaa10415ef4b23a",
+   "testharness"
+  ],
   "css/css-flexbox/flex-minimum-width-flex-items-001.xht": [
    "b8e2866edaa46af46900c287238894cd8ddef24c",
    "reftest"
@@ -333945,6 +333983,10 @@
    "3132d3a3455241347d6fe421f3434c361e424493",
    "reftest"
   ],
+  "css/css-scoping/host-specificity-003.html": [
+   "54a22599d8f83f612d7fc9ff91abfe650114c6e2",
+   "reftest"
+  ],
   "css/css-scoping/host-specificity.html": [
    "3ef61d4135fe7dbf846feb82540ca1a213ce7987",
    "reftest"
@@ -373570,7 +373612,7 @@
    "support"
   ],
   "fetch/corb/OWNERS": [
-   "9a8ac4ee4b3e12fe47b191aea40757b1ef9e79b1",
+   "42d5155b66317b0067f81189e31a9168d13ca2f0",
    "support"
   ],
   "fetch/corb/README.md": [
@@ -390382,7 +390424,7 @@
    "testharness"
   ],
   "html/semantics/scripting-1/the-script-element/module/credentials.sub.html": [
-   "c7cab6e958388f940c41760d32d81bed96bab108",
+   "cf23e67ae40c59c7707e90bba642fc4f18ff99ae",
    "testharness"
   ],
   "html/semantics/scripting-1/the-script-element/module/crossorigin-common.js": [
@@ -390465,6 +390507,14 @@
    "6a01495c333d196313d2d26f1ec101e15d224699",
    "testharness"
   ],
+  "html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-credentials.sub-expected.txt": [
+   "6e9f8aa345e49d4a771efa4b2600369f2a1ef753",
+   "support"
+  ],
+  "html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-credentials.sub.html": [
+   "b939a3ef1639db8027519ac004cd3197e254c0b9",
+   "testharness"
+  ],
   "html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-fetch-error.sub.html": [
    "5cc600c5a3db0cdc96de0b957db654aa733a39e0",
    "testharness"
@@ -391018,13 +391068,17 @@
    "support"
   ],
   "html/semantics/scripting-1/the-script-element/module/resources/credentials-iframe.sub.html": [
-   "d2b28714b1606dea015650114740362bcf6f31b3",
+   "8a0506ccec63b1c7f04b3818e980d0faea45f5eb",
    "support"
   ],
   "html/semantics/scripting-1/the-script-element/module/resources/delayed-modulescript.py": [
    "6ed16212d719203dc9c8b385ee044edff5accf55",
    "support"
   ],
+  "html/semantics/scripting-1/the-script-element/module/resources/dynamic-import-credentials-iframe.sub.html": [
+   "836ece62c5f601b91a9f255f4e6a646160fa7f48",
+   "support"
+  ],
   "html/semantics/scripting-1/the-script-element/module/resources/fast-module.js": [
    "3a76cf71f6c592ce15e2bd2bd158ef4945c9d96b",
    "support"
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-scoping/host-specificity-003.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-scoping/host-specificity-003.html
deleted file mode 100644
index 54a22599..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-scoping/host-specificity-003.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!doctype html>
-<meta charset="utf-8">
-<title>CSS Test: the :host() selector affects specificity</title>
-<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
-<link rel="author" title="Mozilla" href="https://mozilla.org">
-<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
-<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1915">
-<link rel="help" href="https://bugzil.la/1454165">
-<link rel="match" href="reference/green-box.html">
-<p>Test passes if you see a single 100px by 100px green box below.</p>
-<div id="host"></div>
-<script>
-  host.attachShadow({ mode: 'open' }).innerHTML = `
-    <style>
-      :host(*) div {
-        width: 100px;
-        height: 100px;
-        background: green;
-      }
-      div {
-        background: red;
-      }
-    </style>
-    <div></div>
-  `;
-</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/credentials.sub.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/credentials.sub.html
index c7cab6e..cf23e67 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/credentials.sub.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/credentials.sub.html
@@ -40,44 +40,30 @@
     const w = iframe.contentWindow;
 
     assert_equals(w.sameOriginNone, 'found',
-                  'Modules should be loaded without the credentials when the crossOrigin attribute is not specified and the target is same-origin');
+                  'Modules should be loaded with the credentials when the crossOrigin attribute is not specified and the target is same-origin');
     assert_equals(w.sameOriginAnonymous, 'found',
                   'Modules should be loaded with the credentials when the crossOrigin attribute is specified with "anonymous" as its value and the target is same-origin');
     assert_equals(w.sameOriginUseCredentials, 'found',
                   'Modules should be loaded with the credentials when the crossOrigin attribute is specified with "use-credentials" as its value and the target is same-origin');
     assert_equals(w.crossOriginNone, 'not found',
-                  'Modules should be loaded without the credentials when the crossOrigin attribute is not specified and the target is cross-origin');
+                  'Modules should not be loaded with the credentials when the crossOrigin attribute is not specified and the target is cross-origin');
     assert_equals(w.crossOriginAnonymous, 'not found',
-                  'Modules should be loaded without the credentials when the crossOrigin attribute is specified with "anonymous" as its value and the target is cross-origin');
+                  'Modules should not be loaded with the credentials when the crossOrigin attribute is specified with "anonymous" as its value and the target is cross-origin');
     assert_equals(w.crossOriginUseCredentials, 'found',
                   'Modules should be loaded with the credentials when the crossOrigin attribute is specified with "use-credentials" as its value and the target is cross-origin');
 
     assert_equals(w.sameOriginNoneDescendant, 'found',
-                  'Descendant modules should be loaded without the credentials when the crossOrigin attribute is not specified and the target is same-origin');
+                  'Descendant modules should be loaded with the credentials when the crossOrigin attribute is not specified and the target is same-origin');
     assert_equals(w.sameOriginAnonymousDescendant, 'found',
                   'Descendant modules should be loaded with the credentials when the crossOrigin attribute is specified with "anonymous" as its value and the target is same-origin');
     assert_equals(w.sameOriginUseCredentialsDescendant, 'found',
                   'Descendant modules should be loaded with the credentials when the crossOrigin attribute is specified with "use-credentials" as its value and the target is same-origin');
     assert_equals(w.crossOriginNoneDescendant, 'not found',
-                  'Descendant modules should be loaded without the credentials when the crossOrigin attribute is not specified and the target is cross-origin');
+                  'Descendant modules should not be loaded with the credentials when the crossOrigin attribute is not specified and the target is cross-origin');
     assert_equals(w.crossOriginAnonymousDescendant, 'not found',
-                  'Descendant modules should be loaded without the credentials when the crossOrigin attribute is specified with "anonymous" as its value and the target is cross-origin');
+                  'Descendant modules should not be loaded with the credentials when the crossOrigin attribute is specified with "anonymous" as its value and the target is cross-origin');
     assert_equals(w.crossOriginUseCredentialsDescendant, 'found',
                   'Descendant modules should be loaded with the credentials when the crossOrigin attribute is specified with "use-credentials" as its value and the target is cross-origin');
-
-    assert_equals(w.sameOriginNoneDynamicDescendant, 'found',
-                  'Dynamic decendent modules should be loaded without the credentials when the crossOrigin attribute is not specified and the target is same-origin');
-    assert_equals(w.sameOriginAnonymousDynamicDescendant, 'found',
-                  'Dynamic descendant modules should be loaded with the credentials when the crossOrigin attribute is specified with "anonymous" as its value and the target is same-origin');
-    assert_equals(w.sameOriginUseCredentialsDynamicDescendant, 'found',
-                  'Dynamic descendant modules should be loaded with the credentials when the crossOrigin attribute is specified with "use-credentials" as its value and the target is same-origin');
-    assert_equals(w.crossOriginNoneDynamicDescendant, 'not found',
-                  'Dynamic descendant modules should be loaded without the credentials when the crossOrigin attribute is not specified and the target is cross-origin');
-    assert_equals(w.crossOriginAnonymousDynamicDescendant, 'not found',
-                  'Dynamic descendant modules should be loaded without the credentials when the crossOrigin attribute is specified with "anonymous" as its value and the target is cross-origin');
-    assert_equals(w.crossOriginUseCredentialsDynamicDescendant, 'found',
-                  'Dynamic descendant modules should be loaded with the credentials when the crossOrigin attribute is specified with "use-credentials" as its value and the target is cross-origin');
-
 });
 }, 'Modules should be loaded with or without the credentials based on the same-origin-ness and the crossOrigin attribute');
 </script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-credentials.sub-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-credentials.sub-expected.txt
new file mode 100644
index 0000000..6e9f8aa
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-credentials.sub-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Dynamic imports should be loaded with or without the credentials based on the same-origin-ness and the parent script's crossOrigin attribute assert_equals: Dynamic descendant modules should be loaded with the credentials when the crossOrigin attribute is not specified and the target is same-origin expected "found" but got "not found"
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-credentials.sub.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-credentials.sub.html
new file mode 100644
index 0000000..b939a3ef
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-credentials.sub.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+
+<script type="text/javascript">
+host_info = get_host_info();
+
+document.cookie = 'same=1';
+
+const setCookiePromise = fetch(
+    'http://{{domains[www2]}}:{{ports[http][0]}}/cookies/resources/set-cookie.py?name=cross&path=/html/semantics/scripting-1/the-script-element/module/',
+    {
+      mode: 'no-cors',
+      credentials: 'include',
+    });
+
+const windowLoadPromise = new Promise(resolve => {
+  window.addEventListener('load', () => {
+    resolve();
+  });
+});
+
+promise_test(t => {
+  const iframe = document.createElement('iframe');
+
+  return Promise.all([setCookiePromise, windowLoadPromise]).then(() => {
+    const messagePromise = new Promise(resolve => {
+      window.addEventListener('message', event => {
+        resolve();
+      });
+    });
+
+    iframe.src = '../resources/dynamic-import-credentials-iframe.sub.html';
+    document.body.appendChild(iframe);
+
+    return messagePromise;
+  }).then(() => {
+    const w = iframe.contentWindow;
+
+    assert_equals(w.sameOriginNoneDynamicDescendant, 'found',
+                  'Dynamic descendant modules should be loaded with the credentials when the crossOrigin attribute is not specified and the target is same-origin');
+    assert_equals(w.sameOriginAnonymousDynamicDescendant, 'found',
+                  'Dynamic descendant modules should be loaded with the credentials when the crossOrigin attribute is specified with "anonymous" as its value and the target is same-origin');
+    assert_equals(w.sameOriginUseCredentialsDynamicDescendant, 'found',
+                  'Dynamic descendant modules should be loaded with the credentials when the crossOrigin attribute is specified with "use-credentials" as its value and the target is same-origin');
+    assert_equals(w.crossOriginNoneDynamicDescendant, 'not found',
+                  'Dynamic descendant modules should not be loaded with the credentials when the crossOrigin attribute is not specified and the target is cross-origin');
+    assert_equals(w.crossOriginAnonymousDynamicDescendant, 'not found',
+                  'Dynamic descendant modules should not be loaded with the credentials when the crossOrigin attribute is specified with "anonymous" as its value and the target is cross-origin');
+    assert_equals(w.crossOriginUseCredentialsDynamicDescendant, 'found',
+                  'Dynamic descendant modules should be loaded with the credentials when the crossOrigin attribute is specified with "use-credentials" as its value and the target is cross-origin');
+});
+}, 'Dynamic imports should be loaded with or without the credentials based on the same-origin-ness and the parent script\'s crossOrigin attribute');
+</script>
+<body>
+</body>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/resources/credentials-iframe.sub.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/resources/credentials-iframe.sub.html
index d2b2871..8a0506cc 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/resources/credentials-iframe.sub.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/resources/credentials-iframe.sub.html
@@ -43,26 +43,6 @@
 import "http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/check-cookie.py?id=crossOriginUseCredentialsDescendant&cookieName=cross";
 </script>
 
-<script type="module">
-import("./check-cookie.py?id=sameOriginNoneDynamicDescendant&cookieName=same");
-</script>
-<script type="module" crossOrigin="anonymous">
-import("./check-cookie.py?id=sameOriginAnonymousDynamicDescendant&cookieName=same");
-</script>
-<script type="module" crossOrigin="use-credentials">
-import("./check-cookie.py?id=sameOriginUseCredentialsDynamicDescendant&cookieName=same");
-</script>
-<script type="module">
-import("http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/check-cookie.py?id=crossOriginNoneDynamicDescendant&cookieName=cross");
-</script>
-<script type="module" crossOrigin="anonymous">
-import("http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/check-cookie.py?id=crossOriginAnonymousDynamicDescendant&cookieName=cross");
-</script>
-<script type="module" crossOrigin="use-credentials">
-import("http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/check-cookie.py?id=crossOriginUseCredentialsDynamicDescendant&cookieName=cross");
-</script>
-
-
 <script type="text/javascript">
 window.addEventListener('load', event => {
   window.parent.postMessage({}, '*');
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/resources/dynamic-import-credentials-iframe.sub.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/resources/dynamic-import-credentials-iframe.sub.html
new file mode 100644
index 0000000..836ece62c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/module/resources/dynamic-import-credentials-iframe.sub.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<script type="module">
+import("./check-cookie.py?id=sameOriginNoneDynamicDescendant&cookieName=same");
+</script>
+<script type="module" crossOrigin="anonymous">
+import("./check-cookie.py?id=sameOriginAnonymousDynamicDescendant&cookieName=same");
+</script>
+<script type="module" crossOrigin="use-credentials">
+import("./check-cookie.py?id=sameOriginUseCredentialsDynamicDescendant&cookieName=same");
+</script>
+<script type="module">
+import("http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/check-cookie.py?id=crossOriginNoneDynamicDescendant&cookieName=cross");
+</script>
+<script type="module" crossOrigin="anonymous">
+import("http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/check-cookie.py?id=crossOriginAnonymousDynamicDescendant&cookieName=cross");
+</script>
+<script type="module" crossOrigin="use-credentials">
+import("http://{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/module/resources/check-cookie.py?id=crossOriginUseCredentialsDynamicDescendant&cookieName=cross");
+</script>
+
+
+<script type="text/javascript">
+window.addEventListener('load', event => {
+  window.parent.postMessage({}, '*');
+});
+</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webaudio/the-audio-api/the-analysernode-interface/ctor-analyser-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/webaudio/the-audio-api/the-analysernode-interface/ctor-analyser-expected.txt
deleted file mode 100644
index b335e1bd..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/webaudio/the-audio-api/the-analysernode-interface/ctor-analyser-expected.txt
+++ /dev/null
@@ -1,74 +0,0 @@
-This is a testharness.js-based test.
-Found 70 tests; 67 PASS, 3 FAIL, 0 TIMEOUT, 0 NOTRUN.
-PASS # AUDIT TASK RUNNER STARTED.
-PASS > [initialize] 
-PASS   context = new OfflineAudioContext(...) did not throw an exception.
-PASS < [initialize] All assertions passed. (total 1 assertions)
-PASS > [invalid constructor] 
-PASS   new AnalyserNode() threw TypeError: "Failed to construct 'AnalyserNode': 1 argument required, but only 0 present.".
-PASS   new AnalyserNode(1) threw TypeError: "Failed to construct 'AnalyserNode': parameter 1 is not of type 'BaseAudioContext'.".
-PASS   new AnalyserNode(context, 42) threw TypeError: "Failed to construct 'AnalyserNode': parameter 2 ('options') is not an object.".
-PASS < [invalid constructor] All assertions passed. (total 3 assertions)
-PASS > [default constructor] 
-PASS   node0 = new AnalyserNode(context) did not throw an exception.
-PASS   node0 instanceof AnalyserNode is equal to true.
-PASS   node0.numberOfInputs is equal to 1.
-PASS   node0.numberOfOutputs is equal to 1.
-FAIL X node0.channelCount is not equal to 2. Got 1. assert_true: expected true got false
-PASS   node0.channelCountMode is equal to max.
-PASS   node0.channelInterpretation is equal to speakers.
-PASS   node0.fftSize is equal to 2048.
-PASS   node0.frequencyBinCount is equal to 1024.
-PASS   node0.minDecibels is equal to -100.
-PASS   node0.maxDecibels is equal to -30.
-PASS   node0.smoothingTimeConstant is equal to 0.8.
-FAIL < [default constructor] 1 out of 12 assertions were failed. assert_true: expected true got false
-PASS > [test AudioNodeOptions] 
-PASS   new AnalyserNode(c, {channelCount: 17}} did not throw an exception.
-PASS   node.channelCount is equal to 17.
-PASS   new AnalyserNode(c, {channelCount: 0}) threw NotSupportedError: "Failed to construct 'AnalyserNode': The channel count provided (0) is outside the range [1, 32].".
-PASS   new AnalyserNode(c, {channelCount: 99}) threw NotSupportedError: "Failed to construct 'AnalyserNode': The channel count provided (99) is outside the range [1, 32].".
-PASS   new AnalyserNode(c, {channelCountMode: "max"} did not throw an exception.
-PASS   node.channelCountMode is equal to max.
-PASS   new AnalyserNode(c, {channelCountMode: "max"}) did not throw an exception.
-PASS   node.channelCountMode after valid setter is equal to max.
-PASS   new AnalyserNode(c, {channelCountMode: "clamped-max"}) did not throw an exception.
-PASS   node.channelCountMode after valid setter is equal to clamped-max.
-PASS   new AnalyserNode(c, {channelCountMode: "explicit"}) did not throw an exception.
-PASS   node.channelCountMode after valid setter is equal to explicit.
-PASS   new AnalyserNode(c, {channelCountMode: "foobar"} threw TypeError: "Failed to construct 'AnalyserNode': The provided value 'foobar' is not a valid enum value of type ChannelCountMode.".
-PASS   node.channelCountMode after invalid setter is equal to explicit.
-PASS   new AnalyserNode(c, {channelInterpretation: "speakers"}) did not throw an exception.
-PASS   node.channelInterpretation is equal to speakers.
-PASS   new AnalyserNode(c, {channelInterpretation: "discrete"}) did not throw an exception.
-PASS   node.channelInterpretation is equal to discrete.
-PASS   new AnalyserNode(c, {channelInterpretation: "foobar"}) threw TypeError: "Failed to construct 'AnalyserNode': The provided value 'foobar' is not a valid enum value of type ChannelInterpretation.".
-PASS   node.channelInterpretation after invalid setter is equal to discrete.
-PASS < [test AudioNodeOptions] All assertions passed. (total 20 assertions)
-PASS > [constructor with options] 
-PASS   node1 = new AnalyserNode(c, {"fftSize":32,"maxDecibels":1,"minDecibels":-13,"smoothingTimeConstant":0.125}) did not throw an exception.
-PASS   node1 instanceof AnalyserNode is equal to true.
-PASS   node1.fftSize is equal to 32.
-PASS   node1.maxDecibels is equal to 1.
-PASS   node1.minDecibels is equal to -13.
-PASS   node1.smoothingTimeConstant is equal to 0.125.
-PASS < [constructor with options] All assertions passed. (total 6 assertions)
-PASS > [construct invalid options] 
-PASS   node = new AnalyserNode(c, { fftSize: 33 }) threw IndexSizeError: "Failed to construct 'AnalyserNode': The value provided (33) is not a power of two.".
-PASS   node = new AnalyserNode(c, { maxDecibels: -500 }) threw IndexSizeError: "Failed to construct 'AnalyserNode': maxDecibels (-500) must be greater than or equal to minDecibels ( -100).".
-PASS   node = new AnalyserNode(c, { minDecibels: -10 }) threw IndexSizeError: "Failed to construct 'AnalyserNode': maxDecibels (-30) must be greater than or equal to minDecibels ( -10).".
-PASS   node = new AnalyserNode(c, { smoothingTimeConstant: 2 }) threw IndexSizeError: "Failed to construct 'AnalyserNode': The smoothing value provided (2) is outside the range [0, 1].".
-PASS   node = new AnalyserNode(c, { frequencyBinCount: 33 }) did not throw an exception.
-PASS   node.frequencyBinCount is equal to 1024.
-PASS < [construct invalid options] All assertions passed. (total 6 assertions)
-PASS > [setting min/max] 
-PASS   node = new AnalyserNode(c, {"minDecibels":-10,"maxDecibels":20}) did not throw an exception.
-PASS   node = new AnalyserNode(c, {"maxDecibels":20,"minDecibels":-10}) did not throw an exception.
-PASS   node = new AnalyserNode(c, {"minDecibels":-200,"maxDecibels":-150}) did not throw an exception.
-PASS   node = new AnalyserNode(c, {"maxDecibels":-150,"minDecibels":-200}) did not throw an exception.
-PASS   node = new AnalyserNode(c, {"maxDecibels":-150,"minDecibels":-10}) threw IndexSizeError: "Failed to construct 'AnalyserNode': maxDecibels (-150) must be greater than or equal to minDecibels ( -10).".
-PASS   node = new AnalyserNode(c, {"minDecibels":-10,"maxDecibels":-150}) threw IndexSizeError: "Failed to construct 'AnalyserNode': maxDecibels (-150) must be greater than or equal to minDecibels ( -10).".
-PASS < [setting min/max] All assertions passed. (total 6 assertions)
-FAIL # AUDIT TASK RUNNER FINISHED: 1 out of 7 tasks were failed. assert_true: expected true got false
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webaudio/the-audio-api/the-analysernode-interface/test-analysernode-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/webaudio/the-audio-api/the-analysernode-interface/test-analysernode-expected.txt
deleted file mode 100644
index 09df255..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/webaudio/the-audio-api/the-analysernode-interface/test-analysernode-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-FAIL Test AnalyserNode API assert_equals: analyser node has 2 input channels by default expected 2 but got 1
-FAIL Test AnalyserNode's ctor API assert_equals: analyser node has 2 input channels by default expected 2 but got 1
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/fast/dom/shadow/shadow-tree-styles-select-host.html b/third_party/WebKit/LayoutTests/fast/dom/shadow/shadow-tree-styles-select-host.html
index c39514e8..18377e7 100644
--- a/third_party/WebKit/LayoutTests/fast/dom/shadow/shadow-tree-styles-select-host.html
+++ b/third_party/WebKit/LayoutTests/fast/dom/shadow/shadow-tree-styles-select-host.html
@@ -116,7 +116,7 @@
 
 sandbox.appendChild(
     createDOM('style', {},
-        document.createTextNode('#host { border: 1px solid red; }')));
+        document.createTextNode('div { border: 1px solid red; }')));
 sandbox.appendChild(
     createDOM('div', {'id': 'host'},
         createShadowRoot(
diff --git a/third_party/WebKit/LayoutTests/flag-specific/enable-blink-features=LayoutNG/fast/events/popup-allowed-from-gesture-initiated-event-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/enable-blink-features=LayoutNG/fast/events/popup-allowed-from-gesture-initiated-event-expected.txt
deleted file mode 100644
index 50f1454..0000000
--- a/third_party/WebKit/LayoutTests/flag-specific/enable-blink-features=LayoutNG/fast/events/popup-allowed-from-gesture-initiated-event-expected.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-Click Here Click Here Too
-PASS win is non-null.
-PASS successfullyParsed is true
-
-TEST COMPLETE
-PASS win is non-null.
-
diff --git a/third_party/WebKit/LayoutTests/http/tests/credentialmanager/resources/mock-navigator-credentials.js b/third_party/WebKit/LayoutTests/http/tests/credentialmanager/resources/mock-navigator-credentials.js
index a56c90d..b8d4143 100644
--- a/third_party/WebKit/LayoutTests/http/tests/credentialmanager/resources/mock-navigator-credentials.js
+++ b/third_party/WebKit/LayoutTests/http/tests/credentialmanager/resources/mock-navigator-credentials.js
@@ -32,7 +32,7 @@
       icon: new url.mojom.Url({url: icon}),
       password: stringToMojoString16(password),
       federation: new url.mojom.Origin(
-          {scheme: '', host: '', port: 0, unique: true})
+          {scheme: 'https', host: 'foo.com', port: 443})
     });
   }
 
diff --git a/third_party/WebKit/LayoutTests/platform/win/fast/canvas/canvas-blending-text-expected.txt b/third_party/WebKit/LayoutTests/platform/win/fast/canvas/canvas-blending-text-expected.txt
index f99aa32..7f9717a 100644
--- a/third_party/WebKit/LayoutTests/platform/win/fast/canvas/canvas-blending-text-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/fast/canvas/canvas-blending-text-expected.txt
@@ -4,81 +4,81 @@
 
 
 Testing blend mode source-over
-FAIL actualColor(4, 4)[0] should be within 5 of 255. Was 129.
-FAIL actualColor(4, 4)[1] should be within 5 of 129. Was 255.
+PASS actualColor(4, 4)[0] is within 5 of 255
+PASS actualColor(4, 4)[1] is within 5 of 129
 PASS actualColor(4, 4)[2] is within 5 of 129
 PASS actualColor(4, 4)[3] is within 5 of 255
 
 Testing blend mode multiply
 PASS actualColor(4, 4)[0] is within 5 of 129
-FAIL actualColor(4, 4)[1] should be within 5 of 129. Was 255.
-FAIL actualColor(4, 4)[2] should be within 5 of 65.25882352941176. Was 129.
+PASS actualColor(4, 4)[1] is within 5 of 129
+PASS actualColor(4, 4)[2] is within 5 of 65.25882352941176
 PASS actualColor(4, 4)[3] is within 5 of 255
 
 Testing blend mode screen
-FAIL actualColor(4, 4)[0] should be within 5 of 255. Was 129.
+PASS actualColor(4, 4)[0] is within 5 of 255
 PASS actualColor(4, 4)[1] is within 5 of 255
-FAIL actualColor(4, 4)[2] should be within 5 of 192.74117647058821. Was 129.
+PASS actualColor(4, 4)[2] is within 5 of 192.74117647058821
 PASS actualColor(4, 4)[3] is within 5 of 255
 
 Testing blend mode overlay
-FAIL actualColor(4, 4)[0] should be within 5 of 255. Was 129.
+PASS actualColor(4, 4)[0] is within 5 of 255
 PASS actualColor(4, 4)[1] is within 5 of 255
 PASS actualColor(4, 4)[2] is within 5 of 130.48235294117646
 PASS actualColor(4, 4)[3] is within 5 of 255
 
 Testing blend mode darken
 PASS actualColor(4, 4)[0] is within 5 of 129
-FAIL actualColor(4, 4)[1] should be within 5 of 129. Was 255.
+PASS actualColor(4, 4)[1] is within 5 of 129
 PASS actualColor(4, 4)[2] is within 5 of 129
 PASS actualColor(4, 4)[3] is within 5 of 255
 
 Testing blend mode lighten
-FAIL actualColor(4, 4)[0] should be within 5 of 255. Was 129.
+PASS actualColor(4, 4)[0] is within 5 of 255
 PASS actualColor(4, 4)[1] is within 5 of 255
 PASS actualColor(4, 4)[2] is within 5 of 129
 PASS actualColor(4, 4)[3] is within 5 of 255
 
 Testing blend mode color-dodge
-FAIL actualColor(4, 4)[0] should be within 5 of 255. Was 129.
+PASS actualColor(4, 4)[0] is within 5 of 255
 PASS actualColor(4, 4)[1] is within 5 of 255
-FAIL actualColor(4, 4)[2] should be within 5 of 255. Was 129.
+PASS actualColor(4, 4)[2] is within 5 of 255
 PASS actualColor(4, 4)[3] is within 5 of 255
 
 Testing blend mode color-burn
 PASS actualColor(4, 4)[0] is within 5 of 129
 PASS actualColor(4, 4)[1] is within 5 of 255
-FAIL actualColor(4, 4)[2] should be within 5 of 5.930232558139517. Was 129.
+PASS actualColor(4, 4)[2] is within 5 of 5.930232558139517
 PASS actualColor(4, 4)[3] is within 5 of 255
 
 Testing blend mode hard-light
-FAIL actualColor(4, 4)[0] should be within 5 of 255. Was 129.
+PASS actualColor(4, 4)[0] is within 5 of 255
 PASS actualColor(4, 4)[1] is within 5 of 255
 PASS actualColor(4, 4)[2] is within 5 of 130.48235294117646
 PASS actualColor(4, 4)[3] is within 5 of 255
 
 Testing blend mode soft-light
-FAIL actualColor(4, 4)[0] should be within 5 of 181.3697880023021. Was 129.
+PASS actualColor(4, 4)[0] is within 5 of 181.3697880023021
 PASS actualColor(4, 4)[1] is within 5 of 255
 PASS actualColor(4, 4)[2] is within 5 of 129.61611515296823
 PASS actualColor(4, 4)[3] is within 5 of 255
 
 Testing blend mode difference
 PASS actualColor(4, 4)[0] is within 5 of 126
-FAIL actualColor(4, 4)[1] should be within 5 of 126. Was 255.
-FAIL actualColor(4, 4)[2] should be within 5 of 0. Was 129.
+PASS actualColor(4, 4)[1] is within 5 of 126
+PASS actualColor(4, 4)[2] is within 5 of 0
 PASS actualColor(4, 4)[3] is within 5 of 255
 
 Testing blend mode exclusion
 PASS actualColor(4, 4)[0] is within 5 of 126
-FAIL actualColor(4, 4)[1] should be within 5 of 126. Was 255.
+PASS actualColor(4, 4)[1] is within 5 of 126
 PASS actualColor(4, 4)[2] is within 5 of 127.48235294117649
 PASS actualColor(4, 4)[3] is within 5 of 255
 
 Testing blend mode hue
-FAIL actualColor(4, 4)[0] should be within 5 of 255. Was 129.
-FAIL actualColor(4, 4)[1] should be within 5 of 181. Was 255.
-FAIL actualColor(4, 4)[2] should be within 5 of 181. Was 129.
+PASS actualColor(4, 4)[0] is within 5 of 255
+PASS actualColor(4, 4)[1] is within 5 of 181
+PASS actualColor(4, 4)[2] is within 5 of 181
 PASS actualColor(4, 4)[3] is within 5 of 255
 
 Testing blend mode saturation
@@ -88,15 +88,15 @@
 PASS actualColor(4, 4)[3] is within 5 of 255
 
 Testing blend mode color
-FAIL actualColor(4, 4)[0] should be within 5 of 255. Was 129.
-FAIL actualColor(4, 4)[1] should be within 5 of 181. Was 255.
-FAIL actualColor(4, 4)[2] should be within 5 of 181. Was 129.
+PASS actualColor(4, 4)[0] is within 5 of 255
+PASS actualColor(4, 4)[1] is within 5 of 181
+PASS actualColor(4, 4)[2] is within 5 of 181
 PASS actualColor(4, 4)[3] is within 5 of 255
 
 Testing blend mode luminosity
-FAIL actualColor(4, 4)[0] should be within 5 of 92. Was 129.
-FAIL actualColor(4, 4)[1] should be within 5 of 218. Was 255.
-FAIL actualColor(4, 4)[2] should be within 5 of 92. Was 129.
+PASS actualColor(4, 4)[0] is within 5 of 92
+PASS actualColor(4, 4)[1] is within 5 of 218
+PASS actualColor(4, 4)[2] is within 5 of 92
 PASS actualColor(4, 4)[3] is within 5 of 255
 
 PASS successfullyParsed is true
diff --git a/third_party/blink/renderer/core/css/css_selector.cc b/third_party/blink/renderer/core/css/css_selector.cc
index 58b9234b..bfb6bfd 100644
--- a/third_party/blink/renderer/core/css/css_selector.cc
+++ b/third_party/blink/renderer/core/css/css_selector.cc
@@ -104,7 +104,7 @@
     return 0;
   switch (match_) {
     case kId:
-      return kIdSpecificity;
+      return 0x010000;
     case kPseudoClass:
       switch (GetPseudoType()) {
         case kPseudoHost:
@@ -122,7 +122,7 @@
         default:
           break;
       }
-      return kClassLikeSpecificity;
+      return 0x000100;
     case kClass:
     case kPseudoElement:
     case kAttributeExact:
@@ -132,11 +132,11 @@
     case kAttributeContain:
     case kAttributeBegin:
     case kAttributeEnd:
-      return kClassLikeSpecificity;
+      return 0x000100;
     case kTag:
       if (TagQName().LocalName() == UniversalSelectorAtom())
         return 0;
-      return kTagSpecificity;
+      return 0x000001;
     case kUnknown:
       return 0;
   }
diff --git a/third_party/blink/renderer/core/css/css_selector.h b/third_party/blink/renderer/core/css/css_selector.h
index b32eb9e..ff52599 100644
--- a/third_party/blink/renderer/core/css/css_selector.h
+++ b/third_party/blink/renderer/core/css/css_selector.h
@@ -103,10 +103,6 @@
 
   bool operator==(const CSSSelector&) const;
 
-  static constexpr unsigned kIdSpecificity = 0x010000;
-  static constexpr unsigned kClassLikeSpecificity = 0x000100;
-  static constexpr unsigned kTagSpecificity = 0x000001;
-
   // http://www.w3.org/TR/css3-selectors/#specificity
   // We use 256 as the base of the specificity number system.
   unsigned Specificity() const;
diff --git a/third_party/blink/renderer/core/css/selector_checker.cc b/third_party/blink/renderer/core/css/selector_checker.cc
index 7dc4587..ca097214 100644
--- a/third_party/blink/renderer/core/css/selector_checker.cc
+++ b/third_party/blink/renderer/core/css/selector_checker.cc
@@ -1170,8 +1170,6 @@
       sub_context.treat_shadow_host_as_normal_scope = false;
 
       // ::slotted() only allows one compound selector.
-      //
-      // TODO(emilio): Should account for specificity here.
       DCHECK(selector.SelectorList()->First());
       DCHECK(!CSSSelectorList::Next(*selector.SelectorList()->First()));
       sub_context.selector = selector.SelectorList()->First();
@@ -1205,10 +1203,8 @@
   DCHECK(IsShadowHost(element));
 
   // For the case with no parameters, i.e. just :host.
-  if (!selector.SelectorList()) {
-    result.specificity += CSSSelector::kClassLikeSpecificity;
+  if (!selector.SelectorList())
     return true;
-  }
 
   SelectorCheckingContext sub_context(context);
   sub_context.is_sub_selector = true;
@@ -1249,7 +1245,7 @@
     } while (next_element);
   }
   if (matched) {
-    result.specificity += max_specificity + CSSSelector::kClassLikeSpecificity;
+    result.specificity += max_specificity;
     return true;
   }
 
diff --git a/third_party/blink/renderer/core/frame/visual_viewport.cc b/third_party/blink/renderer/core/frame/visual_viewport.cc
index c88c42f6..39f9cb41 100644
--- a/third_party/blink/renderer/core/frame/visual_viewport.cc
+++ b/third_party/blink/renderer/core/frame/visual_viewport.cc
@@ -138,9 +138,8 @@
   }
 
   {
-    TransformationMatrix scale_transform;
-    scale_transform.Scale(Scale());
-    TransformPaintPropertyNode::State state{scale_transform, FloatPoint3D()};
+    TransformPaintPropertyNode::State state;
+    state.matrix.Scale(Scale());
     state.compositor_element_id = GetCompositorElementId();
 
     if (!scale_transform_node_) {
@@ -180,13 +179,11 @@
   }
 
   {
-    TransformationMatrix translate_transform;
+    TransformPaintPropertyNode::State state;
     ScrollOffset scroll_position = GetScrollOffset();
-    translate_transform.Translate(-scroll_position.Width(),
-                                  -scroll_position.Height());
-    TransformPaintPropertyNode::State state{translate_transform,
-                                            FloatPoint3D()};
+    state.matrix.Translate(-scroll_position.Width(), -scroll_position.Height());
     state.scroll = scroll_node_;
+    state.is_identity_or_2d_translation = true;
     if (!translation_transform_node_) {
       translation_transform_node_ = TransformPaintPropertyNode::Create(
           *scale_transform_node_, std::move(state));
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index 5ae2070..8ac913c 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -4127,7 +4127,7 @@
       // This mapping happens for inline box whose LayoutObject is a LayoutBlock
       // whose VisualRect is not in the same transform space as the inline box.
       // For now this happens for EllipsisBox only.
-      DCHECK(scroll_translation->Matrix().IsIdentityOr2DTranslation());
+      DCHECK(scroll_translation->IsIdentityOr2DTranslation());
       auto float_visual_rect = FloatRect(visual_rect);
       float_visual_rect.Move(-scroll_translation->Matrix().To2DTranslation());
       return EnclosingLayoutRect(float_visual_rect);
diff --git a/third_party/blink/renderer/core/layout/layout_object.h b/third_party/blink/renderer/core/layout/layout_object.h
index 31c1596..74f68cb 100644
--- a/third_party/blink/renderer/core/layout/layout_object.h
+++ b/third_party/blink/renderer/core/layout/layout_object.h
@@ -1082,11 +1082,18 @@
   // (point 3 above).
   LayoutObject* Container(AncestorSkipInfo* = nullptr) const;
   // Finds the container as if this object is fixed-position.
-  LayoutBlock* ContainingBlockForFixedPosition(
-      AncestorSkipInfo* = nullptr) const;
-  // Finds the containing block as if this object is absolute-position.
+  LayoutObject* ContainerForAbsolutePosition(AncestorSkipInfo* = nullptr) const;
+  // Finds the container as if this object is absolute-position.
+  LayoutObject* ContainerForFixedPosition(AncestorSkipInfo* = nullptr) const;
+
+  // Returns ContainerForAbsolutePosition() if it's a LayoutBlock, or the
+  // containing LayoutBlock of it.
   LayoutBlock* ContainingBlockForAbsolutePosition(
       AncestorSkipInfo* = nullptr) const;
+  // Returns ContainerForFixedPosition() if it's a LayoutBlock, or the
+  // containing LayoutBlock of it.
+  LayoutBlock* ContainingBlockForFixedPosition(
+      AncestorSkipInfo* = nullptr) const;
 
   bool CanContainOutOfFlowPositionedElement(EPosition position) const {
     DCHECK(position == EPosition::kAbsolute || position == EPosition::kFixed);
@@ -2326,9 +2333,6 @@
 
   inline void InvalidateContainerPreferredLogicalWidths();
 
-  LayoutObject* ContainerForAbsolutePosition(AncestorSkipInfo* = nullptr) const;
-  LayoutObject* ContainerForFixedPosition(AncestorSkipInfo* = nullptr) const;
-
   const LayoutBoxModelObject* EnclosingCompositedContainer() const;
 
   LayoutFlowThread* LocateFlowThreadContainingBlock() const;
diff --git a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
index 15325a0..0961002 100644
--- a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
+++ b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
@@ -2762,7 +2762,7 @@
 }
 
 void CompositedLayerMapping::UpdateBackgroundColor() {
-  auto color = LayoutObjectBackgroundColor();
+  auto color = LayoutObjectBackgroundColor().Rgb();
   graphics_layer_->SetBackgroundColor(color);
   if (scrolling_contents_layer_)
     scrolling_contents_layer_->SetBackgroundColor(color);
diff --git a/third_party/blink/renderer/core/paint/compositing/graphics_layer_tree_as_text.cc b/third_party/blink/renderer/core/paint/compositing/graphics_layer_tree_as_text.cc
index 7e2f21d..6826536 100644
--- a/third_party/blink/renderer/core/paint/compositing/graphics_layer_tree_as_text.cc
+++ b/third_party/blink/renderer/core/paint/compositing/graphics_layer_tree_as_text.cc
@@ -45,8 +45,10 @@
   if (!transform.IsIdentity())
     json.SetArray("transform", TransformAsJSONArray(transform));
 
-  if (!transform.IsIdentityOrTranslation())
-    json.SetArray("origin", PointAsJSONArray(layer->TransformOrigin()));
+  if (!transform.IsIdentityOrTranslation()) {
+    json.SetArray("origin",
+                  PointAsJSONArray(FloatPoint3D(layer->TransformOrigin())));
+  }
 
   AddFlattenInheritedTransformJSON(layer, json);
 
@@ -117,9 +119,9 @@
   if (flags & kLayerTreeIncludesDebugInfo)
     json->SetString("client", PointerAsString(&layer->Client()));
 
-  if (layer->BackgroundColor().Alpha()) {
+  if (Color(layer->BackgroundColor()).Alpha()) {
     json->SetString("backgroundColor",
-                    layer->BackgroundColor().NameForLayoutTreeAsText());
+                    Color(layer->BackgroundColor()).NameForLayoutTreeAsText());
   }
 
   if (flags & kOutputAsLayerTree) {
diff --git a/third_party/blink/renderer/core/paint/paint_layer_clipper.cc b/third_party/blink/renderer/core/paint/paint_layer_clipper.cc
index 63a13cb..d9408bf 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_clipper.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_clipper.cc
@@ -281,28 +281,10 @@
   } else {
     layer_bounds.SetLocation(LayoutPoint(context.sub_pixel_accumulation));
     layer_bounds.MoveBy(fragment_data.PaintOffset());
-
-    const auto* current_transform = fragment_data.PreTransform();
-    const auto* root_transform =
-        context.root_fragment->LocalBorderBoxProperties().Transform();
-    if (current_transform != root_transform) {
-      if (current_transform->RequiresCompositingForRootScroller()) {
-        // This is a fast-path for computing the SourceToDestinationProjection
-        // when the current transform is the root scroller's scroll translation.
-        const auto& transform = current_transform->Matrix();
-#if DCHECK_IS_ON()
-        DCHECK(transform.IsIdentityOr2DTranslation());
-        DCHECK(transform.ApproximatelyEquals(
-            GeometryMapper::SourceToDestinationProjection(current_transform,
-                                                          root_transform)));
-#endif
-        layer_bounds.Move(
-            LayoutSize(LayoutUnit(transform.E()), LayoutUnit(transform.F())));
-      } else {
-        GeometryMapper::SourceToDestinationRect(current_transform,
-                                                root_transform, layer_bounds);
-      }
-    }
+    GeometryMapper::SourceToDestinationRect(
+        fragment_data.PreTransform(),
+        context.root_fragment->LocalBorderBoxProperties().Transform(),
+        layer_bounds);
     layer_bounds.MoveBy(-context.root_fragment->PaintOffset());
   }
 
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index a6ada6c..0e203bc 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -401,6 +401,7 @@
                            paint_offset_translation->Y());
     state.flattens_inherited_transform =
         context_.current.should_flatten_inherited_transform;
+    state.is_identity_or_2d_translation = true;
 
     state.affected_by_outer_viewport_bounds_delta =
         object_.StyleRef().GetPosition() == EPosition::kFixed &&
@@ -438,7 +439,7 @@
       FloatSize sticky_offset(box_model.StickyPositionOffset());
       TransformPaintPropertyNode::State state{AffineTransform::Translation(
           sticky_offset.Width(), sticky_offset.Height())};
-
+      state.is_identity_or_2d_translation = true;
       state.compositor_element_id = CompositorElementIdFromUniqueObjectId(
           box_model.UniqueId(),
           CompositorElementIdNamespace::kStickyTranslation);
@@ -775,15 +776,14 @@
   const auto* layer = ToLayoutBoxModelObject(object_).Layer();
   // Out-of-flow descendants not contained by this object may escape clips.
   if (layer->HasNonContainedAbsolutePositionDescendant() &&
-      object_.ContainingBlockForAbsolutePosition()
+      object_.ContainerForAbsolutePosition()
               ->FirstFragment()
               .PostOverflowClip() != context_.current.clip)
     return false;
   if (layer->HasFixedPositionDescendant() &&
       !object_.CanContainFixedPositionObjects() &&
-      object_.ContainingBlockForFixedPosition()
-              ->FirstFragment()
-              .PostOverflowClip() != context_.current.clip)
+      object_.ContainerForFixedPosition()->FirstFragment().PostOverflowClip() !=
+          context_.current.clip)
     return false;
 
   // Some descendants under a pagination container (e.g. composited objects
@@ -1646,6 +1646,7 @@
       state.matrix.Translate(-scroll_position.X(), -scroll_position.Y());
       state.flattens_inherited_transform =
           context_.current.should_flatten_inherited_transform;
+      state.is_identity_or_2d_translation = true;
       state.direct_compositing_reasons = CompositingReasonsForScroll(box);
       if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled() ||
           RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled()) {
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
index 63918af..79372c1d 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
@@ -6323,4 +6323,54 @@
   EXPECT_FLOAT_EQ(properties->Effect()->Opacity(), 0.5f);
 }
 
+TEST_P(PaintPropertyTreeBuilderTest, EffectOutputClipWithFixedDescendant) {
+  SetBodyInnerHTML(R"HTML(
+    <!-- Case 1: No clip. -->
+    <div id="target1" style="opacity: 0.1">
+      <div style="position: fixed"></div>
+    </div>
+    <!-- Case 2: Clip under the container of fixed-position (the LayoutView) -->
+    <div style="overflow: hidden">
+      <div id="target2" style="opacity: 0.1">
+        <div style="position: fixed"></div>
+      </div>
+    </div>
+    <!-- Case 3: Clip above the container of fixed-position. -->
+    <div id="clip3" style="overflow: hidden">
+      <div style="transform: translateY(0)">
+        <div id="target3" style="opacity: 0.1">
+          <div style="position: fixed"></div>
+        </div>
+      </div>
+    </div>
+    <!-- Case 4: Clip on the container of fixed-position. -->
+    <div id="clip4" style="overflow: hidden; transform: translateY(0)">
+      <div id="target4" style="opacity: 0.1">
+        <div style="position: fixed"></div>
+      </div>
+    </div>
+    <!-- Case 5: The container of fixed-position is not a LayoutBlock. -->
+    <table>
+      <tr style="transform: translateY(0)">
+        <td id="target5" style="opacity: 0.1">
+          <div style="position: fixed"></div>
+        </td>
+      </tr>
+    </table>
+  )HTML");
+
+  EXPECT_EQ(DocContentClip(),
+            PaintPropertiesForElement("target1")->Effect()->OutputClip());
+  // OutputClip is null because the fixed descendant escapes the effect's
+  // current clip.
+  EXPECT_EQ(nullptr,
+            PaintPropertiesForElement("target2")->Effect()->OutputClip());
+  EXPECT_EQ(PaintPropertiesForElement("clip3")->OverflowClip(),
+            PaintPropertiesForElement("target3")->Effect()->OutputClip());
+  EXPECT_EQ(PaintPropertiesForElement("clip4")->OverflowClip(),
+            PaintPropertiesForElement("target4")->Effect()->OutputClip());
+  EXPECT_EQ(DocContentClip(),
+            PaintPropertiesForElement("target5")->Effect()->OutputClip());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/BUILD.gn b/third_party/blink/renderer/modules/BUILD.gn
index 0bd9a1f..a45ad319 100644
--- a/third_party/blink/renderer/modules/BUILD.gn
+++ b/third_party/blink/renderer/modules/BUILD.gn
@@ -83,6 +83,7 @@
   deps = [
     ":make_modules_generated",
     ":module_names",
+    "//net:net",
     "//third_party/blink/renderer/bindings/modules:generated",
     "//third_party/blink/renderer/bindings/modules/v8:bindings_modules_impl",
     "//third_party/blink/renderer/bindings/modules/v8:bindings_modules_origin_trial_features",
@@ -164,6 +165,7 @@
     "//third_party/blink/renderer/modules/xr",
     "//third_party/icu",
     "//third_party/webrtc/pc:libjingle_peerconnection",
+    "//third_party/webrtc/rtc_base:rtc_base",
     "//third_party/webrtc_overrides:init_webrtc",
     "//third_party/zlib",
   ]
@@ -297,6 +299,7 @@
     "payments/payment_test_helper.cc",
     "payments/payment_test_helper.h",
     "payments/payments_validators_test.cc",
+    "peerconnection/adapters/p2p_quic_transport_test.cc",
     "peerconnection/rtc_data_channel_test.cc",
     "peerconnection/rtc_ice_transport_test.cc",
     "peerconnection/rtc_peer_connection_test.cc",
@@ -339,6 +342,7 @@
   deps = [
     ":modules",
     ":modules_testing",
+    "//net:quic_test_tools",
     "//services/device/public/cpp/test:test_support",
     "//services/viz/public/interfaces:interfaces_blink",
     "//skia",
@@ -349,6 +353,7 @@
     "//third_party/blink/renderer/modules/storage:unit_tests",
     "//third_party/blink/renderer/platform",
     "//third_party/blink/renderer/platform/wtf",
+    "//third_party/webrtc/rtc_base:rtc_base",
     "//v8",
   ]
 }
diff --git a/third_party/blink/renderer/modules/peerconnection/BUILD.gn b/third_party/blink/renderer/modules/peerconnection/BUILD.gn
index dddd3f5d..e2b32d4 100644
--- a/third_party/blink/renderer/modules/peerconnection/BUILD.gn
+++ b/third_party/blink/renderer/modules/peerconnection/BUILD.gn
@@ -14,6 +14,16 @@
     "adapters/ice_transport_host.h",
     "adapters/ice_transport_proxy.cc",
     "adapters/ice_transport_proxy.h",
+    "adapters/p2p_quic_packet_transport.h",
+    "adapters/p2p_quic_stream.h",
+    "adapters/p2p_quic_stream_impl.cc",
+    "adapters/p2p_quic_stream_impl.h",
+    "adapters/p2p_quic_transport.h",
+    "adapters/p2p_quic_transport_factory.h",
+    "adapters/p2p_quic_transport_factory_impl.cc",
+    "adapters/p2p_quic_transport_factory_impl.h",
+    "adapters/p2p_quic_transport_impl.cc",
+    "adapters/p2p_quic_transport_impl.h",
     "adapters/web_rtc_cross_thread_copier.cc",
     "adapters/web_rtc_cross_thread_copier.h",
     "rtc_certificate.cc",
diff --git a/third_party/blink/renderer/modules/peerconnection/DEPS b/third_party/blink/renderer/modules/peerconnection/DEPS
index 1e08706b..8fe2aa3 100644
--- a/third_party/blink/renderer/modules/peerconnection/DEPS
+++ b/third_party/blink/renderer/modules/peerconnection/DEPS
@@ -7,4 +7,8 @@
     "+third_party/blink/renderer/modules/mediastream",
     "+third_party/blink/renderer/modules/modules_export.h",
     "+third_party/blink/renderer/modules/peerconnection",
+    "+net/third_party/quic",
+    "+net/quic/chromium",
+    "+net/quic",
+    "+net/test",
 ]
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_packet_transport.h b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_packet_transport.h
new file mode 100644
index 0000000..b9b76fe5
--- /dev/null
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_packet_transport.h
@@ -0,0 +1,69 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_P2P_QUIC_PACKET_TRANSPORT_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_P2P_QUIC_PACKET_TRANSPORT_H_
+
+#include "net/third_party/quic/core/quic_packet_writer.h"
+#include "net/third_party/quic/core/quic_types.h"
+#include "net/third_party/quic/platform/api/quic_export.h"
+
+namespace blink {
+
+// This is the interface for the underlying packet transport used by the
+// P2PQuicTransport for receiving and writing data. The standard
+// implementation of this interface uses an ICE transport.
+//
+// This object should be run entirely on the webrtc worker thread.
+class P2PQuicPacketTransport {
+ public:
+  // This is subclassed by the P2PQuicTransport so that it can receive incoming
+  // data. The standard case is for this to be the P2PQuicTransport.
+  // The P2PQuicPacketTransport will outlive the ReceiveDelegate.
+  class ReceiveDelegate {
+   public:
+    virtual ~ReceiveDelegate() = default;
+    virtual void OnPacketDataReceived(const char* data, size_t data_len) = 0;
+  };
+
+  // This is subclassed by the Writer, so that it is aware when the
+  // P2PQuicPacketTransport is ready to write data. The
+  // P2PQuicPacketTransport will outlive the WriteObserver.
+  class WriteObserver {
+   public:
+    virtual ~WriteObserver() = default;
+    virtual void OnCanWrite() = 0;
+  };
+
+  struct QuicPacket {
+    // This is taken from the quic::QuicConnection, and 0 means that it is not
+    // set. Packet numbers are used to provide metadata to the implementation of
+    // the P2PQuicPacketTransport, but this number is not used by the QUIC
+    // library itself.
+    quic::QuicPacketNumber packet_number;
+    const char* buffer;
+    size_t buf_len;
+  };
+
+  virtual ~P2PQuicPacketTransport() = default;
+
+  // Called by the P2PQuicPacketWriter (in quic_transport_factory_impl.cc) when
+  // writing QUIC packets to the network. Return the number of written bytes.
+  // Return 0 if the write is blocked.
+  virtual int WritePacket(const QuicPacket& packet) = 0;
+  // Sets the ReceiveDelegate for receiving packets.
+  // Since the ReceiveDelegate has a shorter lifetime than the
+  // P2PQuicPacketTransport, it must unset itself upon destruction.
+  virtual void SetReceiveDelegate(ReceiveDelegate* receive_delegate) = 0;
+  // Sets the WriteObserver for obsererving when it can write to the
+  // P2PQuicPacketTransport. Since the WriteObserver has a shorter lifetime than
+  // the P2PQuicPacketTransport, it must unset itself upon destruction.
+  virtual void SetWriteObserver(WriteObserver* write_observer) = 0;
+  // Returns true if the P2PQuicPacketTransport can write.
+  virtual bool Writable() = 0;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_P2P_QUIC_PACKET_TRANSPORT_H_
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream.h b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream.h
new file mode 100644
index 0000000..a7ae56f22
--- /dev/null
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream.h
@@ -0,0 +1,65 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_P2P_QUIC_STREAM_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_P2P_QUIC_STREAM_H_
+
+namespace blink {
+
+// The bidirectional QUIC stream object to be used by the RTCQuicStream Web
+// API. See: https://w3c.github.io/webrtc-quic/#quicstream*
+//
+// Lifetime: The P2PQuicStream is owned by the P2PQuicTransport, and can be
+// deleted after the stream is closed for reading and writing. This can happen
+// in 3 ways: 1) OnRemoteReset has been fired. 2) Calling Reset(). 3) Both
+// Finish() has been called and OnRemoteFinish has been fired.
+class P2PQuicStream {
+ public:
+  // Receives callbacks for receiving RST_STREAM frames or a STREAM_FRAME with
+  // the FIN bit set. The Delegate should be subclassed by an object that can
+  // post the task to the main JS thread. The delegate's lifetime should outlive
+  // this P2PQuicStream.
+  class Delegate {
+   public:
+    virtual ~Delegate() {}
+
+    // Called when the stream receives a RST_STREAM frame from the remote side.
+    // This means the stream is closed and can no longer read or write, and is
+    // deleted by the quic::QuicSession.
+    virtual void OnRemoteReset() {}
+
+    // Called when the P2PQuicStream has consumed all incoming data from the
+    // remote side up to the FIN bit. Consuming means that the data is marked
+    // as consumed by quic::QuicStreamSequencer, but the data has not
+    // necessarily been read by the application. If the stream has already
+    // finished writing, then upon consuming the FIN bit the stream can no
+    // longer read or write and is deleted by the quic::QuicSession.
+    virtual void OnRemoteFinish() {}
+  };
+
+  // Sends a RST_STREAM frame to the remote side. This closes the P2PQuicStream
+  // for reading & writing and it will be deleted by the quic::QuicSession. When
+  // the remote side receives the RST_STREAM frame it will close the stream for
+  // reading and writing and send a RST_STREAM frame back. Calling Reset() will
+  // not trigger OnRemoteReset to be called locally when the RST_STREAM frame is
+  // received from the remote side, because the local stream is already closed.
+  virtual void Reset() = 0;
+
+  // Sends a stream frame with the FIN bit set, which notifies the remote side
+  // that this stream is done writing. The stream can no longer write after
+  // calling this function. If the stream has already received a FIN bit, this
+  // will close the stream for reading & writing and it will be deleted by the
+  // quic::QuicSession.
+  virtual void Finish() = 0;
+
+  // Sets the delegate object, which must outlive the P2PQuicStream.
+  virtual void SetDelegate(Delegate* delegate) = 0;
+
+  // TODO:(https://crbug.com/874296): Create functions for reading and writing,
+  // specifically for waitForReadable/waitForWriteable.
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_P2P_QUIC_STREAM_H_
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream_impl.cc b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream_impl.cc
new file mode 100644
index 0000000..0526f81
--- /dev/null
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream_impl.cc
@@ -0,0 +1,66 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream_impl.h"
+
+#include "net/third_party/quic/core/quic_error_codes.h"
+
+namespace blink {
+
+P2PQuicStreamImpl::P2PQuicStreamImpl(quic::QuicStreamId id,
+                                     quic::QuicSession* session)
+    : quic::QuicStream(id, session, /*is_static=*/false) {}
+
+P2PQuicStreamImpl::~P2PQuicStreamImpl() {}
+
+void P2PQuicStreamImpl::OnDataAvailable() {
+  // We just drop the data by marking all data as immediately consumed.
+  sequencer()->MarkConsumed(sequencer()->ReadableBytes());
+  if (sequencer()->IsClosed()) {
+    // This means all data has been consumed up to the FIN bit.
+    OnFinRead();
+  }
+}
+
+void P2PQuicStreamImpl::Reset() {
+  if (rst_sent()) {
+    // No need to reset twice. This could have already been sent as consequence
+    // of receiving a RST_STREAM frame.
+    return;
+  }
+  quic::QuicStream::Reset(quic::QuicRstStreamErrorCode::QUIC_STREAM_CANCELLED);
+}
+
+void P2PQuicStreamImpl::Finish() {
+  // Should never call Finish twice.
+  DCHECK(!fin_sent());
+  quic::QuicStream::WriteOrBufferData("", /*fin=*/true, nullptr);
+}
+
+void P2PQuicStreamImpl::SetDelegate(P2PQuicStream::Delegate* delegate) {
+  delegate_ = delegate;
+}
+
+void P2PQuicStreamImpl::OnStreamReset(const quic::QuicRstStreamFrame& frame) {
+  // TODO(https://crbug.com/874296): If we get an incoming stream we need to
+  // make sure that the delegate is set before we have incoming data.
+  DCHECK(delegate_);
+  // Calling this on the QuicStream will ensure that the stream is closed
+  // for reading and writing and we send a RST frame to the remote side if
+  // we have not already.
+  quic::QuicStream::OnStreamReset(frame);
+  delegate_->OnRemoteReset();
+}
+
+void P2PQuicStreamImpl::OnFinRead() {
+  // TODO(https://crbug.com/874296): If we get an incoming stream we need to
+  // make sure that the delegate is set before we have incoming data.
+  DCHECK(delegate_);
+  // Calling this on the QuicStream ensures that the stream is closed
+  // for reading.
+  quic::QuicStream::OnFinRead();
+  delegate_->OnRemoteFinish();
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream_impl.h b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream_impl.h
new file mode 100644
index 0000000..81cb68d
--- /dev/null
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream_impl.h
@@ -0,0 +1,57 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_P2P_QUIC_STREAM_IMPL_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_P2P_QUIC_STREAM_IMPL_H_
+
+#include "net/third_party/quic/core/quic_session.h"
+#include "third_party/blink/renderer/modules/modules_export.h"
+#include "third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream.h"
+
+namespace blink {
+
+class MODULES_EXPORT P2PQuicStreamImpl final : public P2PQuicStream,
+                                               public quic::QuicStream {
+ public:
+  P2PQuicStreamImpl(quic::QuicStreamId id, quic::QuicSession* session);
+  ~P2PQuicStreamImpl() override;
+
+  // QuicStream overrides.
+  //
+  // Right now this marks the data as consumed and drops it.
+  // TODO(https://crbug.com/874296): We need to update this function for
+  // reading and consuming data properly while the main JavaScript thread is
+  // busy. See:
+  // https://w3c.github.io/webrtc-quic/#dom-rtcquicstream-waitforreadable
+  void OnDataAvailable() override;
+
+  // P2PQuicStream overrides
+  void SetDelegate(P2PQuicStream::Delegate* delegate) override;
+
+  void Reset() override;
+
+  void Finish() override;
+
+  // quic::QuicStream overrides
+  //
+  // Called by the quic::QuicSession when receiving a RST_STREAM frame from the
+  // remote side. This closes the stream for reading & writing (if not already
+  // closed), and sends a RST_STREAM frame if one has not been sent yet.
+  void OnStreamReset(const quic::QuicRstStreamFrame& frame) override;
+
+  // Called when the stream has finished consumed data up to the FIN bit from
+  // the quic::QuicStreamSequencer. This will close the underlying QuicStream
+  // for reading. This can be called either by the P2PQuicStreamImpl when
+  // reading data, or by the quic::QuicStreamSequencer if we're done reading &
+  // receive a stream frame with the FIN bit.
+  void OnFinRead() override;
+
+ private:
+  using quic::QuicStream::Reset;
+  Delegate* delegate_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_P2P_QUIC_STREAM_IMPL_H_
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport.h b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport.h
new file mode 100644
index 0000000..ace4820eb
--- /dev/null
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport.h
@@ -0,0 +1,77 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_P2P_QUIC_TRANSPORT_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_P2P_QUIC_TRANSPORT_H_
+
+#include "third_party/webrtc/rtc_base/sslfingerprint.h"
+
+namespace blink {
+
+class P2PQuicStream;
+
+// Used by the RTCQuicTransport Web API. This transport creates and manages
+// streams, handles negotiation, state changes and errors. Every
+// P2PQuicTransport function maps directly to a method in the RTCQuicTransport
+// Web API, i.e. RTCQuicTransport::stop() -->
+// P2PQuicTransport::Stop(). This allows posting just one task across
+// thread boundaries to execute a function.
+//
+// This object should be run entirely on the webrtc worker thread.
+class P2PQuicTransport {
+ public:
+  // Used for receiving callbacks from the P2PQuicTransport regarding QUIC
+  // connection changes, handshake success/failures and new QuicStreams being
+  // added from the remote side.
+  class Delegate {
+   public:
+    virtual ~Delegate() = default;
+    // Called when receiving a close frame from the remote side, due to
+    // calling P2PQuicTransport::Stop().
+    virtual void OnRemoteStopped() {}
+    // Called when the connection is closed due to a QUIC error. This can happen
+    // locally by the framer, or remotely by the peer.
+    virtual void OnConnectionFailed(const std::string& error_details,
+                                    bool from_remote) {}
+    // Called when the crypto handshake has completed and fingerprints have been
+    // verified.
+    virtual void OnConnected() {}
+
+    // Called when an incoming stream is received from the remote side. This
+    // stream is owned by the P2PQuicTransport. Its lifetime is managed by the
+    // P2PQuicTransport, and can be deleted when:
+    // - The P2PQuicStream becomes closed for reading and writing.
+    // - Stop() is called.
+    // - The P2PQuicTransport is deleted.
+    virtual void OnStream(P2PQuicStream* stream) {}
+  };
+
+  virtual ~P2PQuicTransport() = default;
+
+  // Closes the QuicConnection and sends a close frame to the remote side.
+  // This will trigger P2PQuicTransport::Delegate::OnRemoteClosed() on the
+  // remote side.
+  virtual void Stop() = 0;
+
+  // Starts the QUIC handshake negotiation and sets the remote fingerprints
+  // that were signaled through a secure channel. These fingerprints are used to
+  // verify the self signed remote certificate used in the QUIC handshake. See:
+  // https://w3c.github.io/webrtc-quic/#quic-transport*
+  virtual void Start(std::vector<std::unique_ptr<rtc::SSLFingerprint>>
+                         remote_fingerprints) = 0;
+
+  // Creates a new outgoing stream. This stream is owned by the
+  // P2PQuicTransport. Its lifetime is managed by the P2PQuicTransport,
+  // and can be deleted when:
+  // - The P2PQuicStream becomes closed for reading and writing.
+  // - Stop() is called.
+  // - The P2PQuicTransport is deleted.
+  virtual P2PQuicStream* CreateStream() = 0;
+
+  // TODO(https://crbug.com/874296): Consider adding a getter for the
+  // local fingerprints of the certificate(s) set in the constructor.
+};
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_P2P_QUIC_TRANSPORT_H_
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_factory.h b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_factory.h
new file mode 100644
index 0000000..49e9948fae
--- /dev/null
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_factory.h
@@ -0,0 +1,73 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_P2P_QUIC_TRANSPORT_FACTORY_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_P2P_QUIC_TRANSPORT_FACTORY_H_
+
+#include "third_party/blink/renderer/modules/modules_export.h"
+#include "third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_packet_transport.h"
+#include "third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport.h"
+#include "third_party/webrtc/rtc_base/rtccertificate.h"
+#include "third_party/webrtc/rtc_base/scoped_ref_ptr.h"
+
+namespace blink {
+
+// A simple config object for creating a P2PQuicTransport. It's constructor
+// guarantees that the required objects for creating a P2PQuicTransport are part
+// of the P2PQuicTransportConfig.
+struct P2PQuicTransportConfig final {
+  // This object is only moveable.
+  explicit P2PQuicTransportConfig(
+      P2PQuicTransport::Delegate* const delegate_in,
+      P2PQuicPacketTransport* const packet_transport_in,
+      const std::vector<rtc::scoped_refptr<rtc::RTCCertificate>>
+          certificates_in)
+      : packet_transport(packet_transport_in),
+        certificates(certificates_in),
+        delegate(delegate_in) {
+    DCHECK_GT(certificates.size(), 0u);
+    DCHECK(packet_transport);
+    DCHECK(delegate);
+  }
+  P2PQuicTransportConfig(const P2PQuicTransportConfig&) = delete;
+  P2PQuicTransportConfig& operator=(const P2PQuicTransportConfig&) = delete;
+  P2PQuicTransportConfig(P2PQuicTransportConfig&&) = default;
+  P2PQuicTransportConfig& operator=(P2PQuicTransportConfig&&) = default;
+  ~P2PQuicTransportConfig() = default;
+
+  //  The standard case is an ICE transport. It's lifetime will be managed by
+  //  the ICE transport objects and outlive the P2PQuicTransport.
+  P2PQuicPacketTransport* const packet_transport;
+  bool is_server = true;
+  // The certificates are owned by the P2PQuicTransport. These come from
+  // blink::RTCCertificates: https://www.w3.org/TR/webrtc/#dom-rtccertificate
+  const std::vector<rtc::scoped_refptr<rtc::RTCCertificate>> certificates;
+  // Mandatory for creating a P2PQuicTransport and must outlive
+  // the P2PQuicTransport. In the standard case the |delegate_| will be
+  // the object that owns the P2PQuicTransport.
+  P2PQuicTransport::Delegate* const delegate;
+  // When set to true the P2PQuicTransport will immediately be able
+  // to listen and respond to a crypto handshake upon construction.
+  // This will NOT start a handshake.
+  bool can_respond_to_crypto_handshake = true;
+};
+
+// For creating a P2PQuicTransport. This factory should be injected into
+// whichever object plans to own and use a P2PQuicTransport. The
+// P2PQuicTransportFactory needs to outlive the P2PQuicTransport it creates.
+//
+// This object should be run entirely on the webrtc worker thread.
+class P2PQuicTransportFactory {
+ public:
+  virtual ~P2PQuicTransportFactory() = default;
+
+  // Creates the P2PQuicTransport. This should be called on the same
+  // thread that the P2PQuicTransport will be used on.
+  virtual std::unique_ptr<P2PQuicTransport> CreateQuicTransport(
+      P2PQuicTransportConfig config) = 0;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_P2P_QUIC_TRANSPORT_FACTORY_H_
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_factory_impl.cc b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_factory_impl.cc
new file mode 100644
index 0000000..0f1a76c
--- /dev/null
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_factory_impl.cc
@@ -0,0 +1,194 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_factory_impl.h"
+#include "net/third_party/quic/core/quic_packet_writer.h"
+#include "third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_packet_transport.h"
+#include "third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_impl.h"
+#include "third_party/webrtc/rtc_base/rtccertificate.h"
+
+namespace blink {
+
+namespace {
+
+// The P2PQuicPacketWriter is a private helper class that implements the
+// QuicPacketWriter using a P2PQuicPacketTransport. This allows us to
+// connect our own packet transport for writing into the QuicConnection.
+// The normal case is using an ICE transport (packet_transport) for writing.
+class P2PQuicPacketWriter : public quic::QuicPacketWriter,
+                            public P2PQuicPacketTransport::WriteObserver {
+ public:
+  P2PQuicPacketWriter(P2PQuicPacketTransport* packet_transport)
+      : packet_transport_(packet_transport) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    DCHECK(packet_transport_);
+    packet_transport_->SetWriteObserver(this);
+  }
+
+  // This way the packet transport knows it no longer has a write observer and
+  // can DCHECK this on destruction.
+  ~P2PQuicPacketWriter() override {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    packet_transport_->SetWriteObserver(nullptr);
+  }
+
+  // Sets the QuicConnection (which owns this packet writer). This allows us
+  // to get the packet numbers of QUIC packets we write. The QuicConnection
+  // is created with a quic::QuicPacketWriter, so we can't set the connection
+  // in the constructor.
+  void InitializeWithQuicConnection(
+      const quic::QuicConnection* const connection) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    DCHECK(connection);
+    if (packet_transport_->Writable()) {
+      SetWritable();
+    }
+    connection_ = connection;
+  }
+
+  // quic::QuicPacketWriter overrides.
+
+  // Writes a QUIC packet to the network with the packet number as additional
+  // packet  info.
+  quic::WriteResult WritePacket(const char* buffer,
+                                size_t buf_len,
+                                const quic::QuicIpAddress& self_address,
+                                const quic::QuicSocketAddress& peer_address,
+                                quic::PerPacketOptions* options) override {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    DCHECK(connection_);
+    if (IsWriteBlocked()) {
+      return quic::WriteResult(quic::WRITE_STATUS_BLOCKED, EWOULDBLOCK);
+    }
+
+    P2PQuicPacketTransport::QuicPacket packet;
+    packet.packet_number = connection_->packet_generator().packet_number();
+    packet.buffer = buffer;
+    packet.buf_len = buf_len;
+    int bytes_written = packet_transport_->WritePacket(packet);
+    if (bytes_written <= 0) {
+      writable_ = false;
+      return quic::WriteResult(quic::WRITE_STATUS_BLOCKED, EWOULDBLOCK);
+    }
+    return quic::WriteResult(quic::WRITE_STATUS_OK, bytes_written);
+  }
+
+  bool IsWriteBlockedDataBuffered() const override {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    return false;
+  }
+
+  bool IsWriteBlocked() const override {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    return !writable_;
+  }
+
+  quic::QuicByteCount GetMaxPacketSize(
+      const quic::QuicSocketAddress& peer_address) const override {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    // This can be configured later.
+    return 1200;
+  }
+
+  void SetWritable() override {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    writable_ = true;
+  }
+
+  bool SupportsReleaseTime() const override {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    return false;
+  }
+
+  bool IsBatchMode() const override {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    return false;
+  }
+
+  char* GetNextWriteLocation(
+      const quic::QuicIpAddress& self_address,
+      const quic::QuicSocketAddress& peer_address) override {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    return nullptr;
+  }
+
+  quic::WriteResult Flush() override {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    return quic::WriteResult(quic::WRITE_STATUS_OK, 0);
+  }
+
+  // P2PQuicPacketTransport::WriteDelegate override.
+  void OnCanWrite() override {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    SetWritable();
+  }
+
+ private:
+  // The packet transport is owned by the P2PQuicSession, not the
+  // BlinkPacketWriter.
+  P2PQuicPacketTransport* packet_transport_;
+  // The QuicConnection owns this packet writer and will outlive it.
+  const quic::QuicConnection* connection_;
+
+  bool writable_ = false;
+  THREAD_CHECKER(thread_checker_);
+};
+
+// Creates the QuicConnection for the QuicSession. Currently this connection
+// uses a dummy address and ID. The |packet_writer| is a basic implementation
+// using the QuicTransportConfig::packet_transport for writing. The |helper|
+// and |alarm_factory| should be chromium specific implementations.
+std::unique_ptr<quic::QuicConnection> CreateQuicConnection(
+    bool is_server,
+    quic::QuicConnectionHelperInterface* helper,
+    quic::QuicPacketWriter* packet_writer,
+    quic::QuicAlarmFactory* alarm_factory) {
+  quic::QuicIpAddress ip;
+  ip.FromString("0.0.0.0");
+  quic::QuicSocketAddress dummy_address(ip, 0 /* Port */);
+  quic::Perspective perspective =
+      is_server ? quic::Perspective::IS_SERVER : quic::Perspective::IS_CLIENT;
+  return std::make_unique<quic::QuicConnection>(
+      0 /* dummy ID */, dummy_address, helper, alarm_factory, packet_writer,
+      /* owns_writer */ true, perspective, quic::CurrentSupportedVersions());
+}
+}  // namespace
+
+P2PQuicTransportFactoryImpl::P2PQuicTransportFactoryImpl(
+    quic::QuicClock* clock,
+    std::unique_ptr<quic::QuicAlarmFactory> alarm_factory)
+    : clock_(clock), alarm_factory_(std::move(alarm_factory)) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+}
+
+// The P2PQuicTransportImpl is created with Chromium specific QUIC objects:
+// QuicClock, QuicRandom, QuicConnectionHelper and QuicAlarmFactory.
+std::unique_ptr<P2PQuicTransport>
+P2PQuicTransportFactoryImpl::CreateQuicTransport(
+    P2PQuicTransportConfig config) {
+  DCHECK(config.packet_transport);
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  quic::QuicRandom* quic_random = quic::QuicRandom::GetInstance();
+  // The P2PQuicSession owns these chromium specific objects required
+  // by the QuicConnection. These outlive the QuicConnection itself.
+  std::unique_ptr<net::QuicChromiumConnectionHelper> helper =
+      std::make_unique<net::QuicChromiumConnectionHelper>(clock_, quic_random);
+
+  P2PQuicPacketWriter* packet_writer =
+      new P2PQuicPacketWriter(config.packet_transport);
+  std::unique_ptr<quic::QuicConnection> quic_connection = CreateQuicConnection(
+      config.is_server, helper.get(), packet_writer, alarm_factory_.get());
+  // It's okay for the quic::QuicConnection to have a P2PQuicPacketWriter before
+  // the P2PQuicPacketWriter is initialized, because the P2QuicPacketWriter
+  // won't be writable until this occurs.
+  packet_writer->InitializeWithQuicConnection(quic_connection.get());
+
+  // QUIC configurations for the session are specified here.
+  quic::QuicConfig quic_config;
+  return std::make_unique<P2PQuicTransportImpl>(
+      std::move(config), std::move(helper), std::move(quic_connection),
+      quic_config, clock_);
+}
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_factory_impl.h b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_factory_impl.h
new file mode 100644
index 0000000..6bc8354
--- /dev/null
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_factory_impl.h
@@ -0,0 +1,43 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_P2P_QUIC_TRANSPORT_FACTORY_IMPL_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_P2P_QUIC_TRANSPORT_FACTORY_IMPL_H_
+
+#include "net/third_party/quic/core/quic_connection.h"
+#include "third_party/blink/renderer/modules/modules_export.h"
+#include "third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_factory.h"
+#include "third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_impl.h"
+
+namespace blink {
+
+// For creating a P2PQuicTransportImpl to be used for the blink Web API -
+// RTCQuicTransport.
+//
+// This object should be run entirely on the webrtc worker thread.
+class MODULES_EXPORT P2PQuicTransportFactoryImpl final
+    : public P2PQuicTransportFactory {
+ public:
+  P2PQuicTransportFactoryImpl(
+      quic::QuicClock* clock,
+      std::unique_ptr<quic::QuicAlarmFactory> alarm_factory);
+  ~P2PQuicTransportFactoryImpl() override {}
+
+  // QuicTransportFactoryInterface override.
+  std::unique_ptr<P2PQuicTransport> CreateQuicTransport(
+      P2PQuicTransportConfig config) override;
+
+ private:
+  // This is used to create a QuicChromiumConnectionHelper for the session.
+  // Should outlive the P2PQuicTransportFactoryImpl.
+  quic::QuicClock* clock_;
+  // Used for alarms that drive the underlying QUIC library. Should use the same
+  // clock as |clock_|.
+  std::unique_ptr<quic::QuicAlarmFactory> alarm_factory_;
+  THREAD_CHECKER(thread_checker_);
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_P2P_QUIC_TRANSPORT_FACTORY_IMPL_H_
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_impl.cc b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_impl.cc
new file mode 100644
index 0000000..dca963b
--- /dev/null
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_impl.cc
@@ -0,0 +1,350 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#include "third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_impl.h"
+
+#include "net/quic/quic_chromium_connection_helper.h"
+#include "net/third_party/quic/core/crypto/proof_source.h"
+#include "net/third_party/quic/core/crypto/quic_random.h"
+#include "net/third_party/quic/core/quic_config.h"
+#include "net/third_party/quic/core/tls_client_handshaker.h"
+#include "net/third_party/quic/core/tls_server_handshaker.h"
+#include "net/third_party/quic/tools/quic_simple_crypto_server_stream_helper.h"
+
+namespace blink {
+
+namespace {
+
+static const char kClosingDetails[] = "Application closed connection.";
+static const size_t kHostnameLength = 32;
+
+// TODO(https://crbug.com/874300): Create a secure connection by implementing a
+// P2PProofSource and P2PProofVerifier and remove these once the TLS 1.3
+// handshake is implemented for QUIC. This will allow us to verify for both the
+// server and client:
+// - The self signed certificate fingerprint matches the remote
+//   fingerprint that was signaled.
+// - The peer owns the certificate, by verifying the signature of the hash of
+//   the handshake context.
+//
+// Used by QuicCryptoServerConfig to provide dummy proof credentials
+// (taken from quic/quartc).
+class DummyProofSource : public quic::ProofSource {
+ public:
+  DummyProofSource() {}
+  ~DummyProofSource() override {}
+
+  // ProofSource override.
+  void GetProof(const quic::QuicSocketAddress& server_addr,
+                const quic::QuicString& hostname,
+                const quic::QuicString& server_config,
+                quic::QuicTransportVersion transport_version,
+                quic::QuicStringPiece chlo_hash,
+                std::unique_ptr<Callback> callback) override {
+    quic::QuicReferenceCountedPointer<ProofSource::Chain> chain;
+    quic::QuicCryptoProof proof;
+    std::vector<quic::QuicString> certs;
+    certs.push_back("Dummy cert");
+    chain = new ProofSource::Chain(certs);
+    proof.signature = "Dummy signature";
+    proof.leaf_cert_scts = "Dummy timestamp";
+    callback->Run(true, chain, proof, nullptr /* details */);
+  }
+
+  quic::QuicReferenceCountedPointer<Chain> GetCertChain(
+      const quic::QuicSocketAddress& server_address,
+      const quic::QuicString& hostname) override {
+    return quic::QuicReferenceCountedPointer<Chain>();
+  }
+
+  void ComputeTlsSignature(
+      const quic::QuicSocketAddress& server_address,
+      const quic::QuicString& hostname,
+      uint16_t signature_algorithm,
+      quic::QuicStringPiece in,
+      std::unique_ptr<SignatureCallback> callback) override {
+    callback->Run(true, "Dummy signature");
+  }
+};
+
+// Used by QuicCryptoClientConfig to ignore the peer's credentials
+// and establish an insecure QUIC connection (taken from quic/quartc).
+class InsecureProofVerifier : public quic::ProofVerifier {
+ public:
+  InsecureProofVerifier() {}
+  ~InsecureProofVerifier() override {}
+
+  // ProofVerifier override.
+  quic::QuicAsyncStatus VerifyProof(
+      const quic::QuicString& hostname,
+      const uint16_t port,
+      const quic::QuicString& server_config,
+      quic::QuicTransportVersion transport_version,
+      quic::QuicStringPiece chlo_hash,
+      const std::vector<quic::QuicString>& certs,
+      const quic::QuicString& cert_sct,
+      const quic::QuicString& signature,
+      const quic::ProofVerifyContext* context,
+      quic::QuicString* error_details,
+      std::unique_ptr<quic::ProofVerifyDetails>* verify_details,
+      std::unique_ptr<quic::ProofVerifierCallback> callback) override {
+    return quic::QUIC_SUCCESS;
+  }
+
+  quic::QuicAsyncStatus VerifyCertChain(
+      const quic::QuicString& hostname,
+      const std::vector<quic::QuicString>& certs,
+      const quic::ProofVerifyContext* context,
+      quic::QuicString* error_details,
+      std::unique_ptr<quic::ProofVerifyDetails>* details,
+      std::unique_ptr<quic::ProofVerifierCallback> callback) override {
+    return quic::QUIC_SUCCESS;
+  }
+
+  std::unique_ptr<quic::ProofVerifyContext> CreateDefaultContext() override {
+    return nullptr;
+  }
+};
+
+// A dummy helper for a server crypto stream that accepts all client hellos
+// and generates a random connection ID.
+class DummyCryptoServerStreamHelper
+    : public quic::QuicCryptoServerStream::Helper {
+ public:
+  explicit DummyCryptoServerStreamHelper(quic::QuicRandom* random)
+      : random_(random) {}
+  ~DummyCryptoServerStreamHelper() override {}
+
+  quic::QuicConnectionId GenerateConnectionIdForReject(
+      quic::QuicConnectionId connection_id) const override {
+    return random_->RandUint64();
+  }
+
+  bool CanAcceptClientHello(const quic::CryptoHandshakeMessage& message,
+                            const quic::QuicSocketAddress& client_address,
+                            const quic::QuicSocketAddress& peer_address,
+                            const quic::QuicSocketAddress& self_address,
+                            quic::QuicString* error_details) const override {
+    return true;
+  }
+
+ private:
+  // Used to generate random connection IDs. Needs to outlive this.
+  quic::QuicRandom* random_;
+};
+}  // namespace
+
+P2PQuicTransportImpl::P2PQuicTransportImpl(
+    P2PQuicTransportConfig p2p_transport_config,
+    std::unique_ptr<net::QuicChromiumConnectionHelper> helper,
+    std::unique_ptr<quic::QuicConnection> connection,
+    const quic::QuicConfig& quic_config,
+    quic::QuicClock* clock)
+    : quic::QuicSession(connection.get(), nullptr /* visitor */, quic_config),
+      helper_(std::move(helper)),
+      connection_(std::move(connection)),
+      perspective_(p2p_transport_config.is_server
+                       ? quic::Perspective::IS_SERVER
+                       : quic::Perspective::IS_CLIENT),
+      packet_transport_(p2p_transport_config.packet_transport),
+      delegate_(p2p_transport_config.delegate),
+      clock_(clock) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(delegate_);
+  DCHECK(clock_);
+  DCHECK(packet_transport_);
+  DCHECK_GT(p2p_transport_config.certificates.size(), 0u);
+  if (p2p_transport_config.can_respond_to_crypto_handshake) {
+    InitializeCryptoStream();
+  }
+  // TODO(https://crbug.com/874296): The web API accepts multiple certificates,
+  // and we might want to pass these down to let QUIC decide on what to use.
+  certificate_ = p2p_transport_config.certificates[0];
+  packet_transport_->SetReceiveDelegate(this);
+}
+
+P2PQuicTransportImpl::~P2PQuicTransportImpl() {
+  packet_transport_->SetReceiveDelegate(nullptr);
+}
+
+void P2PQuicTransportImpl::Stop() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  if (IsClosed()) {
+    return;
+  }
+  // The error code used for the connection closing is
+  // quic::QUIC_CONNECTION_CANCELLED. This allows us to distinguish that the
+  // application closed the connection, as opposed to it closing from a
+  // failure/error.
+  connection_->CloseConnection(
+      quic::QuicErrorCode::QUIC_CONNECTION_CANCELLED, kClosingDetails,
+      quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+}
+
+void P2PQuicTransportImpl::Start(
+    std::vector<std::unique_ptr<rtc::SSLFingerprint>> remote_fingerprints) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK_EQ(remote_fingerprints_.size(), 0u);
+  DCHECK_GT(remote_fingerprints.size(), 0u);
+  if (IsClosed()) {
+    // We could have received a close from the remote side before calling this.
+    return;
+  }
+  // These will be used to verify the remote certificate during the handshake.
+  remote_fingerprints_ = std::move(remote_fingerprints);
+
+  if (perspective_ == quic::Perspective::IS_CLIENT) {
+    quic::QuicCryptoClientStream* client_crypto_stream =
+        static_cast<quic::QuicCryptoClientStream*>(crypto_stream_.get());
+    client_crypto_stream->CryptoConnect();
+  }
+}
+
+void P2PQuicTransportImpl::OnPacketDataReceived(const char* data,
+                                                size_t data_len) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  // Received data from the |packet_transport_|. Create a QUIC packet and send
+  // it to be processed by the QuicSession/Connection.
+  quic::QuicReceivedPacket packet(data, data_len, clock_->Now());
+  ProcessUdpPacket(connection()->self_address(), connection()->peer_address(),
+                   packet);
+}
+
+quic::QuicCryptoStream* P2PQuicTransportImpl::GetMutableCryptoStream() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  return crypto_stream_.get();
+}
+
+const quic::QuicCryptoStream* P2PQuicTransportImpl::GetCryptoStream() const {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  return crypto_stream_.get();
+}
+
+P2PQuicStreamImpl* P2PQuicTransportImpl::CreateStream() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  return CreateOutgoingDynamicStream();
+}
+
+P2PQuicStreamImpl* P2PQuicTransportImpl::CreateOutgoingDynamicStream() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  P2PQuicStreamImpl* stream = CreateStreamInternal(GetNextOutgoingStreamId());
+  ActivateStream(std::unique_ptr<P2PQuicStreamImpl>(stream));
+  return stream;
+}
+
+P2PQuicStreamImpl* P2PQuicTransportImpl::CreateIncomingDynamicStream(
+    quic::QuicStreamId id) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  P2PQuicStreamImpl* stream = CreateStreamInternal(id);
+  ActivateStream(std::unique_ptr<P2PQuicStreamImpl>(stream));
+  delegate_->OnStream(stream);
+  return stream;
+}
+
+P2PQuicStreamImpl* P2PQuicTransportImpl::CreateStreamInternal(
+    quic::QuicStreamId id) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(crypto_stream_);
+  DCHECK(IsEncryptionEstablished());
+  DCHECK(!IsClosed());
+  return new P2PQuicStreamImpl(id, this);
+}
+
+void P2PQuicTransportImpl::InitializeCryptoStream() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(!crypto_stream_);
+  switch (perspective_) {
+    case quic::Perspective::IS_CLIENT: {
+      if (!crypto_client_config_) {
+        // The |crypto_client_config_| has not already been set (by the test).
+        std::unique_ptr<quic::ProofVerifier> proof_verifier(
+            new InsecureProofVerifier);
+        crypto_client_config_ = std::make_unique<quic::QuicCryptoClientConfig>(
+            std::move(proof_verifier),
+            quic::TlsClientHandshaker::CreateSslCtx());
+      }
+      // The host must be unique for every endpoint the client communicates
+      // with.
+      char random_hostname[kHostnameLength];
+      helper_->GetRandomGenerator()->RandBytes(random_hostname,
+                                               kHostnameLength);
+      quic::QuicServerId server_id(
+          /*host=*/quic::QuicString(random_hostname, kHostnameLength),
+          /*port=*/0,
+          /*privacy_mode_enabled=*/false);
+      crypto_stream_ = std::make_unique<quic::QuicCryptoClientStream>(
+          server_id, /*QuicSession=*/this,
+          crypto_client_config_->proof_verifier()->CreateDefaultContext(),
+          crypto_client_config_.get(), /*ProofHandler=*/this);
+      QuicSession::Initialize();
+      break;
+    }
+    case quic::Perspective::IS_SERVER: {
+      std::unique_ptr<quic::ProofSource> proof_source(new DummyProofSource);
+      crypto_server_config_ = std::make_unique<quic::QuicCryptoServerConfig>(
+          quic::QuicCryptoServerConfig::TESTING, helper_->GetRandomGenerator(),
+          std::move(proof_source), quic::KeyExchangeSource::Default(),
+          quic::TlsServerHandshaker::CreateSslCtx());
+      // Provide server with serialized config string to prove ownership.
+      quic::QuicCryptoServerConfig::ConfigOptions options;
+      // The |message| is used to handle the return value of AddDefaultConfig
+      // which is raw pointer of the CryptoHandshakeMessage.
+      std::unique_ptr<quic::CryptoHandshakeMessage> message(
+          crypto_server_config_->AddDefaultConfig(
+              helper_->GetRandomGenerator(), helper_->GetClock(), options));
+      compressed_certs_cache_.reset(new quic::QuicCompressedCertsCache(
+          quic::QuicCompressedCertsCache::kQuicCompressedCertsCacheSize));
+      bool use_stateless_rejects_if_peer_supported = false;
+      server_stream_helper_ = std::make_unique<DummyCryptoServerStreamHelper>(
+          helper_->GetRandomGenerator());
+
+      crypto_stream_ = std::make_unique<quic::QuicCryptoServerStream>(
+          crypto_server_config_.get(), compressed_certs_cache_.get(),
+          use_stateless_rejects_if_peer_supported, this,
+          server_stream_helper_.get());
+      QuicSession::Initialize();
+      break;
+    }
+    default:
+      NOTREACHED();
+      break;
+  }
+}
+
+void P2PQuicTransportImpl::OnCryptoHandshakeEvent(CryptoHandshakeEvent event) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  QuicSession::OnCryptoHandshakeEvent(event);
+  if (event == HANDSHAKE_CONFIRMED) {
+    DCHECK(IsEncryptionEstablished());
+    DCHECK(IsCryptoHandshakeConfirmed());
+    delegate_->OnConnected();
+  }
+}
+
+void P2PQuicTransportImpl::OnConnectionClosed(
+    quic::QuicErrorCode error,
+    const std::string& error_details,
+    quic::ConnectionCloseSource source) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  quic::QuicSession::OnConnectionClosed(error, error_details, source);
+  if (error != quic::QuicErrorCode::QUIC_CONNECTION_CANCELLED) {
+    delegate_->OnConnectionFailed(
+        error_details, source == quic::ConnectionCloseSource::FROM_PEER);
+  } else if (source == quic::ConnectionCloseSource::FROM_PEER) {
+    // This connection was closed by the application of the remote side.
+    delegate_->OnRemoteStopped();
+  }
+}
+
+bool P2PQuicTransportImpl::IsClosed() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  return !connection_->connected();
+}
+
+void P2PQuicTransportImpl::set_crypto_client_config(
+    std::unique_ptr<quic::QuicCryptoClientConfig> crypto_client_config) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  crypto_client_config_ = std::move(crypto_client_config);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_impl.h b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_impl.h
new file mode 100644
index 0000000..75372c7
--- /dev/null
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_impl.h
@@ -0,0 +1,179 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_P2P_QUIC_TRANSPORT_IMPL_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_P2P_QUIC_TRANSPORT_IMPL_H_
+
+#include "base/threading/thread_checker.h"
+#include "net/quic/quic_chromium_connection_helper.h"
+#include "net/third_party/quic/core/crypto/quic_crypto_client_config.h"
+#include "net/third_party/quic/core/crypto/quic_crypto_server_config.h"
+#include "net/third_party/quic/core/quic_crypto_client_stream.h"
+#include "net/third_party/quic/core/quic_crypto_server_stream.h"
+#include "net/third_party/quic/core/quic_packet_writer.h"
+#include "net/third_party/quic/core/quic_session.h"
+#include "net/third_party/quic/tools/quic_simple_crypto_server_stream_helper.h"
+#include "third_party/blink/renderer/modules/modules_export.h"
+#include "third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_packet_transport.h"
+#include "third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream_impl.h"
+#include "third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport.h"
+#include "third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_factory.h"
+#include "third_party/webrtc/rtc_base/rtccertificate.h"
+
+namespace blink {
+
+// The P2PQuicTransportImpl subclasses the quic::QuicSession in order to expose
+// QUIC as a P2P transport. This specific subclass implements the crypto
+// handshake for a peer to peer connection, which requires verifying the remote
+// certificate's fingerprint, but otherwise exposes a raw quic::QuicSession.
+//
+// At a high level, the quic::QuicConnection manages the actual connection
+// between two endpoints, while the quic::QuicSession owns and manages the
+// quic::QuicStreams. The quic::QuicSession also handles the negotiation between
+// endpoints, session control (reset, window updates, control frames, etc.), and
+// callbacks from either the connection (quic::QuicConnectionVisitorInterface),
+// frames being acked or lost (quic::SessionNotifierInterface), or handshake
+// state changes.
+//
+// This object should be run entirely on the webrtc worker thread.
+class MODULES_EXPORT P2PQuicTransportImpl final
+    : public quic::QuicSession,
+      public P2PQuicTransport,
+      public P2PQuicPacketTransport::ReceiveDelegate,
+      public quic::QuicCryptoClientStream::ProofHandler {
+ public:
+  P2PQuicTransportImpl(
+      P2PQuicTransportConfig p2p_transport_config,
+      std::unique_ptr<net::QuicChromiumConnectionHelper> helper,
+      std::unique_ptr<quic::QuicConnection> connection,
+      const quic::QuicConfig& quic_config,
+      quic::QuicClock* clock);
+
+  ~P2PQuicTransportImpl() override;
+
+  // P2PQuicTransport overrides.
+
+  void Stop() override;
+
+  // Sets the remote fingerprints, and if the the P2PQuicTransportImpl is a
+  // client starts the QUIC handshake . This handshake is currently insecure,
+  // meaning that the certificates used are fake and are not verified. It also
+  // assumes a handshake for a server/client case. This must be called before
+  // creating any streams.
+  //
+  // TODO(https://crbug.com/874300): Verify both the client and server
+  // certificates with the signaled remote fingerprints. Until the TLS 1.3
+  // handshake is supported in the QUIC core library we can only verify the
+  // server's certificate, but not the client's. Note that this means
+  // implementing the handshake for a P2P case, in which case verification
+  // completes after both receiving the signaled remote fingerprint and getting
+  // a client hello. Because either can come first, a synchronous call to verify
+  // the remote fingerprint is not possible.
+  void Start(std::vector<std::unique_ptr<rtc::SSLFingerprint>>
+                 remote_fingerprints) override;
+
+  // Creates an outgoing stream that is owned by the quic::QuicSession.
+  P2PQuicStreamImpl* CreateStream() override;
+
+  // P2PQuicPacketTransport::Delegate override.
+  void OnPacketDataReceived(const char* data, size_t data_len) override;
+
+  // quic::QuicCryptoClientStream::ProofHandler overrides used in a client
+  // connection to get certificate verification details.
+
+  // Called when the proof verification completes. This information is used
+  // for 0 RTT handshakes, which isn't relevant for our P2P handshake.
+  void OnProofValid(
+      const quic::QuicCryptoClientConfig::CachedState& cached) override{};
+
+  // Called when proof verification become available.
+  void OnProofVerifyDetailsAvailable(
+      const quic::ProofVerifyDetails& verify_details) override{};
+
+  // quic::QuicConnectionVisitorInterface override.
+  void OnConnectionClosed(quic::QuicErrorCode error,
+                          const std::string& error_details,
+                          quic::ConnectionCloseSource source) override;
+
+ protected:
+  // quic::QuicSession overrides.
+  // TODO(https://crbug.com/874296): Subclass QuicStream and implement these
+  // functions.
+
+  // Creates a new stream initiated from the remote side. The caller does not
+  // own the stream, so the stream is activated and ownership is moved to the
+  // quic::QuicSession.
+  P2PQuicStreamImpl* CreateIncomingDynamicStream(
+      quic::QuicStreamId id) override;
+
+  // Creates a new outgoing stream. The caller does not own the
+  // stream, so the stream is activated and ownership is moved to the
+  // quic::QuicSession.
+  P2PQuicStreamImpl* CreateOutgoingDynamicStream() override;
+
+  void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) override;
+
+ private:
+  // This is for testing connection failures and handshake failures.
+  friend class P2PQuicTransportTest;
+
+  // These functions are used for testing.
+  //
+  // Returns true if the quic::QuicConnection has been closed remotely or
+  // locally.
+  bool IsClosed();
+  quic::QuicConnection* connection() { return connection_.get(); }
+  // Allows the test to set its own proof verification.
+  void set_crypto_client_config(
+      std::unique_ptr<quic::QuicCryptoClientConfig> crypto_client_config);
+
+  // quic::QuicSession overrides.
+  const quic::QuicCryptoStream* GetCryptoStream() const override;
+  quic::QuicCryptoStream* GetMutableCryptoStream() override;
+
+  // Creates the crypto stream necessary for handshake negotiation, and
+  // initializes the parent class (quic::QuicSession). This must be called on
+  // both sides before communicating between endpoints (Start, Close, etc.).
+  void InitializeCryptoStream();
+
+  // Creates a new stream. This helper function is used when we need to create
+  // a new incoming stream or outgoing stream.
+  P2PQuicStreamImpl* CreateStreamInternal(quic::QuicStreamId id);
+
+  // The server_config and client_config are used for setting up the crypto
+  // connection. The ownership of these objects or the objects they own
+  // (quic::ProofSource, quic::ProofVerifier, etc.), are not passed on to the
+  // QUIC library for the handshake, so we must own them here. A client
+  // |perspective_| will not have a crypto_server_config and vice versa.
+  std::unique_ptr<quic::QuicCryptoServerConfig> crypto_server_config_;
+  std::unique_ptr<quic::QuicCryptoClientConfig> crypto_client_config_;
+  // Used by server |crypto_stream_| to track most recently compressed certs.
+  std::unique_ptr<quic::QuicCompressedCertsCache> compressed_certs_cache_;
+  std::unique_ptr<quic::QuicCryptoServerStream::Helper> server_stream_helper_;
+  // Owned by the P2PQuicTransportImpl. |helper_| is placed before
+  // |connection_| to ensure it outlives it.
+  std::unique_ptr<net::QuicChromiumConnectionHelper> helper_;
+
+  std::unique_ptr<quic::QuicConnection> connection_;
+
+  std::unique_ptr<quic::QuicCryptoStream> crypto_stream_;
+  // Crypto information. Note that currently the handshake is insecure and these
+  // are not used...
+  rtc::scoped_refptr<rtc::RTCCertificate> certificate_;
+  std::vector<std::unique_ptr<rtc::SSLFingerprint>> remote_fingerprints_;
+
+  quic::Perspective perspective_;
+  // Outlives the P2PQuicTransport.
+  P2PQuicPacketTransport* packet_transport_;
+  P2PQuicTransport::Delegate* delegate_ = nullptr;
+  // Owned by whatever creates the P2PQuicTransportImpl. The |clock_| needs to
+  // outlive the P2PQuicTransportImpl.
+  quic::QuicClock* clock_ = nullptr;
+
+  THREAD_CHECKER(thread_checker_);
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_P2P_QUIC_TRANSPORT_IMPL_H_
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_test.cc b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_test.cc
new file mode 100644
index 0000000..c889ea9
--- /dev/null
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_test.cc
@@ -0,0 +1,1177 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_chromium_alarm_factory.h"
+#include "net/quic/test_task_runner.h"
+#include "net/test/gtest_util.h"
+#include "net/third_party/quic/core/tls_client_handshaker.h"
+#include "net/third_party/quic/test_tools/mock_clock.h"
+#include "third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_packet_transport.h"
+#include "third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_factory_impl.h"
+#include "third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_transport_impl.h"
+#include "third_party/webrtc/rtc_base/rtccertificate.h"
+#include "third_party/webrtc/rtc_base/sslfingerprint.h"
+#include "third_party/webrtc/rtc_base/sslidentity.h"
+
+namespace blink {
+
+namespace {
+
+// The types of callbacks that can be fired on a P2PQuicTransport::Delegate.
+enum class TransportCallbackType {
+  kNone,
+  kOnRemoteStopped,
+  kOnConnectionFailed,
+  kOnConnected,
+  kOnStream
+};
+
+// The types of callbacks that can be fired on a P2PQuicStream::Delegate.
+enum class StreamCallbackType { kNone, kOnRemoteReset, kOnRemoteFinish };
+
+// The QuicStreamDelegate implements counters for callbacks. It can also set
+// expectations for a specific callback. When an expectation is set the
+// quic::TestTaskRunner drives the test until the callbacks have been fired, and
+// we are no longer expecting the callback.
+class QuicStreamDelegateForTesting final : public P2PQuicStream::Delegate {
+ public:
+  ~QuicStreamDelegateForTesting() override {}
+
+  void OnRemoteReset() override {
+    if (callback_type_expected_ == StreamCallbackType::kOnRemoteReset) {
+      callback_type_expected_ = StreamCallbackType::kNone;
+    }
+    remote_reset_count_++;
+  };
+
+  void OnRemoteFinish() override {
+    if (callback_type_expected_ == StreamCallbackType::kOnRemoteFinish) {
+      callback_type_expected_ = StreamCallbackType::kNone;
+    }
+    remote_finish_count_++;
+  };
+
+  int remote_reset_count() { return remote_reset_count_; }
+
+  int remote_finish_count() { return remote_finish_count_; }
+
+  // Sets the type of callback expected to be called.
+  void ExpectCallback(StreamCallbackType callback_type) {
+    callback_type_expected_ = callback_type;
+  }
+
+  // Returns if we are expecting a callback that hasn't been fired yet.
+  bool IsExpectingCallback() const {
+    return callback_type_expected_ != StreamCallbackType::kNone;
+  }
+
+ private:
+  int remote_reset_count_ = 0;
+  int remote_finish_count_ = 0;
+  StreamCallbackType callback_type_expected_ = StreamCallbackType::kNone;
+};
+
+// Implements counters for callbacks. It can also set expectations for a
+// specific callback. When an expectation is set the quic::TestTaskRunner
+// drives the test until the callbacks have been fired, and we are no longer
+// expecting the callback.
+//
+// TODO(https://crbug.com/874296): If these files get moved to the platform
+// directory we will run the tests in a different test environment. In that case
+// it will make more sense to use the TestCompletionCallback and the RunLoop for
+// driving the test.
+class QuicTransportDelegateForTest final : public P2PQuicTransport::Delegate {
+ public:
+  ~QuicTransportDelegateForTest() override {}
+  void OnRemoteStopped() override {
+    if (callback_type_expected_ == TransportCallbackType::kOnRemoteStopped) {
+      callback_type_expected_ = TransportCallbackType::kNone;
+    }
+    stopped_count_++;
+  }
+
+  void OnConnectionFailed(const std::string& error_details,
+                          bool from_remote) override {
+    if (callback_type_expected_ == TransportCallbackType::kOnConnectionFailed) {
+      callback_type_expected_ = TransportCallbackType::kNone;
+    }
+    connection_failed_count_++;
+  }
+
+  void OnConnected() override {
+    if (callback_type_expected_ == TransportCallbackType::kOnConnected) {
+      callback_type_expected_ = TransportCallbackType::kNone;
+    }
+    connected_count_++;
+  }
+
+  // We store the remotely created stream.
+  void OnStream(P2PQuicStream* stream) override {
+    if (callback_type_expected_ == TransportCallbackType::kOnStream) {
+      callback_type_expected_ = TransportCallbackType::kNone;
+    }
+    streams_.push_back(static_cast<P2PQuicStreamImpl*>(stream));
+    on_stream_count_++;
+  }
+
+  int stopped_count() const { return stopped_count_; }
+
+  int connection_failed_count() const { return connection_failed_count_; }
+
+  int connected_count() const { return connected_count_; }
+
+  int on_stream_count() const { return on_stream_count_; }
+
+  // Sets the type of callback expected to be called.
+  void ExpectCallback(TransportCallbackType callback_type) {
+    callback_type_expected_ = callback_type;
+  }
+
+  // Returns if we are expecting a callback that hasn't been fired yet.
+  bool IsExpectingCallback() const {
+    return callback_type_expected_ != TransportCallbackType::kNone;
+  }
+
+  std::vector<P2PQuicStreamImpl*> streams() const { return streams_; }
+
+ private:
+  TransportCallbackType callback_type_expected_ = TransportCallbackType::kNone;
+  int stopped_count_ = 0;
+  int connection_failed_count_ = 0;
+  int connected_count_ = 0;
+  int on_stream_count_ = 0;
+  // The delegates created for each stream as a result of the remote side
+  // creating streams and sending data (triggering OnStream). P2PQuicStreamsImpl
+  // are owned by the P2PQuicTransport.
+  std::vector<P2PQuicStreamImpl*> streams_;
+};
+
+// This is a fake packet transport to be used by the P2PQuicTransportImpl. It
+// allows to easily connect two packet transports together. We send packets
+// asynchronously, by using the same alarm factory that is being used for the
+// underlying QUIC library.
+class FakePacketTransport : public P2PQuicPacketTransport,
+                            public quic::QuicAlarm::Delegate {
+ public:
+  FakePacketTransport(quic::QuicAlarmFactory* alarm_factory,
+                      quic::MockClock* clock)
+      : alarm_(alarm_factory->CreateAlarm(new AlarmDelegate(this))),
+        clock_(clock) {}
+  ~FakePacketTransport() override {
+    // The write observer should be unset when it is destroyed.
+    DCHECK(!write_observer_);
+  };
+
+  // Called by QUIC for writing data to the other side. The flow for writing a
+  // packet is P2PQuicTransportImpl --> quic::QuicConnection -->
+  // quic::QuicPacketWriter --> FakePacketTransport. In this case the
+  // FakePacketTransport just writes directly to the FakePacketTransport on the
+  // other side.
+  int WritePacket(const QuicPacket& packet) override {
+    // For the test there should always be a peer_packet_transport_ connected at
+    // this point.
+    if (!peer_packet_transport_) {
+      return 0;
+    }
+    last_packet_num_ = packet.packet_number;
+    packet_queue_.emplace_back(packet.buffer, packet.buf_len);
+    alarm_->Cancel();
+    // We don't want get 0 RTT.
+    alarm_->Set(clock_->Now() + quic::QuicTime::Delta::FromMicroseconds(10));
+
+    return packet.buf_len;
+  }
+
+  // Sets the P2PQuicTransportImpl as the delegate.
+  void SetReceiveDelegate(
+      P2PQuicPacketTransport::ReceiveDelegate* delegate) override {
+    // We can't set two ReceiveDelegates for one packet transport.
+    DCHECK(!delegate_ || !delegate);
+    delegate_ = delegate;
+  }
+
+  void SetWriteObserver(
+      P2PQuicPacketTransport::WriteObserver* write_observer) override {
+    // We can't set two WriteObservers for one packet transport.
+    DCHECK(!write_observer_ || !write_observer);
+    write_observer_ = write_observer;
+  }
+
+  bool Writable() override { return true; }
+
+  // Connects the other FakePacketTransport, so we can write to the peer.
+  void ConnectPeerTransport(FakePacketTransport* peer_packet_transport) {
+    DCHECK(!peer_packet_transport_);
+    peer_packet_transport_ = peer_packet_transport;
+  }
+
+  // Disconnects the delegate, so we no longer write to it. The test must call
+  // this before destructing either of the packet transports!
+  void DisconnectPeerTransport(FakePacketTransport* peer_packet_transport) {
+    DCHECK(peer_packet_transport_ == peer_packet_transport);
+    peer_packet_transport_ = nullptr;
+  }
+
+  // The callback used in order for us to communicate between
+  // FakePacketTransports.
+  void OnDataReceivedFromPeer(const char* data, size_t data_len) {
+    DCHECK(delegate_);
+    delegate_->OnPacketDataReceived(data, data_len);
+  }
+
+  int last_packet_num() { return last_packet_num_; }
+
+ private:
+  // Wraps the FakePacketTransport so that we can pass in a raw pointer that can
+  // be reference counted when calling CreateAlarm().
+  class AlarmDelegate : public quic::QuicAlarm::Delegate {
+   public:
+    explicit AlarmDelegate(FakePacketTransport* packet_transport)
+        : packet_transport_(packet_transport) {}
+
+    void OnAlarm() override { packet_transport_->OnAlarm(); }
+
+   private:
+    FakePacketTransport* packet_transport_;
+  };
+
+  // Called when we should write any buffered data.
+  void OnAlarm() override {
+    // Send the data to the peer at this point.
+    peer_packet_transport_->OnDataReceivedFromPeer(
+        packet_queue_.front().c_str(), packet_queue_.front().length());
+    packet_queue_.pop_front();
+
+    // If there's more packets to be sent out, reset the alarm to send it as the
+    // next task.
+    if (!packet_queue_.empty()) {
+      alarm_->Cancel();
+      alarm_->Set(clock_->Now());
+    }
+  }
+  // If async, packets are queued here to send.
+  quic::QuicDeque<quic::QuicString> packet_queue_;
+  // Alarm used to send data asynchronously.
+  quic::QuicArenaScopedPtr<quic::QuicAlarm> alarm_;
+  // The P2PQuicTransportImpl, which sets itself as the delegate in its
+  // constructor. After receiving data it forwards it along to QUIC.
+  P2PQuicPacketTransport::ReceiveDelegate* delegate_ = nullptr;
+
+  // The P2PQuicPacketWriter, which sets itself as a write observer
+  // during the P2PQuicTransportFactoryImpl::CreateQuicTransport. It is
+  // owned by the QuicConnection and will
+  P2PQuicPacketTransport::WriteObserver* write_observer_ = nullptr;
+
+  // The other FakePacketTransport that we are writing to. It's the
+  // responsibility of the test to disconnect this delegate
+  // (set_delegate(nullptr);) before it is destructed.
+  FakePacketTransport* peer_packet_transport_ = nullptr;
+  quic::QuicPacketNumber last_packet_num_;
+  quic::MockClock* clock_;
+};
+
+// A helper class to bundle test objects together.
+class QuicPeerForTest {
+ public:
+  QuicPeerForTest(
+      std::unique_ptr<FakePacketTransport> packet_transport,
+      std::unique_ptr<QuicTransportDelegateForTest> quic_transport_delegate,
+      std::unique_ptr<P2PQuicTransportImpl> quic_transport,
+      rtc::scoped_refptr<rtc::RTCCertificate> certificate)
+      : packet_transport_(std::move(packet_transport)),
+        quic_transport_delegate_(std::move(quic_transport_delegate)),
+        quic_transport_(std::move(quic_transport)),
+        certificate_(certificate) {}
+
+  FakePacketTransport* packet_transport() { return packet_transport_.get(); }
+
+  QuicTransportDelegateForTest* quic_transport_delegate() {
+    return quic_transport_delegate_.get();
+  }
+
+  P2PQuicTransportImpl* quic_transport() { return quic_transport_.get(); }
+
+  rtc::scoped_refptr<rtc::RTCCertificate> certificate() { return certificate_; }
+
+ private:
+  std::unique_ptr<FakePacketTransport> packet_transport_;
+  std::unique_ptr<QuicTransportDelegateForTest> quic_transport_delegate_;
+  std::unique_ptr<P2PQuicTransportImpl> quic_transport_;
+  rtc::scoped_refptr<rtc::RTCCertificate> certificate_;
+};
+
+rtc::scoped_refptr<rtc::RTCCertificate> CreateTestCertificate() {
+  rtc::KeyParams params;
+  rtc::SSLIdentity* ssl_identity =
+      rtc::SSLIdentity::Generate("dummy_certificate", params);
+  return rtc::RTCCertificate::Create(
+      std::unique_ptr<rtc::SSLIdentity>(ssl_identity));
+}
+
+// Allows faking a failing handshake.
+class FailingProofVerifier : public quic::ProofVerifier {
+ public:
+  FailingProofVerifier() {}
+  ~FailingProofVerifier() override {}
+
+  // ProofVerifier override.
+  quic::QuicAsyncStatus VerifyProof(
+      const quic::QuicString& hostname,
+      const uint16_t port,
+      const quic::QuicString& server_config,
+      quic::QuicTransportVersion transport_version,
+      quic::QuicStringPiece chlo_hash,
+      const std::vector<quic::QuicString>& certs,
+      const quic::QuicString& cert_sct,
+      const quic::QuicString& signature,
+      const quic::ProofVerifyContext* context,
+      quic::QuicString* error_details,
+      std::unique_ptr<quic::ProofVerifyDetails>* verify_details,
+      std::unique_ptr<quic::ProofVerifierCallback> callback) override {
+    return quic::QUIC_FAILURE;
+  }
+
+  quic::QuicAsyncStatus VerifyCertChain(
+      const quic::QuicString& hostname,
+      const std::vector<quic::QuicString>& certs,
+      const quic::ProofVerifyContext* context,
+      quic::QuicString* error_details,
+      std::unique_ptr<quic::ProofVerifyDetails>* details,
+      std::unique_ptr<quic::ProofVerifierCallback> callback) override {
+    return quic::QUIC_FAILURE;
+  }
+
+  std::unique_ptr<quic::ProofVerifyContext> CreateDefaultContext() override {
+    return nullptr;
+  }
+};
+}  // namespace
+
+// Unit tests for the P2PQuicTransport, using an underlying fake packet
+// transport that sends packets directly between endpoints. This also tests
+// P2PQuicStreams for test cases that involve two streams connected between
+// separate endpoints. This is because the P2PQuicStream is highly coupled to
+// the P2PQuicSession for communicating between endpoints, so we would like to
+// test it with the real session object.
+//
+// The test is driven using the quic::TestTaskRunner to run posted tasks until
+// callbacks have been fired.
+class P2PQuicTransportTest : public testing::Test {
+ public:
+  P2PQuicTransportTest() {}
+
+  ~P2PQuicTransportTest() override {
+    // This must be done before desctructing the transports so that we don't
+    // have any dangling pointers.
+    client_peer_->packet_transport()->DisconnectPeerTransport(
+        server_peer_->packet_transport());
+    server_peer_->packet_transport()->DisconnectPeerTransport(
+        client_peer_->packet_transport());
+  }
+
+  // Connects both peer's underlying transports and creates both
+  // P2PQuicTransportImpls.
+  void Initialize(bool can_respond_to_crypto_handshake = true) {
+    // Quic crashes if packets are sent at time 0, and the clock defaults to 0.
+    clock_.AdvanceTime(quic::QuicTime::Delta::FromMilliseconds(1000));
+    runner_ = new net::test::TestTaskRunner(&clock_);
+    net::QuicChromiumAlarmFactory* alarm_factory =
+        new net::QuicChromiumAlarmFactory(runner_.get(), &clock_);
+    quic_transport_factory_ = std::make_unique<P2PQuicTransportFactoryImpl>(
+        &clock_, std::unique_ptr<net::QuicChromiumAlarmFactory>(alarm_factory));
+
+    std::unique_ptr<FakePacketTransport> client_packet_transport =
+        std::make_unique<FakePacketTransport>(alarm_factory, &clock_);
+    std::unique_ptr<FakePacketTransport> server_packet_transport =
+        std::make_unique<FakePacketTransport>(alarm_factory, &clock_);
+    // Connect the transports so that they can speak to each other.
+    client_packet_transport->ConnectPeerTransport(
+        server_packet_transport.get());
+    server_packet_transport->ConnectPeerTransport(
+        client_packet_transport.get());
+    rtc::scoped_refptr<rtc::RTCCertificate> client_cert =
+        CreateTestCertificate();
+
+    std::unique_ptr<QuicTransportDelegateForTest>
+        client_quic_transport_delegate =
+            std::make_unique<QuicTransportDelegateForTest>();
+    std::vector<rtc::scoped_refptr<rtc::RTCCertificate>> client_certificates;
+    client_certificates.push_back(client_cert);
+    P2PQuicTransportConfig client_config(client_quic_transport_delegate.get(),
+                                         client_packet_transport.get(),
+                                         client_certificates);
+    client_config.is_server = false;
+    client_config.can_respond_to_crypto_handshake =
+        can_respond_to_crypto_handshake;
+    // We can't downcast a unique_ptr to an object, so we have to release, cast
+    // it, then create a unique_ptr of the downcasted pointer.
+    P2PQuicTransportImpl* client_quic_transport_ptr =
+        static_cast<P2PQuicTransportImpl*>(
+            quic_transport_factory_
+                ->CreateQuicTransport(std::move(client_config))
+                .release());
+    std::unique_ptr<P2PQuicTransportImpl> client_quic_transport =
+        std::unique_ptr<P2PQuicTransportImpl>(client_quic_transport_ptr);
+    client_peer_ = std::make_unique<QuicPeerForTest>(
+        std::move(client_packet_transport),
+        std::move(client_quic_transport_delegate),
+        std::move(client_quic_transport), client_cert);
+
+    std::unique_ptr<QuicTransportDelegateForTest>
+        server_quic_transport_delegate =
+            std::make_unique<QuicTransportDelegateForTest>();
+
+    rtc::scoped_refptr<rtc::RTCCertificate> server_cert =
+        CreateTestCertificate();
+    std::vector<rtc::scoped_refptr<rtc::RTCCertificate>> server_certificates;
+    server_certificates.push_back(server_cert);
+    P2PQuicTransportConfig server_config(server_quic_transport_delegate.get(),
+                                         server_packet_transport.get(),
+                                         server_certificates);
+    server_config.is_server = true;
+    server_config.can_respond_to_crypto_handshake =
+        can_respond_to_crypto_handshake;
+    P2PQuicTransportImpl* server_quic_transport_ptr =
+        static_cast<P2PQuicTransportImpl*>(
+            quic_transport_factory_
+                ->CreateQuicTransport(std::move(server_config))
+                .release());
+    std::unique_ptr<P2PQuicTransportImpl> server_quic_transport =
+        std::unique_ptr<P2PQuicTransportImpl>(server_quic_transport_ptr);
+    server_peer_ = std::make_unique<QuicPeerForTest>(
+        std::move(server_packet_transport),
+        std::move(server_quic_transport_delegate),
+        std::move(server_quic_transport), server_cert);
+  }
+
+  // Sets a FailingProofVerifier to the client transport before initializing
+  // the its crypto stream. This allows the client to fail the proof
+  // verification step during the crypto handshake.
+  void InitializeWithFailingProofVerification() {
+    // Allows us to initialize the crypto streams after constructing the
+    // objects.
+    Initialize(false);
+    // Create the client crypto config and insert it into the client transport.
+    std::unique_ptr<quic::ProofVerifier> proof_verifier(
+        new FailingProofVerifier);
+    std::unique_ptr<quic::QuicCryptoClientConfig> crypto_client_config =
+        std::make_unique<quic::QuicCryptoClientConfig>(
+            std::move(proof_verifier),
+            quic::TlsClientHandshaker::CreateSslCtx());
+    client_peer_->quic_transport()->set_crypto_client_config(
+        std::move(crypto_client_config));
+    // Now initialize the crypto streams.
+    client_peer_->quic_transport()->InitializeCryptoStream();
+    server_peer_->quic_transport()->InitializeCryptoStream();
+  }
+
+  // Drives the test until we are't expecting any more callbacks to be fired.
+  // This is done using the net::test::TestTaskRunner, which runs the tasks
+  // in the correct order and then advances the quic::MockClock to the time the
+  // task is run.
+  void RunUntilCallbacksFired() {
+    while (server_peer_->quic_transport_delegate()->IsExpectingCallback() ||
+           client_peer_->quic_transport_delegate()->IsExpectingCallback() ||
+           ExpectingStreamCallback()) {
+      // We shouldn't enter a case where we are expecting a callback
+      // and we're out of tasks to run.
+      ASSERT_GT(runner_->GetPostedTasks().size(), 0u);
+      runner_->RunNextTask();
+    }
+  }
+
+  bool ExpectingStreamCallback() {
+    return streams_setup_ && (client_stream_delegate_->IsExpectingCallback() ||
+                              server_stream_delegate_->IsExpectingCallback());
+  }
+
+  // Drives the test by running the current tasks that are posted.
+  void RunCurrentTasks() {
+    size_t posted_tasks_size = runner_->GetPostedTasks().size();
+    for (size_t i = 0; i < posted_tasks_size; ++i) {
+      runner_->RunNextTask();
+    }
+  }
+
+  // Starts the handshake, by setting the remote fingerprints and kicking off
+  // the handshake from the client.
+  void StartHandshake() {
+    std::vector<std::unique_ptr<rtc::SSLFingerprint>> server_fingerprints;
+    server_fingerprints.emplace_back(rtc::SSLFingerprint::Create(
+        "sha-256", server_peer_->certificate()->identity()));
+    // The server side doesn't currently need call this to set the remote
+    // fingerprints, but once P2P certificate verification is supported in the
+    // TLS 1.3 handshake this will ben necessary.
+    server_peer_->quic_transport()->Start(std::move(server_fingerprints));
+
+    std::vector<std::unique_ptr<rtc::SSLFingerprint>> client_fingerprints;
+    client_fingerprints.emplace_back(rtc::SSLFingerprint::Create(
+        "sha-256", client_peer_->certificate()->identity()));
+    client_peer_->quic_transport()->Start(std::move(client_fingerprints));
+  }
+
+  // Sets up an initial handshake and connection between peers.
+  void Connect() {
+    client_peer_->quic_transport_delegate()->ExpectCallback(
+        TransportCallbackType::kOnConnected);
+    server_peer_->quic_transport_delegate()->ExpectCallback(
+        TransportCallbackType::kOnConnected);
+    StartHandshake();
+    RunUntilCallbacksFired();
+    ExpectSecureConnection();
+  }
+
+  // Creates a P2PQuicStreamImpl on both the client and server side that are
+  // connected to each other.
+  void SetupConnectedStreams() {
+    // We must already have a secure connection before streams are created.
+    ASSERT_TRUE(client_peer_->quic_transport()->IsEncryptionEstablished());
+    ASSERT_TRUE(server_peer_->quic_transport()->IsEncryptionEstablished());
+
+    client_stream_ = client_peer_->quic_transport()->CreateStream();
+    ASSERT_TRUE(client_stream_);
+    client_stream_id_ = client_stream_->id();
+    client_stream_delegate_ = std::make_unique<QuicStreamDelegateForTesting>();
+    client_stream_->SetDelegate(client_stream_delegate_.get());
+
+    // Send some data to trigger the remote side (server side) to get an
+    // incoming stream.
+    server_peer_->quic_transport_delegate()->ExpectCallback(
+        TransportCallbackType::kOnStream);
+    client_stream_->WriteOrBufferData("hello", false, nullptr);
+    RunUntilCallbacksFired();
+
+    ASSERT_EQ(1u, server_peer_->quic_transport()->GetNumActiveStreams());
+    ASSERT_EQ(1u, client_peer_->quic_transport()->GetNumActiveStreams());
+    ASSERT_EQ(1u, server_peer_->quic_transport_delegate()->streams().size());
+    server_stream_ = server_peer_->quic_transport_delegate()->streams()[0];
+    ASSERT_TRUE(server_stream_);
+    server_stream_id_ = server_stream_->id();
+    server_stream_delegate_ = std::make_unique<QuicStreamDelegateForTesting>();
+    server_stream_->SetDelegate(server_stream_delegate_.get());
+    streams_setup_ = true;
+  }
+
+  void ExpectSecureConnection() {
+    EXPECT_TRUE(client_peer_->quic_transport()->IsEncryptionEstablished());
+    EXPECT_TRUE(client_peer_->quic_transport()->IsCryptoHandshakeConfirmed());
+    EXPECT_TRUE(server_peer_->quic_transport()->IsCryptoHandshakeConfirmed());
+    EXPECT_TRUE(server_peer_->quic_transport()->IsEncryptionEstablished());
+  }
+
+  void ExpectConnectionNotEstablished() {
+    EXPECT_FALSE(client_peer_->quic_transport()->IsEncryptionEstablished());
+    EXPECT_FALSE(client_peer_->quic_transport()->IsCryptoHandshakeConfirmed());
+    EXPECT_FALSE(server_peer_->quic_transport()->IsCryptoHandshakeConfirmed());
+    EXPECT_FALSE(server_peer_->quic_transport()->IsEncryptionEstablished());
+  }
+
+  // Test that the callbacks were called appropriately after a successful
+  // crypto handshake.
+  void ExpectSuccessfulHandshake() {
+    EXPECT_EQ(1, client_peer_->quic_transport_delegate()->connected_count());
+    EXPECT_EQ(0, client_peer_->quic_transport_delegate()->stopped_count());
+    EXPECT_EQ(
+        0, client_peer_->quic_transport_delegate()->connection_failed_count());
+
+    EXPECT_EQ(1, server_peer_->quic_transport_delegate()->connected_count());
+    EXPECT_EQ(0, server_peer_->quic_transport_delegate()->stopped_count());
+    EXPECT_EQ(
+        0, server_peer_->quic_transport_delegate()->connection_failed_count());
+  }
+
+  void ExpectTransportsClosed() {
+    EXPECT_TRUE(client_peer_->quic_transport()->IsClosed());
+    EXPECT_TRUE(server_peer_->quic_transport()->IsClosed());
+  }
+
+  void ExpectStreamsClosed() {
+    ASSERT_TRUE(streams_setup_);
+    EXPECT_EQ(0u, client_peer_->quic_transport()->GetNumActiveStreams());
+    EXPECT_TRUE(
+        client_peer_->quic_transport()->IsClosedStream(client_stream_id_));
+    EXPECT_EQ(0u, server_peer_->quic_transport()->GetNumActiveStreams());
+    EXPECT_TRUE(
+        server_peer()->quic_transport()->IsClosedStream(server_stream_id_));
+  }
+
+  // Exposes these private functions to the test.
+  bool IsClientClosed() { return client_peer_->quic_transport()->IsClosed(); }
+  bool IsServerClosed() { return server_peer_->quic_transport()->IsClosed(); }
+
+  // Tests that the callbacks were appropriately called after the client
+  // stops the connection. Only the server should receive the OnRemoteStopped()
+  // callback.
+  void ExpectClientStopped() {
+    ExpectTransportsClosed();
+    EXPECT_EQ(0, client_peer_->quic_transport_delegate()->stopped_count());
+    EXPECT_EQ(
+        0, client_peer_->quic_transport_delegate()->connection_failed_count());
+    EXPECT_EQ(1, server_peer_->quic_transport_delegate()->stopped_count());
+    EXPECT_EQ(
+        0, server_peer_->quic_transport_delegate()->connection_failed_count());
+  }
+
+  // Tests that the callbacks were appropriately called after the server
+  // stops the connection. Only the client should receive the OnRemoteStopped()
+  // callback.
+  void ExpectServerStopped() {
+    ExpectTransportsClosed();
+    EXPECT_EQ(1, client_peer_->quic_transport_delegate()->stopped_count());
+    EXPECT_EQ(
+        0, client_peer_->quic_transport_delegate()->connection_failed_count());
+    EXPECT_EQ(0, server_peer_->quic_transport_delegate()->stopped_count());
+    EXPECT_EQ(
+        0, server_peer_->quic_transport_delegate()->connection_failed_count());
+  }
+
+  QuicPeerForTest* client_peer() { return client_peer_.get(); }
+
+  quic::QuicConnection* client_connection() {
+    return client_peer_->quic_transport()->connection();
+  }
+
+  QuicPeerForTest* server_peer() { return server_peer_.get(); }
+
+  quic::QuicConnection* server_connection() {
+    return server_peer_->quic_transport()->connection();
+  }
+
+  P2PQuicStreamImpl* server_stream() { return server_stream_; }
+
+  P2PQuicStreamImpl* client_stream() { return client_stream_; }
+
+  quic::QuicStreamId server_stream_id() { return server_stream_id_; }
+
+  quic::QuicStreamId client_stream_id() { return client_stream_id_; }
+
+  QuicStreamDelegateForTesting* server_stream_delegate() {
+    return server_stream_delegate_.get();
+  }
+
+  QuicStreamDelegateForTesting* client_stream_delegate() {
+    return client_stream_delegate_.get();
+  }
+
+ private:
+  quic::MockClock clock_;
+  // The TestTaskRunner is used by the QUIC library for setting/firing alarms.
+  // We are able to explicitly run these tasks ourselves with the
+  // TestTaskRunner.
+  scoped_refptr<net::test::TestTaskRunner> runner_;
+
+  std::unique_ptr<P2PQuicTransportFactoryImpl> quic_transport_factory_;
+  std::unique_ptr<QuicPeerForTest> client_peer_;
+  std::unique_ptr<QuicPeerForTest> server_peer_;
+
+  // Stream objects, which are created with SetupConnectedStream().
+  bool streams_setup_ = false;
+  std::unique_ptr<QuicStreamDelegateForTesting> client_stream_delegate_;
+  std::unique_ptr<QuicStreamDelegateForTesting> server_stream_delegate_;
+  // The P2PQuicStreamImpls are owned by the P2PQuicTransport.
+  P2PQuicStreamImpl* client_stream_ = nullptr;
+  P2PQuicStreamImpl* server_stream_ = nullptr;
+  // We cache the values before the streams are potentially closed and deleted.
+  quic::QuicStreamId server_stream_id_;
+  quic::QuicStreamId client_stream_id_;
+};
+
+// Tests that we can connect two quic transports.
+TEST_F(P2PQuicTransportTest, HandshakeConnectsPeers) {
+  Initialize();
+  Connect();
+
+  ExpectSuccessfulHandshake();
+}
+
+// Tests the standard case for the server side closing the connection.
+TEST_F(P2PQuicTransportTest, ServerStops) {
+  Initialize();
+  Connect();
+  client_peer()->quic_transport_delegate()->ExpectCallback(
+      TransportCallbackType::kOnRemoteStopped);
+  server_peer()->quic_transport()->Stop();
+  RunUntilCallbacksFired();
+
+  ExpectServerStopped();
+}
+
+// Tests the standard case for the client side closing the connection.
+TEST_F(P2PQuicTransportTest, ClientStops) {
+  Initialize();
+  Connect();
+  server_peer()->quic_transport_delegate()->ExpectCallback(
+      TransportCallbackType::kOnRemoteStopped);
+  client_peer()->quic_transport()->Stop();
+  RunUntilCallbacksFired();
+
+  ExpectClientStopped();
+}
+
+// Tests that if either side tries to close the connection a second time, it
+// will be ignored because the connection has already been closed.
+TEST_F(P2PQuicTransportTest, StopAfterStopped) {
+  Initialize();
+  Connect();
+  server_peer()->quic_transport_delegate()->ExpectCallback(
+      TransportCallbackType::kOnRemoteStopped);
+  client_peer()->quic_transport()->Stop();
+  RunUntilCallbacksFired();
+  client_peer()->quic_transport()->Stop();
+  server_peer()->quic_transport()->Stop();
+  RunCurrentTasks();
+
+  ExpectClientStopped();
+}
+
+// Tests that when the client closes the connection the subsequent call to
+// Start() will be ignored.
+TEST_F(P2PQuicTransportTest, ClientStopsBeforeClientStarts) {
+  Initialize();
+  server_peer()->quic_transport_delegate()->ExpectCallback(
+      TransportCallbackType::kOnRemoteStopped);
+  client_peer()->quic_transport()->Stop();
+  StartHandshake();
+  RunUntilCallbacksFired();
+
+  ExpectConnectionNotEstablished();
+  ExpectClientStopped();
+}
+
+// Tests that if the server closes the connection before the client starts the
+// handshake, the client side will already be closed and Start() will be
+// ignored.
+TEST_F(P2PQuicTransportTest, ServerStopsBeforeClientStarts) {
+  Initialize();
+  client_peer()->quic_transport_delegate()->ExpectCallback(
+      TransportCallbackType::kOnRemoteStopped);
+  server_peer()->quic_transport()->Stop();
+  StartHandshake();
+  RunUntilCallbacksFired();
+
+  ExpectConnectionNotEstablished();
+  ExpectServerStopped();
+}
+
+// Tests that when the server's connection fails and then a handshake is
+// attempted the transports will not become connected.
+TEST_F(P2PQuicTransportTest, ClientConnectionClosesBeforeHandshake) {
+  Initialize();
+  client_peer()->quic_transport_delegate()->ExpectCallback(
+      TransportCallbackType::kOnConnectionFailed);
+  server_peer()->quic_transport_delegate()->ExpectCallback(
+      TransportCallbackType::kOnConnectionFailed);
+  client_connection()->CloseConnection(
+      quic::QuicErrorCode::QUIC_INTERNAL_ERROR, "internal error",
+      quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+  StartHandshake();
+  RunUntilCallbacksFired();
+
+  ExpectConnectionNotEstablished();
+}
+
+// Tests that when the server's connection fails and then a handshake is
+// attempted the transports will not become connected.
+TEST_F(P2PQuicTransportTest, ServerConnectionClosesBeforeHandshake) {
+  Initialize();
+  client_peer()->quic_transport_delegate()->ExpectCallback(
+      TransportCallbackType::kOnConnectionFailed);
+  server_peer()->quic_transport_delegate()->ExpectCallback(
+      TransportCallbackType::kOnConnectionFailed);
+  server_connection()->CloseConnection(
+      quic::QuicErrorCode::QUIC_INTERNAL_ERROR, "internal error",
+      quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+  StartHandshake();
+  RunUntilCallbacksFired();
+
+  ExpectConnectionNotEstablished();
+}
+
+// Tests that the appropriate callbacks are fired when the handshake fails.
+TEST_F(P2PQuicTransportTest, HandshakeFailure) {
+  InitializeWithFailingProofVerification();
+  client_peer()->quic_transport_delegate()->ExpectCallback(
+      TransportCallbackType::kOnConnectionFailed);
+  server_peer()->quic_transport_delegate()->ExpectCallback(
+      TransportCallbackType::kOnConnectionFailed);
+  StartHandshake();
+  RunUntilCallbacksFired();
+
+  EXPECT_EQ(
+      1, client_peer()->quic_transport_delegate()->connection_failed_count());
+  EXPECT_EQ(
+      1, server_peer()->quic_transport_delegate()->connection_failed_count());
+  ExpectConnectionNotEstablished();
+  ExpectTransportsClosed();
+}
+
+// Tests that the appropriate callbacks are fired when the client's connection
+// fails after the transports have connected.
+TEST_F(P2PQuicTransportTest, ClientConnectionFailureAfterConnected) {
+  Initialize();
+  Connect();
+  // Close the connection with an internal QUIC error.
+  client_peer()->quic_transport_delegate()->ExpectCallback(
+      TransportCallbackType::kOnConnectionFailed);
+  server_peer()->quic_transport_delegate()->ExpectCallback(
+      TransportCallbackType::kOnConnectionFailed);
+  client_connection()->CloseConnection(
+      quic::QuicErrorCode::QUIC_INTERNAL_ERROR, "internal error",
+      quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+  RunUntilCallbacksFired();
+
+  ExpectTransportsClosed();
+  EXPECT_EQ(
+      1, client_peer()->quic_transport_delegate()->connection_failed_count());
+  EXPECT_EQ(
+      1, server_peer()->quic_transport_delegate()->connection_failed_count());
+}
+
+// Tests that the appropriate callbacks are fired when the server's connection
+// fails after the transports have connected.
+TEST_F(P2PQuicTransportTest, ServerConnectionFailureAfterConnected) {
+  Initialize();
+  Connect();
+  // Close the connection with an internal QUIC error.
+  client_peer()->quic_transport_delegate()->ExpectCallback(
+      TransportCallbackType::kOnConnectionFailed);
+  server_peer()->quic_transport_delegate()->ExpectCallback(
+      TransportCallbackType::kOnConnectionFailed);
+  server_connection()->CloseConnection(
+      quic::QuicErrorCode::QUIC_INTERNAL_ERROR, "internal error",
+      quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+  RunUntilCallbacksFired();
+
+  ExpectTransportsClosed();
+  EXPECT_EQ(
+      1, client_peer()->quic_transport_delegate()->connection_failed_count());
+  EXPECT_EQ(
+      1, server_peer()->quic_transport_delegate()->connection_failed_count());
+}
+
+// Tests that closing the connection with no ACK frame does not make any
+// difference in the closing procedure.
+TEST_F(P2PQuicTransportTest, ConnectionFailureNoAckFrame) {
+  Initialize();
+  Connect();
+  client_peer()->quic_transport_delegate()->ExpectCallback(
+      TransportCallbackType::kOnConnectionFailed);
+  server_peer()->quic_transport_delegate()->ExpectCallback(
+      TransportCallbackType::kOnConnectionFailed);
+  client_connection()->CloseConnection(
+      quic::QuicErrorCode::QUIC_INTERNAL_ERROR, "internal error",
+      quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET_WITH_NO_ACK);
+  RunUntilCallbacksFired();
+
+  ExpectTransportsClosed();
+  EXPECT_EQ(
+      1, client_peer()->quic_transport_delegate()->connection_failed_count());
+  EXPECT_EQ(
+      1, server_peer()->quic_transport_delegate()->connection_failed_count());
+}
+
+// Tests that a silent failure will only close on one side.
+TEST_F(P2PQuicTransportTest, ConnectionSilentFailure) {
+  Initialize();
+  Connect();
+  client_peer()->quic_transport_delegate()->ExpectCallback(
+      TransportCallbackType::kOnConnectionFailed);
+  client_connection()->CloseConnection(
+      quic::QuicErrorCode::QUIC_INTERNAL_ERROR, "internal error",
+      quic::ConnectionCloseBehavior::SILENT_CLOSE);
+  RunUntilCallbacksFired();
+
+  EXPECT_TRUE(IsClientClosed());
+  EXPECT_EQ(
+      1, client_peer()->quic_transport_delegate()->connection_failed_count());
+  EXPECT_FALSE(IsServerClosed());
+  EXPECT_EQ(
+      0, server_peer()->quic_transport_delegate()->connection_failed_count());
+}
+
+// Tests that the client transport can create a stream and an incoming stream
+// will be created on the remote server.
+TEST_F(P2PQuicTransportTest, ClientCreatesStream) {
+  Initialize();
+  Connect();
+  P2PQuicStreamImpl* client_stream =
+      client_peer()->quic_transport()->CreateStream();
+  RunCurrentTasks();
+
+  ASSERT_TRUE(client_stream);
+  EXPECT_TRUE(client_peer()->quic_transport()->HasOpenDynamicStreams());
+  EXPECT_EQ(0, server_peer()->quic_transport_delegate()->on_stream_count());
+  EXPECT_FALSE(server_peer()->quic_transport()->HasOpenDynamicStreams());
+
+  // After sending data across it will trigger a stream to be created on the
+  // server side.
+  server_peer()->quic_transport_delegate()->ExpectCallback(
+      TransportCallbackType::kOnStream);
+  client_stream->WriteOrBufferData("hello", false, nullptr);
+  RunUntilCallbacksFired();
+
+  EXPECT_EQ(1, server_peer()->quic_transport_delegate()->on_stream_count());
+  EXPECT_TRUE(server_peer()->quic_transport()->HasOpenDynamicStreams());
+}
+
+// Tests that the server transport can create a stream and an incoming stream
+// will be created on the remote client.
+TEST_F(P2PQuicTransportTest, ServerCreatesStream) {
+  Initialize();
+  Connect();
+  P2PQuicStreamImpl* server_stream =
+      server_peer()->quic_transport()->CreateStream();
+  RunCurrentTasks();
+
+  ASSERT_TRUE(server_stream);
+  EXPECT_TRUE(server_peer()->quic_transport()->HasOpenDynamicStreams());
+  EXPECT_EQ(0, client_peer()->quic_transport_delegate()->on_stream_count());
+  EXPECT_FALSE(client_peer()->quic_transport()->HasOpenDynamicStreams());
+
+  // After sending data across it will trigger a stream to be created on the
+  // client side.
+  client_peer()->quic_transport_delegate()->ExpectCallback(
+      TransportCallbackType::kOnStream);
+  server_stream->WriteOrBufferData("hello", false, nullptr);
+  RunUntilCallbacksFired();
+
+  EXPECT_EQ(1, client_peer()->quic_transport_delegate()->on_stream_count());
+  EXPECT_TRUE(client_peer()->quic_transport()->HasOpenDynamicStreams());
+}
+
+// Tests that when the client transport calls Stop() it closes its outgoing
+// stream, which, in turn closes the incoming stream on the server quic
+// transport.
+TEST_F(P2PQuicTransportTest, ClientClosingConnectionClosesStreams) {
+  Initialize();
+  Connect();
+  SetupConnectedStreams();
+
+  client_peer()->quic_transport()->Stop();
+  RunCurrentTasks();
+
+  ExpectTransportsClosed();
+  ExpectStreamsClosed();
+}
+
+// Tests that when the server transport calls Stop() it closes its incoming
+// stream, which, in turn closes the outgoing stream on the client quic
+// transport.
+TEST_F(P2PQuicTransportTest, ServerClosingConnectionClosesStreams) {
+  Initialize();
+  Connect();
+  SetupConnectedStreams();
+
+  server_peer()->quic_transport()->Stop();
+  RunCurrentTasks();
+
+  ExpectTransportsClosed();
+  ExpectStreamsClosed();
+}
+
+// Tests that calling Reset() will close both side's streams for reading and
+// writing.
+TEST_F(P2PQuicTransportTest, ClientStreamReset) {
+  Initialize();
+  Connect();
+  SetupConnectedStreams();
+
+  server_stream_delegate()->ExpectCallback(StreamCallbackType::kOnRemoteReset);
+  client_stream()->Reset();
+  RunUntilCallbacksFired();
+
+  ExpectStreamsClosed();
+}
+
+// Tests that calling Reset() will close both side's streams for reading and
+// writing.
+TEST_F(P2PQuicTransportTest, ServerStreamReset) {
+  Initialize();
+  Connect();
+  SetupConnectedStreams();
+
+  client_stream_delegate()->ExpectCallback(StreamCallbackType::kOnRemoteReset);
+  server_stream()->Reset();
+  RunUntilCallbacksFired();
+
+  ExpectStreamsClosed();
+}
+
+// Tests the basic case for calling Finish() on both sides.
+TEST_F(P2PQuicTransportTest, StreamFinishHandshake) {
+  Initialize();
+  Connect();
+  SetupConnectedStreams();
+
+  server_stream_delegate()->ExpectCallback(StreamCallbackType::kOnRemoteFinish);
+  client_stream()->Finish();
+  RunUntilCallbacksFired();
+
+  ASSERT_EQ(1u, server_peer()->quic_transport()->GetNumActiveStreams());
+  ASSERT_EQ(1u, client_peer()->quic_transport()->GetNumActiveStreams());
+  EXPECT_EQ(0, client_stream_delegate()->remote_finish_count());
+  EXPECT_TRUE(client_stream()->write_side_closed());
+  EXPECT_FALSE(client_stream()->reading_stopped());
+  EXPECT_FALSE(server_stream()->write_side_closed());
+  EXPECT_TRUE(server_stream()->reading_stopped());
+  EXPECT_FALSE(
+      server_peer()->quic_transport()->IsClosedStream(server_stream_id()));
+  EXPECT_FALSE(
+      client_peer()->quic_transport()->IsClosedStream(client_stream_id()));
+
+  client_stream_delegate()->ExpectCallback(StreamCallbackType::kOnRemoteFinish);
+  server_stream()->Finish();
+  RunUntilCallbacksFired();
+  // This is required so that the client acks the FIN back to the server side
+  // and the server side removes its zombie streams.
+  RunCurrentTasks();
+
+  ASSERT_EQ(0u, server_peer()->quic_transport()->GetNumActiveStreams());
+  ASSERT_EQ(0u, client_peer()->quic_transport()->GetNumActiveStreams());
+  EXPECT_EQ(1, server_stream_delegate()->remote_finish_count());
+  EXPECT_EQ(1, client_stream_delegate()->remote_finish_count());
+  EXPECT_TRUE(
+      server_peer()->quic_transport()->IsClosedStream(server_stream_id()));
+  EXPECT_TRUE(
+      client_peer()->quic_transport()->IsClosedStream(client_stream_id()));
+}
+
+// Tests that if a Reset() is called after Finish(), both sides close down
+// properly.
+TEST_F(P2PQuicTransportTest, StreamResetAfterFinish) {
+  Initialize();
+  Connect();
+  SetupConnectedStreams();
+
+  server_stream_delegate()->ExpectCallback(StreamCallbackType::kOnRemoteFinish);
+  client_stream()->Finish();
+  RunUntilCallbacksFired();
+
+  server_stream_delegate()->ExpectCallback(StreamCallbackType::kOnRemoteReset);
+  client_stream()->Reset();
+  RunUntilCallbacksFired();
+
+  ExpectStreamsClosed();
+  EXPECT_EQ(0, client_stream_delegate()->remote_reset_count());
+}
+
+// Tests that if a Reset() is called after receiving a stream frame with the FIN
+// bit set from the remote side, both sides close down properly.
+TEST_F(P2PQuicTransportTest, StreamResetAfterRemoteFinish) {
+  Initialize();
+  Connect();
+  SetupConnectedStreams();
+
+  server_stream_delegate()->ExpectCallback(StreamCallbackType::kOnRemoteFinish);
+  client_stream()->Finish();
+  RunUntilCallbacksFired();
+
+  client_stream_delegate()->ExpectCallback(StreamCallbackType::kOnRemoteReset);
+  // The server stream has received its FIN bit from the remote side, and
+  // responds with a Reset() to close everything down.
+  server_stream()->Reset();
+  RunUntilCallbacksFired();
+
+  ExpectStreamsClosed();
+  EXPECT_EQ(0, server_stream_delegate()->remote_reset_count());
+}
+
+// The following unit tests are more isolated to the P2PQuicStreamImpl
+// implementation. They only test a stream's behavior on one side (not any
+// interactions between two connected streams).
+
+TEST_F(P2PQuicTransportTest, StreamFinishSendsFinAndCanNoLongerWrite) {
+  Initialize();
+  Connect();
+  P2PQuicStreamImpl* stream = client_peer()->quic_transport()->CreateStream();
+
+  stream->Finish();
+  EXPECT_TRUE(stream->fin_sent());
+  EXPECT_TRUE(stream->write_side_closed());
+  EXPECT_FALSE(stream->reading_stopped());
+}
+
+TEST_F(P2PQuicTransportTest, StreamResetSendsRstAndBecomesClosed) {
+  Initialize();
+  Connect();
+
+  P2PQuicStreamImpl* stream = client_peer()->quic_transport()->CreateStream();
+  quic::QuicStreamId stream_id = stream->id();
+
+  stream->Reset();
+
+  EXPECT_TRUE(client_peer()->quic_transport()->IsClosedStream(stream_id));
+}
+
+// Tests that when a stream receives a stream frame with the FIN bit set it
+// will fire the appropriate callback and close the stream for reading.
+TEST_F(P2PQuicTransportTest, StreamOnStreamFrameWithFin) {
+  Initialize();
+  Connect();
+  P2PQuicStreamImpl* stream = client_peer()->quic_transport()->CreateStream();
+  QuicStreamDelegateForTesting stream_delegate;
+  stream->SetDelegate(&stream_delegate);
+
+  quic::QuicStreamFrame fin_frame(stream->id(), /*fin=*/true, 0, 0);
+  stream->OnStreamFrame(fin_frame);
+  EXPECT_EQ(1, stream_delegate.remote_finish_count());
+  EXPECT_TRUE(stream->reading_stopped());
+  EXPECT_FALSE(stream->write_side_closed());
+}
+
+// Tests that when a stream receives a stream frame with the FIN bit set after
+// it has called Finish(), then the stream will close.
+TEST_F(P2PQuicTransportTest, StreamClosedAfterReceivesFin) {
+  Initialize();
+  Connect();
+  P2PQuicStreamImpl* stream = client_peer()->quic_transport()->CreateStream();
+  quic::QuicStreamId stream_id = stream->id();
+  QuicStreamDelegateForTesting stream_delegate;
+  stream->SetDelegate(&stream_delegate);
+
+  stream->Finish();
+  EXPECT_FALSE(client_peer()->quic_transport()->IsClosedStream(stream_id));
+  quic::QuicStreamFrame fin_frame(stream->id(), /*fin=*/true, 0, 0);
+  stream->OnStreamFrame(fin_frame);
+
+  EXPECT_TRUE(client_peer()->quic_transport()->IsClosedStream(stream_id));
+}
+
+// Tests that when a stream calls Finish() after receiving a stream frame with
+// the FIN bit then the stream will close.
+TEST_F(P2PQuicTransportTest, StreamClosedAfterFinish) {
+  Initialize();
+  Connect();
+  P2PQuicStreamImpl* stream = client_peer()->quic_transport()->CreateStream();
+  quic::QuicStreamId stream_id = stream->id();
+  QuicStreamDelegateForTesting stream_delegate;
+  stream->SetDelegate(&stream_delegate);
+
+  quic::QuicStreamFrame fin_frame(stream->id(), /*fin=*/true, 0, 0);
+  stream->OnStreamFrame(fin_frame);
+  EXPECT_FALSE(client_peer()->quic_transport()->IsClosedStream(stream_id));
+  stream->Finish();
+
+  EXPECT_TRUE(client_peer()->quic_transport()->IsClosedStream(stream_id));
+}
+
+// Tests that when a stream receives a RST_STREAM frame it will fire the
+// appropriate callback and the stream will become closed.
+TEST_F(P2PQuicTransportTest, StreamClosedAfterReceivingReset) {
+  Initialize();
+  Connect();
+  P2PQuicStreamImpl* stream = client_peer()->quic_transport()->CreateStream();
+  quic::QuicStreamId stream_id = stream->id();
+  QuicStreamDelegateForTesting stream_delegate;
+  stream->SetDelegate(&stream_delegate);
+
+  quic::QuicRstStreamFrame rst_frame(quic::kInvalidControlFrameId, stream_id,
+                                     quic::QUIC_STREAM_CANCELLED, 0);
+  stream->OnStreamReset(rst_frame);
+
+  EXPECT_EQ(1, stream_delegate.remote_reset_count());
+  EXPECT_TRUE(client_peer()->quic_transport()->IsClosedStream(stream_id));
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/webaudio/analyser_node.cc b/third_party/blink/renderer/modules/webaudio/analyser_node.cc
index 6a2819e..39ee21a 100644
--- a/third_party/blink/renderer/modules/webaudio/analyser_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/analyser_node.cc
@@ -35,7 +35,7 @@
 
 AnalyserHandler::AnalyserHandler(AudioNode& node, float sample_rate)
     : AudioBasicInspectorHandler(kNodeTypeAnalyser, node, sample_rate, 1) {
-  channel_count_ = 1;
+  channel_count_ = 2;
   Initialize();
 }
 
diff --git a/third_party/blink/renderer/platform/fonts/font_cache.h b/third_party/blink/renderer/platform/fonts/font_cache.h
index 4acb8d4..f32563d 100644
--- a/third_party/blink/renderer/platform/fonts/font_cache.h
+++ b/third_party/blink/renderer/platform/fonts/font_cache.h
@@ -312,7 +312,7 @@
 #if defined(OS_WIN)
   static bool antialiased_text_enabled_;
   static bool lcd_text_enabled_;
-  static HashMap<String, sk_sp<SkTypeface>>* sideloaded_fonts_;
+  static HashMap<String, sk_sp<SkTypeface>, CaseFoldingHash>* sideloaded_fonts_;
   // The system font metrics cache.
   static AtomicString* menu_font_family_name_;
   static int32_t menu_font_height_;
diff --git a/third_party/blink/renderer/platform/fonts/skia/font_cache_skia.cc b/third_party/blink/renderer/platform/fonts/skia/font_cache_skia.cc
index 4614cc8..7468c06 100644
--- a/third_party/blink/renderer/platform/fonts/skia/font_cache_skia.cc
+++ b/third_party/blink/renderer/platform/fonts/skia/font_cache_skia.cc
@@ -262,8 +262,8 @@
 #if defined(OS_WIN)
   // TODO(vmpstr): Deal with paint typeface here.
   if (sideloaded_fonts_) {
-    HashMap<String, sk_sp<SkTypeface>>::iterator sideloaded_font =
-        sideloaded_fonts_->find(name.data());
+    HashMap<String, sk_sp<SkTypeface>, CaseFoldingHash>::iterator
+        sideloaded_font = sideloaded_fonts_->find(name.data());
     if (sideloaded_font != sideloaded_fonts_->end())
       return sideloaded_font->value;
   }
diff --git a/third_party/blink/renderer/platform/fonts/win/font_cache_skia_win.cc b/third_party/blink/renderer/platform/fonts/win/font_cache_skia_win.cc
index 9f5066a..b3e09a3 100644
--- a/third_party/blink/renderer/platform/fonts/win/font_cache_skia_win.cc
+++ b/third_party/blink/renderer/platform/fonts/win/font_cache_skia_win.cc
@@ -47,7 +47,8 @@
 
 namespace blink {
 
-HashMap<String, sk_sp<SkTypeface>>* FontCache::sideloaded_fonts_ = nullptr;
+HashMap<String, sk_sp<SkTypeface>, CaseFoldingHash>*
+    FontCache::sideloaded_fonts_ = nullptr;
 
 // Cached system font metrics.
 AtomicString* FontCache::menu_font_family_name_ = nullptr;
@@ -71,10 +72,11 @@
 // static
 void FontCache::AddSideloadedFontForTesting(sk_sp<SkTypeface> typeface) {
   if (!sideloaded_fonts_)
-    sideloaded_fonts_ = new HashMap<String, sk_sp<SkTypeface>>;
+    sideloaded_fonts_ = new HashMap<String, sk_sp<SkTypeface>, CaseFoldingHash>;
   SkString name;
   typeface->getFamilyName(&name);
-  sideloaded_fonts_->Set(name.c_str(), std::move(typeface));
+  String name_wtf(name.c_str());
+  sideloaded_fonts_->Set(name_wtf, std::move(typeface));
 }
 
 // static
diff --git a/third_party/blink/renderer/platform/geometry/float_point.cc b/third_party/blink/renderer/platform/geometry/float_point.cc
index 662e799..ddf4db1e 100644
--- a/third_party/blink/renderer/platform/geometry/float_point.cc
+++ b/third_party/blink/renderer/platform/geometry/float_point.cc
@@ -38,6 +38,7 @@
 #include "third_party/blink/renderer/platform/wtf/text/text_stream.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 #include "third_party/skia/include/core/SkPoint.h"
+#include "ui/gfx/geometry/point3_f.h"
 #include "ui/gfx/geometry/scroll_offset.h"
 
 namespace blink {
@@ -119,6 +120,10 @@
   return gfx::Vector2dF(x_, y_);
 }
 
+FloatPoint::operator gfx::Point3F() const {
+  return gfx::Point3F(x_, y_, 0.f);
+}
+
 std::ostream& operator<<(std::ostream& ostream, const FloatPoint& point) {
   return ostream << point.ToString();
 }
diff --git a/third_party/blink/renderer/platform/geometry/float_point.h b/third_party/blink/renderer/platform/geometry/float_point.h
index fb3a29c..56d9bf3 100644
--- a/third_party/blink/renderer/platform/geometry/float_point.h
+++ b/third_party/blink/renderer/platform/geometry/float_point.h
@@ -47,6 +47,7 @@
 
 namespace gfx {
 class PointF;
+class Point3F;
 class ScrollOffset;
 class Vector2dF;
 }
@@ -140,6 +141,7 @@
   operator gfx::PointF() const;
   explicit operator gfx::ScrollOffset() const;
   explicit operator gfx::Vector2dF() const;
+  operator gfx::Point3F() const;
 
   String ToString() const;
 
diff --git a/third_party/blink/renderer/platform/geometry/float_point_3d.cc b/third_party/blink/renderer/platform/geometry/float_point_3d.cc
index 5a99342..f14586c70 100644
--- a/third_party/blink/renderer/platform/geometry/float_point_3d.cc
+++ b/third_party/blink/renderer/platform/geometry/float_point_3d.cc
@@ -30,6 +30,9 @@
 
 namespace blink {
 
+FloatPoint3D::FloatPoint3D(const gfx::Point3F& point)
+    : x_(point.x()), y_(point.y()), z_(point.z()) {}
+
 void FloatPoint3D::Normalize() {
   float temp_length = length();
 
diff --git a/third_party/blink/renderer/platform/geometry/float_point_3d.h b/third_party/blink/renderer/platform/geometry/float_point_3d.h
index 9083de7..64bfe84 100644
--- a/third_party/blink/renderer/platform/geometry/float_point_3d.h
+++ b/third_party/blink/renderer/platform/geometry/float_point_3d.h
@@ -47,6 +47,8 @@
   constexpr FloatPoint3D(const FloatPoint3D& p)
       : x_(p.X()), y_(p.Y()), z_(p.Z()) {}
 
+  FloatPoint3D(const gfx::Point3F&);
+
   constexpr float X() const { return x_; }
   void SetX(float x) { x_ = x; }
 
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
index 04bcabb..c89bf81 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
@@ -3209,7 +3209,7 @@
 TEST_P(PaintArtifactCompositorTest, CreatesViewportNodes) {
   TransformationMatrix matrix;
   matrix.Scale(2);
-  TransformPaintPropertyNode::State transform_state{matrix, FloatPoint3D()};
+  TransformPaintPropertyNode::State transform_state{matrix};
   transform_state.compositor_element_id =
       CompositorElementIdFromUniqueObjectId(1);
 
diff --git a/third_party/blink/renderer/platform/graphics/graphics_layer.cc b/third_party/blink/renderer/platform/graphics/graphics_layer.cc
index 37914b0..a7523363 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_layer.cc
+++ b/third_party/blink/renderer/platform/graphics/graphics_layer.cc
@@ -78,17 +78,9 @@
 
 GraphicsLayer::GraphicsLayer(GraphicsLayerClient& client)
     : client_(client),
-      background_color_(Color::kTransparent),
-      opacity_(1),
-      blend_mode_(BlendMode::kNormal),
-      has_transform_origin_(false),
-      contents_opaque_(false),
       prevent_contents_opaque_changes_(false),
-      should_flatten_transform_(true),
-      backface_visibility_(true),
       draws_content_(false),
       contents_visible_(true),
-      is_root_for_isolated_group_(false),
       hit_testable_without_draws_content_(false),
       needs_check_raster_invalidation_(false),
       has_scroll_parent_(false),
@@ -107,14 +99,14 @@
   client.VerifyNotPainting();
 #endif
   layer_ = cc::PictureLayer::Create(this);
-  layer_->SetIsDrawable(draws_content_ && contents_visible_);
-  layer_->SetLayerClient(weak_ptr_factory_.GetWeakPtr());
+  CcLayer()->SetIsDrawable(draws_content_ && contents_visible_);
+  CcLayer()->SetLayerClient(weak_ptr_factory_.GetWeakPtr());
 
   UpdateTrackingRasterInvalidations();
 }
 
 GraphicsLayer::~GraphicsLayer() {
-  layer_->SetLayerClient(nullptr);
+  CcLayer()->SetLayerClient(nullptr);
   SetContentsLayer(nullptr);
   for (size_t i = 0; i < link_highlights_.size(); ++i)
     link_highlights_[i]->ClearCurrentGraphicsLayer();
@@ -136,17 +128,17 @@
 
 void GraphicsLayer::SetHasWillChangeTransformHint(
     bool has_will_change_transform) {
-  layer_->SetHasWillChangeTransformHint(has_will_change_transform);
+  CcLayer()->SetHasWillChangeTransformHint(has_will_change_transform);
 }
 
 void GraphicsLayer::SetOverscrollBehavior(
     const cc::OverscrollBehavior& behavior) {
-  layer_->SetOverscrollBehavior(behavior);
+  CcLayer()->SetOverscrollBehavior(behavior);
 }
 
 void GraphicsLayer::SetSnapContainerData(
     base::Optional<SnapContainerData> data) {
-  layer_->SetSnapContainerData(std::move(data));
+  CcLayer()->SetSnapContainerData(std::move(data));
 }
 
 void GraphicsLayer::SetIsResizedByBrowserControls(
@@ -342,7 +334,7 @@
       GetPaintController().AppendDebugDrawingAfterCommit(std::move(record),
                                                          layer_state_->state);
       // Ensure the compositor will raster the under-invalidation overlay.
-      layer_->SetNeedsDisplay();
+      CcLayer()->SetNeedsDisplay();
     }
   }
 
@@ -414,12 +406,12 @@
   // shouldn't receive the drawsContent flag, so it is only given
   // contentsVisible.
 
-  layer_->SetIsDrawable(draws_content_ && contents_visible_);
+  CcLayer()->SetIsDrawable(draws_content_ && contents_visible_);
   if (cc::Layer* contents_layer = ContentsLayerIfRegistered())
     contents_layer->SetIsDrawable(contents_visible_);
 
   if (draws_content_) {
-    layer_->SetNeedsDisplay();
+    CcLayer()->SetNeedsDisplay();
     for (size_t i = 0; i < link_highlights_.size(); ++i)
       link_highlights_[i]->Invalidate();
   }
@@ -523,7 +515,7 @@
 
   // Insert the content layer first. Video elements require this, because they
   // have shadow content that must display in front of the video.
-  layer_->InsertChild(contents_layer_, 0);
+  CcLayer()->InsertChild(contents_layer_, 0);
   cc::PictureLayer* border_cc_layer =
       contents_clipping_mask_layer_ ? contents_clipping_mask_layer_->CcLayer()
                                     : nullptr;
@@ -636,18 +628,18 @@
 }
 
 const gfx::Size& GraphicsLayer::Size() const {
-  return layer_->bounds();
+  return CcLayer()->bounds();
 }
 
 void GraphicsLayer::SetSize(const gfx::Size& size) {
   DCHECK(size.width() >= 0 && size.height() >= 0);
 
-  if (size == layer_->bounds())
+  if (size == CcLayer()->bounds())
     return;
 
   Invalidate(PaintInvalidationReason::kIncremental);  // as DisplayItemClient.
 
-  layer_->SetBounds(size);
+  CcLayer()->SetBounds(size);
   // Note that we don't resize m_contentsLayer. It's up the caller to do that.
 }
 
@@ -656,19 +648,20 @@
   CcLayer()->SetTransform(TransformationMatrix::ToTransform(transform));
 }
 
-void GraphicsLayer::SetTransformOrigin(const FloatPoint3D& transform_origin) {
-  has_transform_origin_ = true;
-  transform_origin_ = transform_origin;
+void GraphicsLayer::SetTransformOrigin(const gfx::Point3F& transform_origin) {
   CcLayer()->SetTransformOrigin(transform_origin);
 }
 
+const gfx::Point3F& GraphicsLayer::TransformOrigin() const {
+  return CcLayer()->transform_origin();
+}
+
+bool GraphicsLayer::ShouldFlattenTransform() const {
+  return CcLayer()->should_flatten_transform();
+}
+
 void GraphicsLayer::SetShouldFlattenTransform(bool should_flatten) {
-  if (should_flatten == should_flatten_transform_)
-    return;
-
-  should_flatten_transform_ = should_flatten;
-
-  layer_->SetShouldFlattenTransform(should_flatten);
+  CcLayer()->SetShouldFlattenTransform(should_flatten);
 }
 
 void GraphicsLayer::SetRenderingContext(int context) {
@@ -676,18 +669,18 @@
     return;
 
   rendering_context3d_ = context;
-  layer_->Set3dSortingContextId(context);
+  CcLayer()->Set3dSortingContextId(context);
 
   if (contents_layer_)
-    contents_layer_->Set3dSortingContextId(rendering_context3d_);
+    CcLayer()->Set3dSortingContextId(rendering_context3d_);
 }
 
 bool GraphicsLayer::MasksToBounds() const {
-  return layer_->masks_to_bounds();
+  return CcLayer()->masks_to_bounds();
 }
 
 void GraphicsLayer::SetMasksToBounds(bool masks_to_bounds) {
-  layer_->SetMasksToBounds(masks_to_bounds);
+  CcLayer()->SetMasksToBounds(masks_to_bounds);
 }
 
 void GraphicsLayer::SetDrawsContent(bool draws_content) {
@@ -719,25 +712,28 @@
 
 void GraphicsLayer::SetClipParent(cc::Layer* parent) {
   has_clip_parent_ = !!parent;
-  layer_->SetClipParent(parent);
+  CcLayer()->SetClipParent(parent);
 }
 
 void GraphicsLayer::SetScrollParent(cc::Layer* parent) {
   has_scroll_parent_ = !!parent;
-  layer_->SetScrollParent(parent);
+  CcLayer()->SetScrollParent(parent);
 }
 
-void GraphicsLayer::SetBackgroundColor(const Color& color) {
-  if (color == background_color_)
-    return;
+RGBA32 GraphicsLayer::BackgroundColor() const {
+  return CcLayer()->background_color();
+}
 
-  background_color_ = color;
-  layer_->SetBackgroundColor(background_color_.Rgb());
+void GraphicsLayer::SetBackgroundColor(RGBA32 color) {
+  CcLayer()->SetBackgroundColor(color);
+}
+
+bool GraphicsLayer::ContentsOpaque() const {
+  return CcLayer()->contents_opaque();
 }
 
 void GraphicsLayer::SetContentsOpaque(bool opaque) {
-  contents_opaque_ = opaque;
-  layer_->SetContentsOpaque(contents_opaque_);
+  CcLayer()->SetContentsOpaque(opaque);
   ClearContentsLayerIfUnregistered();
   if (contents_layer_ && !prevent_contents_opaque_changes_)
     contents_layer_->SetContentsOpaque(opaque);
@@ -749,7 +745,7 @@
     return;
 
   mask_layer_ = mask_layer;
-  layer_->SetMaskLayer(mask_layer_ ? mask_layer_->CcLayer() : nullptr);
+  CcLayer()->SetMaskLayer(mask_layer_ ? mask_layer_->CcLayer() : nullptr);
   if (mask_layer_)
     mask_layer_->CcLayer()->set_is_rounded_corner_mask(is_rounded_corner_mask);
 }
@@ -773,28 +769,35 @@
   UpdateContentsRect();
 }
 
+bool GraphicsLayer::BackfaceVisibility() const {
+  return CcLayer()->double_sided();
+}
+
 void GraphicsLayer::SetBackfaceVisibility(bool visible) {
-  backface_visibility_ = visible;
-  CcLayer()->SetDoubleSided(backface_visibility_);
+  CcLayer()->SetDoubleSided(visible);
 }
 
 void GraphicsLayer::SetOpacity(float opacity) {
-  float clamped_opacity = clampTo(opacity, 0.0f, 1.0f);
-  opacity_ = clamped_opacity;
   CcLayer()->SetOpacity(opacity);
 }
 
+float GraphicsLayer::Opacity() const {
+  return CcLayer()->opacity();
+}
+
 void GraphicsLayer::SetBlendMode(BlendMode blend_mode) {
-  if (blend_mode_ == blend_mode)
-    return;
-  blend_mode_ = blend_mode;
   CcLayer()->SetBlendMode(WebCoreBlendModeToSkBlendMode(blend_mode));
 }
 
+BlendMode GraphicsLayer::GetBlendMode() const {
+  return BlendModeFromSkBlendMode(CcLayer()->blend_mode());
+}
+
+bool GraphicsLayer::IsRootForIsolatedGroup() const {
+  return CcLayer()->is_root_for_isolated_group();
+}
+
 void GraphicsLayer::SetIsRootForIsolatedGroup(bool isolated) {
-  if (is_root_for_isolated_group_ == isolated)
-    return;
-  is_root_for_isolated_group_ = isolated;
   CcLayer()->SetIsRootForIsolatedGroup(isolated);
 }
 
@@ -817,7 +820,7 @@
   if (!DrawsContent())
     return;
 
-  layer_->SetNeedsDisplay();
+  CcLayer()->SetNeedsDisplay();
   for (size_t i = 0; i < link_highlights_.size(); ++i)
     link_highlights_[i]->Invalidate();
 
@@ -833,7 +836,7 @@
 void GraphicsLayer::SetNeedsDisplayInRect(const IntRect& rect) {
   DCHECK(DrawsContent());
 
-  layer_->SetNeedsDisplayRect(rect);
+  CcLayer()->SetNeedsDisplayRect(rect);
   for (auto* link_highlight : link_highlights_)
     link_highlight->Invalidate();
 }
@@ -911,7 +914,7 @@
 
 void GraphicsLayer::SetStickyPositionConstraint(
     const cc::LayerStickyPositionConstraint& sticky_constraint) {
-  layer_->SetStickyPositionConstraint(sticky_constraint);
+  CcLayer()->SetStickyPositionConstraint(sticky_constraint);
 }
 
 void GraphicsLayer::SetFilterQuality(SkFilterQuality filter_quality) {
diff --git a/third_party/blink/renderer/platform/graphics/graphics_layer.h b/third_party/blink/renderer/platform/graphics/graphics_layer.h
index b9e45dc5..dd43884 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_layer.h
+++ b/third_party/blink/renderer/platform/graphics/graphics_layer.h
@@ -139,8 +139,8 @@
   const gfx::PointF& GetPosition() const;
   void SetPosition(const gfx::PointF&);
 
-  const FloatPoint3D& TransformOrigin() const { return transform_origin_; }
-  void SetTransformOrigin(const FloatPoint3D&);
+  const gfx::Point3F& TransformOrigin() const;
+  void SetTransformOrigin(const gfx::Point3F&);
 
   // The size of the layer.
   const gfx::Size& Size() const;
@@ -148,14 +148,14 @@
 
   const TransformationMatrix& Transform() const { return transform_; }
   void SetTransform(const TransformationMatrix&);
+
+  bool ShouldFlattenTransform() const;
   void SetShouldFlattenTransform(bool);
   void SetRenderingContext(int id);
 
   bool MasksToBounds() const;
   void SetMasksToBounds(bool);
 
-  bool IsRootForIsolatedGroup() const { return is_root_for_isolated_group_; }
-
   bool DrawsContent() const { return draws_content_; }
   void SetDrawsContent(bool);
 
@@ -168,23 +168,22 @@
   // For special cases, e.g. drawing missing tiles on Android.
   // The compositor should never paint this color in normal cases because the
   // Layer will paint the background by itself.
-  Color BackgroundColor() const { return background_color_; }
-  void SetBackgroundColor(const Color&);
+  RGBA32 BackgroundColor() const;
+  void SetBackgroundColor(RGBA32);
 
   // Opaque means that we know the layer contents have no alpha.
-  bool ContentsOpaque() const { return contents_opaque_; }
+  bool ContentsOpaque() const;
   void SetContentsOpaque(bool);
 
-  bool BackfaceVisibility() const { return backface_visibility_; }
+  bool BackfaceVisibility() const;
   void SetBackfaceVisibility(bool visible);
 
-  bool ShouldFlattenTransform() const { return should_flatten_transform_; }
-
-  float Opacity() const { return opacity_; }
+  float Opacity() const;
   void SetOpacity(float);
 
-  BlendMode GetBlendMode() const { return blend_mode_; }
+  BlendMode GetBlendMode() const;
   void SetBlendMode(BlendMode);
+  bool IsRootForIsolatedGroup() const;
   void SetIsRootForIsolatedGroup(bool);
 
   void SetHitTestableWithoutDrawsContent(bool);
@@ -370,21 +369,10 @@
   IntSize offset_from_layout_object_;
 
   TransformationMatrix transform_;
-  FloatPoint3D transform_origin_;
 
-  Color background_color_;
-  float opacity_;
-
-  BlendMode blend_mode_;
-
-  bool has_transform_origin_ : 1;
-  bool contents_opaque_ : 1;
   bool prevent_contents_opaque_changes_ : 1;
-  bool should_flatten_transform_ : 1;
-  bool backface_visibility_ : 1;
   bool draws_content_ : 1;
   bool contents_visible_ : 1;
-  bool is_root_for_isolated_group_ : 1;
   bool hit_testable_without_draws_content_ : 1;
   bool needs_check_raster_invalidation_ : 1;
 
diff --git a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.cc b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.cc
index c1338cb7..00b977b9 100644
--- a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.cc
@@ -69,8 +69,8 @@
     const TransformPaintPropertyNode* destination,
     bool& success) {
   DCHECK(source && destination);
-  DEFINE_STATIC_LOCAL(TransformationMatrix, identity, (TransformationMatrix()));
-  DEFINE_STATIC_LOCAL(TransformationMatrix, temp, (TransformationMatrix()));
+  DEFINE_STATIC_LOCAL(TransformationMatrix, identity, ());
+  DEFINE_STATIC_LOCAL(TransformationMatrix, temp, ());
 
   source = source->Unalias();
   destination = destination->Unalias();
@@ -80,6 +80,15 @@
     return identity;
   }
 
+  if (source->Parent() && destination == source->Parent()->Unalias() &&
+      // The result will be translate(origin)*matrix*translate(-origin) which
+      // equals to matrix if the origin is zero or if the matrix is just
+      // identity or 2d translation.
+      (source->Origin().IsZero() || source->IsIdentityOr2DTranslation())) {
+    success = true;
+    return source->Matrix();
+  }
+
   const GeometryMapperTransformCache& source_cache =
       source->GetTransformCache();
   const GeometryMapperTransformCache& destination_cache =
diff --git a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h
index 050edc7..55f55e1e 100644
--- a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h
+++ b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h
@@ -52,18 +52,35 @@
   // Same as SourceToDestinationProjection() except that it maps the rect
   // rather than returning the matrix.
   // |mapping_rect| is both input and output. Its type can be FloatRect,
-  // LayoutRect and IntRect.
+  // LayoutRect or IntRect.
   template <typename Rect>
-  static void SourceToDestinationRect(
-      const TransformPaintPropertyNode* source_transform_node,
-      const TransformPaintPropertyNode* destination_transform_node,
+  ALWAYS_INLINE static void SourceToDestinationRect(
+      const TransformPaintPropertyNode* source,
+      const TransformPaintPropertyNode* destination,
       Rect& mapping_rect) {
-    if (source_transform_node == destination_transform_node)
+    if (source == destination)
       return;
+
+    // Fast-path optimization for mapping through just |source| when |source| is
+    // a 2d translation.
+    if (destination == source->Parent() &&
+        source->IsIdentityOr2DTranslation()) {
+      MoveRect(mapping_rect, source->Matrix().E(), source->Matrix().F());
+      return;
+    }
+
+    // Fast-path optimization for mapping through just |destination| when
+    // |destination| is a 2d translation.
+    if (source == destination->Parent() &&
+        destination->IsIdentityOr2DTranslation()) {
+      MoveRect(mapping_rect, -destination->Matrix().E(),
+               -destination->Matrix().F());
+      return;
+    }
+
     bool success = false;
     const TransformationMatrix& source_to_destination =
-        SourceToDestinationProjectionInternal(
-            source_transform_node, destination_transform_node, success);
+        SourceToDestinationProjectionInternal(source, destination, success);
     mapping_rect =
         success ? source_to_destination.MapRect(mapping_rect) : Rect();
   }
@@ -179,6 +196,20 @@
       InclusiveIntersectOrNot,
       bool& success);
 
+  static void MoveRect(FloatRect& rect, double dx, double dy) {
+    rect.Move(static_cast<float>(dx), static_cast<float>(dy));
+  }
+
+  static void MoveRect(LayoutRect& rect, double dx, double dy) {
+    rect.Move(LayoutUnit(dx), LayoutUnit(dy));
+  }
+
+  static void MoveRect(IntRect& rect, double dx, double dy) {
+    auto float_rect = FloatRect(rect);
+    MoveRect(float_rect, dx, dy);
+    rect = EnclosingIntRect(float_rect);
+  }
+
   friend class GeometryMapperTest;
   friend class PaintLayerClipperTest;
 };
diff --git a/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.cc b/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.cc
index a17f077..d59f866 100644
--- a/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.cc
@@ -13,9 +13,7 @@
       TransformPaintPropertyNode, root,
       base::AdoptRef(new TransformPaintPropertyNode(
           nullptr,
-          State{TransformationMatrix(), FloatPoint3D(), false,
-                BackfaceVisibility::kVisible, 0, CompositingReason::kNone,
-                CompositorElementId(), &ScrollPaintPropertyNode::Root()},
+          State{TransformationMatrix(), &ScrollPaintPropertyNode::Root()},
           true /* is_parent_alias */)));
   return *root;
 }
diff --git a/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h b/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h
index d5ced2c..dc600e85 100644
--- a/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h
+++ b/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h
@@ -45,14 +45,18 @@
   // a struct with default values is used to represent the state.
   struct State {
     TransformationMatrix matrix;
+    scoped_refptr<const ScrollPaintPropertyNode> scroll;
     FloatPoint3D origin;
     bool flattens_inherited_transform = false;
+    // Caches value of matrix_.IsIdentityOr2DTranslation(). The caller can set
+    // this field to true if the matrix is known to be identity or 2d
+    // translation, or the field will be updated automatically.
+    bool is_identity_or_2d_translation = false;
+    bool affected_by_outer_viewport_bounds_delta = false;
     BackfaceVisibility backface_visibility = BackfaceVisibility::kInherited;
     unsigned rendering_context_id = 0;
     CompositingReasons direct_compositing_reasons = CompositingReason::kNone;
     CompositorElementId compositor_element_id;
-    scoped_refptr<const ScrollPaintPropertyNode> scroll;
-    bool affected_by_outer_viewport_bounds_delta = false;
     CompositorStickyConstraint sticky_constraint;
 
     bool operator==(const State& o) const {
@@ -93,6 +97,7 @@
     DCHECK(!IsParentAlias()) << "Changed the state of an alias node.";
     SetChanged();
     state_ = std::move(state);
+    CheckAndUpdateIsIdentityOr2DTranslation();
     Validate();
     return true;
   }
@@ -134,6 +139,12 @@
     return state_.flattens_inherited_transform;
   }
 
+  bool IsIdentityOr2DTranslation() const {
+    DCHECK_EQ(state_.is_identity_or_2d_translation,
+              state_.matrix.IsIdentityOr2DTranslation());
+    return state_.is_identity_or_2d_translation;
+  }
+
   // Returns the local BackfaceVisibility value set on this node.
   // See |IsBackfaceHidden()| for computing whether this transform node is
   // hidden or not.
@@ -201,15 +212,28 @@
                              State&& state,
                              bool is_parent_alias)
       : PaintPropertyNode(parent, is_parent_alias), state_(std::move(state)) {
+    CheckAndUpdateIsIdentityOr2DTranslation();
     Validate();
   }
 
+  void CheckAndUpdateIsIdentityOr2DTranslation() {
+    if (IsParentAlias()) {
+      DCHECK(state_.matrix.IsIdentity());
+      state_.is_identity_or_2d_translation = true;
+    } else if (state_.is_identity_or_2d_translation) {
+      DCHECK(state_.matrix.IsIdentityOr2DTranslation());
+    } else {
+      state_.is_identity_or_2d_translation =
+          state_.matrix.IsIdentityOr2DTranslation();
+    }
+  }
+
   void Validate() const {
 #if DCHECK_IS_ON()
     if (state_.scroll) {
       // If there is an associated scroll node, this can only be a 2d
       // translation for scroll offset.
-      DCHECK(state_.matrix.IsIdentityOr2DTranslation());
+      DCHECK(state_.is_identity_or_2d_translation);
       // The scroll compositor element id should be stored on the scroll node.
       DCHECK(!state_.compositor_element_id);
     }
diff --git a/third_party/blink/renderer/platform/mojo/security_origin_struct_traits.h b/third_party/blink/renderer/platform/mojo/security_origin_struct_traits.h
index 3785ab7..738b03c 100644
--- a/third_party/blink/renderer/platform/mojo/security_origin_struct_traits.h
+++ b/third_party/blink/renderer/platform/mojo/security_origin_struct_traits.h
@@ -8,6 +8,7 @@
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 #include "url/mojom/origin.mojom-blink.h"
+#include "url/mojom/origin_mojom_traits.h"
 
 namespace mojo {
 
@@ -26,31 +27,38 @@
       const scoped_refptr<const ::blink::SecurityOrigin>& origin) {
     return origin->EffectivePort();
   }
-  static bool unique(
+  static base::Optional<base::UnguessableToken> nonce_if_opaque(
       const scoped_refptr<const ::blink::SecurityOrigin>& origin) {
-    return origin->IsOpaque();
+    if (origin->IsOpaque())
+      return base::UnguessableToken::Create();
+
+    return base::nullopt;
   }
   static bool Read(url::mojom::blink::Origin::DataView data,
                    scoped_refptr<const ::blink::SecurityOrigin>* out) {
-    if (data.unique()) {
-      *out = ::blink::SecurityOrigin::CreateUniqueOpaque();
-    } else {
-      WTF::String scheme;
-      WTF::String host;
-      if (!data.ReadScheme(&scheme) || !data.ReadHost(&host))
-        return false;
+    WTF::String scheme;
+    WTF::String host;
+    base::Optional<base::UnguessableToken> nonce_if_opaque;
+    if (!data.ReadScheme(&scheme) || !data.ReadHost(&host) ||
+        !data.ReadNonceIfOpaque(&nonce_if_opaque))
+      return false;
 
-      *out = ::blink::SecurityOrigin::Create(scheme, host, data.port());
+    if (nonce_if_opaque) {
+      *out = blink::SecurityOrigin::CreateUniqueOpaque();
+      return true;
     }
 
-    // If a unique origin was created, but the unique flag wasn't set, then
-    // the values provided to 'create' were invalid.
-    if (!data.unique() && (*out)->IsOpaque())
+    *out = blink::SecurityOrigin::Create(scheme, host, data.port());
+
+    // If an opaque origin was created, but the opaque flag wasn't set, then
+    // the values provided to 'Create' were invalid.
+    if ((*out)->IsOpaque())
       return false;
 
     return true;
   }
 };
-}
+
+}  // namespace mojo
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_MOJO_SECURITY_ORIGIN_STRUCT_TRAITS_H_
diff --git a/third_party/blink/renderer/platform/transforms/transformation_matrix.h b/third_party/blink/renderer/platform/transforms/transformation_matrix.h
index 76cf5e5..8d88d5d 100644
--- a/third_party/blink/renderer/platform/transforms/transformation_matrix.h
+++ b/third_party/blink/renderer/platform/transforms/transformation_matrix.h
@@ -37,14 +37,6 @@
 #include "third_party/blink/renderer/platform/wtf/alignment.h"
 #include "third_party/blink/renderer/platform/wtf/allocator.h"
 
-#if DCHECK_IS_ON()
-namespace {
-bool ApproximatelyEquals(const double& d1, const double& d2) {
-  return std::fabs(d1 - d2) < std::numeric_limits<double>::epsilon();
-}
-}  // namespace
-#endif
-
 namespace gfx {
 class Transform;
 }
@@ -418,27 +410,6 @@
   // details and discussion.
   void FlattenTo2d();
 
-#if DCHECK_IS_ON()
-  bool ApproximatelyEquals(const TransformationMatrix& m2) const {
-    return ::ApproximatelyEquals(matrix_[0][0], m2.matrix_[0][0]) &&
-           ::ApproximatelyEquals(matrix_[0][1], m2.matrix_[0][1]) &&
-           ::ApproximatelyEquals(matrix_[0][2], m2.matrix_[0][2]) &&
-           ::ApproximatelyEquals(matrix_[0][3], m2.matrix_[0][3]) &&
-           ::ApproximatelyEquals(matrix_[1][0], m2.matrix_[1][0]) &&
-           ::ApproximatelyEquals(matrix_[1][1], m2.matrix_[1][1]) &&
-           ::ApproximatelyEquals(matrix_[1][2], m2.matrix_[1][2]) &&
-           ::ApproximatelyEquals(matrix_[1][3], m2.matrix_[1][3]) &&
-           ::ApproximatelyEquals(matrix_[2][0], m2.matrix_[2][0]) &&
-           ::ApproximatelyEquals(matrix_[2][1], m2.matrix_[2][1]) &&
-           ::ApproximatelyEquals(matrix_[2][2], m2.matrix_[2][2]) &&
-           ::ApproximatelyEquals(matrix_[2][3], m2.matrix_[2][3]) &&
-           ::ApproximatelyEquals(matrix_[3][0], m2.matrix_[3][0]) &&
-           ::ApproximatelyEquals(matrix_[3][1], m2.matrix_[3][1]) &&
-           ::ApproximatelyEquals(matrix_[3][2], m2.matrix_[3][2]) &&
-           ::ApproximatelyEquals(matrix_[3][3], m2.matrix_[3][3]);
-  }
-#endif
-
   bool operator==(const TransformationMatrix& m2) const {
     return matrix_[0][0] == m2.matrix_[0][0] &&
            matrix_[0][1] == m2.matrix_[0][1] &&
diff --git a/third_party/blink/tools/audit_non_blink_usage.py b/third_party/blink/tools/audit_non_blink_usage.py
index 9ca4a03..5fd0abd3 100755
--- a/third_party/blink/tools/audit_non_blink_usage.py
+++ b/third_party/blink/tools/audit_non_blink_usage.py
@@ -422,6 +422,7 @@
             'cricket::.*',
             'rtc::.+',
             'webrtc::.+',
+            'quic::.+',
         ]
     },
     {
diff --git a/third_party/blink/tools/blinkpy/style/checkers/cpp.py b/third_party/blink/tools/blinkpy/style/checkers/cpp.py
index dffaa12..f0fb8fb 100644
--- a/third_party/blink/tools/blinkpy/style/checkers/cpp.py
+++ b/third_party/blink/tools/blinkpy/style/checkers/cpp.py
@@ -1123,16 +1123,6 @@
                   'The class %s probably needs a virtual destructor due to '
                   'having virtual method(s), one declared at line %d.'
                   % (classinfo.name, classinfo.virtual_method_line_number))
-        # Look for mixed bool and unsigned bitfields.
-        if classinfo.bool_bitfields and classinfo.unsigned_bitfields:
-            bool_list = ', '.join(classinfo.bool_bitfields)
-            unsigned_list = ', '.join(classinfo.unsigned_bitfields)
-            error(classinfo.line_number, 'runtime/bitfields', 5,
-                  'The class %s contains mixed unsigned and bool bitfields, '
-                  'which will pack into separate words on the MSVC compiler.\n'
-                  'Bool bitfields are [%s].\nUnsigned bitfields are [%s].\n'
-                  'Consider converting bool bitfields to unsigned.'
-                  % (classinfo.name, bool_list, unsigned_list))
     else:
         classinfo.brace_depth = brace_depth
 
diff --git a/third_party/blink/tools/blinkpy/style/checkers/cpp_unittest.py b/third_party/blink/tools/blinkpy/style/checkers/cpp_unittest.py
index 55b0fda..dc5505a 100644
--- a/third_party/blink/tools/blinkpy/style/checkers/cpp_unittest.py
+++ b/third_party/blink/tools/blinkpy/style/checkers/cpp_unittest.py
@@ -1506,51 +1506,6 @@
         self.assert_lint('long int a : 30;', errmsg)
         self.assert_lint('int a = 1 ? 0 : 30;', '')
 
-    # A mixture of unsigned and bool bitfields in a class will generate a warning.
-    def test_mixing_unsigned_bool_bitfields(self):
-        def errmsg(bool_bitfields, unsigned_bitfields, name):
-            bool_list = ', '.join(bool_bitfields)
-            unsigned_list = ', '.join(unsigned_bitfields)
-            return ('The class %s contains mixed unsigned and bool bitfields, '
-                    'which will pack into separate words on the MSVC compiler.\n'
-                    'Bool bitfields are [%s].\nUnsigned bitfields are [%s].\n'
-                    'Consider converting bool bitfields to unsigned.  [runtime/bitfields] [5]'
-                    % (name, bool_list, unsigned_list))
-
-        def build_test_case(bitfields, name, will_warn, extra_warnings=None):
-            bool_bitfields = []
-            unsigned_bitfields = []
-            test_string = 'class %s {\n' % (name,)
-            line = 2
-            for bitfield in bitfields:
-                test_string += '    %s %s : %d;\n' % bitfield
-                if bitfield[0] == 'bool':
-                    bool_bitfields.append('%d: %s' % (line, bitfield[1]))
-                elif bitfield[0].startswith('unsigned'):
-                    unsigned_bitfields.append('%d: %s' % (line, bitfield[1]))
-                line += 1
-            test_string += '}\n'
-            error = ''
-            if will_warn:
-                error = errmsg(bool_bitfields, unsigned_bitfields, name)
-            if extra_warnings and error:
-                error = extra_warnings + [error]
-            self.assert_multi_line_lint(test_string, error)
-
-        build_test_case([('bool', 'm_boolMember', 4), ('unsigned', 'm_unsignedMember', 3)],
-                        'MyClass', True)
-        build_test_case([('bool', 'm_boolMember', 4), ('bool', 'm_anotherBool', 3)],
-                        'MyClass', False)
-        build_test_case([('unsigned', 'm_unsignedMember', 4), ('unsigned', 'm_anotherUnsigned', 3)],
-                        'MyClass', False)
-
-        self.assert_multi_line_lint('class NoProblemsHere {\n'
-                                    '  bool m_boolMember;\n'
-                                    '  unsigned m_unsignedMember;\n'
-                                    '  unsigned m_bitField1 : 1;\n'
-                                    '  unsigned m_bitField4 : 4;\n'
-                                    '}\n', '')
-
     # Bitfields which are not declared unsigned or bool will generate a warning.
     def test_unsigned_bool_bitfields(self):
         def errmsg(member, name, bit_type):
diff --git a/third_party/libFuzzer/BUILD.gn b/third_party/libFuzzer/BUILD.gn
index 70d5c4f..ec3573f 100644
--- a/third_party/libFuzzer/BUILD.gn
+++ b/third_party/libFuzzer/BUILD.gn
@@ -41,7 +41,11 @@
   ]
 
   configs -= fuzzing_engine_remove_configs
-  configs += fuzzing_engine_add_configs
+  if (!is_win) {
+    # TODO(crbug.com/883948): Remove the check for is_win when libFuzzer on
+    # Windows handles the nosanitize attribute properly.
+    configs += fuzzing_engine_add_configs
+  }
 }
 
 source_set("afl_driver") {
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index d2abcd61..bd6488f3 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -24191,6 +24191,9 @@
   <int value="2" label="Resumed">
     The user clicked the InfoBar again resuming subresource loading.
   </int>
+  <int value="3" label="Network Usage Stopped">
+    The InfoBar was dismissed because the network usage on the page stopped.
+  </int>
 </enum>
 
 <enum name="HIDContinueScenarioType">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index aeafb5a..980e688 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -5223,11 +5223,34 @@
   </summary>
 </histogram>
 
+<histogram name="Assistant.OptInFlow.GetSettingsRequestTime" units="ms">
+  <owner>updowndota@chromium.org</owner>
+  <summary>
+    Amount of time between get settings request and response. The request will
+    be sent for each time the Assistant OptIn Flow is triggered to get string
+    texts from the server.
+  </summary>
+</histogram>
+
+<histogram name="Assistant.OptInFlow.LoadingTimeoutCount">
+  <owner>updowndota@chromium.org</owner>
+  <summary>
+    Number of times that the loading timeout triggers. The loading timeout is
+    set by the Assistant OptIn Flow loading screen, which shows up during the
+    server string texts and webviews are pending.
+  </summary>
+</histogram>
+
 <histogram name="Assistant.OptInFlowStatus" enum="AssistantOptInFlowStatus">
   <owner>updowndota@chromium.org</owner>
   <summary>Record the status of the Assistant opt-in flow.</summary>
 </histogram>
 
+<histogram name="Assistant.ServiceStartTime" units="ms">
+  <owner>updowndota@chromium.org</owner>
+  <summary>Amount of time spent in starting Assistant service.</summary>
+</histogram>
+
 <histogram name="AsyncDNS.AttemptCountFail">
   <owner>mgersh@chromium.org</owner>
   <summary>
@@ -38956,7 +38979,7 @@
 </histogram>
 
 <histogram name="interstitial.ssl_nonoverridable.is_recurrent_error"
-    enum="BooleanRecurrentError" expires_after="M71">
+    enum="BooleanRecurrentError" expires_after="M72">
   <owner>estark@chromium.org</owner>
   <summary>
     Recorded when a non-overridable SSL interstitial is shown. The value is true
@@ -38966,7 +38989,7 @@
 </histogram>
 
 <histogram name="interstitial.ssl_nonoverridable.is_recurrent_error.ct_error"
-    enum="BooleanRecurrentError" expires_after="M71">
+    enum="BooleanRecurrentError" expires_after="M72">
   <owner>estark@chromium.org</owner>
   <summary>
     Recorded when a non-overridable SSL interstitial is shown for a Certificate
@@ -38977,7 +39000,7 @@
 </histogram>
 
 <histogram name="interstitial.ssl_overridable.is_recurrent_error"
-    enum="BooleanRecurrentError" expires_after="M71">
+    enum="BooleanRecurrentError" expires_after="M72">
   <owner>estark@chromium.org</owner>
   <summary>
     Recorded when an overridable SSL interstitial is shown. The value is true if
@@ -38987,7 +39010,7 @@
 </histogram>
 
 <histogram name="interstitial.ssl_overridable.is_recurrent_error.ct_error"
-    enum="BooleanRecurrentError" expires_after="M71">
+    enum="BooleanRecurrentError" expires_after="M72">
   <owner>estark@chromium.org</owner>
   <summary>
     Recorded when an overridable SSL interstitial is shown for a Certificate
@@ -38998,7 +39021,7 @@
 </histogram>
 
 <histogram name="interstitial.ssl_recurrent_error.action"
-    enum="RecurrentErrorAction" expires_after="M71">
+    enum="RecurrentErrorAction" expires_after="M72">
   <owner>estark@chromium.org</owner>
   <summary>
     Recorded whenever the user sees or proceeds through an SSL interstitial for
@@ -39008,7 +39031,7 @@
 </histogram>
 
 <histogram name="interstitial.ssl_recurrent_error.ct_error.action"
-    enum="RecurrentErrorAction" expires_after="M71">
+    enum="RecurrentErrorAction" expires_after="M72">
   <owner>estark@chromium.org</owner>
   <summary>
     Recorded whenever the user sees or proceeds through an SSL interstitial for
@@ -65016,6 +65039,26 @@
   </summary>
 </histogram>
 
+<histogram name="Notifications.Windows.ImageRetainerDestructionTime" units="ms">
+  <owner>chengx@chromium.org</owner>
+  <owner>finnur@chromium.org</owner>
+  <owner>peter@chromium.org</owner>
+  <summary>
+    Amount of time taken to destroy a NotificationImageRetainer object when the
+    image directory is valid.
+  </summary>
+</histogram>
+
+<histogram name="Notifications.Windows.ImageRetainerInitializationTime"
+    units="ms">
+  <owner>chengx@chromium.org</owner>
+  <owner>finnur@chromium.org</owner>
+  <owner>peter@chromium.org</owner>
+  <summary>
+    Amount of time taken to initialize the image retainer directory.
+  </summary>
+</histogram>
+
 <histogram name="Notifications.Windows.LaunchIdDecodeStatus"
     enum="WindowsNotificationLaunchIdDecodeStatus">
   <owner>chengx@chromium.org</owner>
diff --git a/ui/accessibility/mojom/ax_host.mojom b/ui/accessibility/mojom/ax_host.mojom
index e3865b6..23826d3 100644
--- a/ui/accessibility/mojom/ax_host.mojom
+++ b/ui/accessibility/mojom/ax_host.mojom
@@ -18,9 +18,12 @@
 //   browser.
 // * Sends requests for actions (e.g. click a button) to the remote process.
 interface AXHost {
-  // Registers a host in a remote process.
-  // TODO(jamescook): Support multiple remote processes.
-  SetRemoteHost(AXRemoteHost remote);
+  // Registers a host in a remote process. |tree_id| is the ID to use as the
+  // root of the AX node tree. If |automation_enabled| is true then the remote
+  // process must send its initial AX node tree immediately (because a feature
+  // like ChromeVox is enabled).
+  SetRemoteHost(AXRemoteHost remote) =>
+      (string tree_id, bool automation_enabled);
 
   // Handles an accessibility |event| (e.g. focus change) for |tree_id| in the
   // remote process. Includes |updates| to child nodes.
diff --git a/ui/android/java/res/values/attrs.xml b/ui/android/java/res/values/attrs.xml
index c2989fe..e4d4b41 100644
--- a/ui/android/java/res/values/attrs.xml
+++ b/ui/android/java/res/values/attrs.xml
@@ -31,5 +31,6 @@
         <attr name="cornerRadiusTopEnd" format="dimension" />
         <attr name="cornerRadiusBottomStart" format="dimension" />
         <attr name="cornerRadiusBottomEnd" format="dimension" />
+        <attr name="roundedfillColor" format="reference|color" />
     </declare-styleable>
 </resources>
diff --git a/ui/android/java/src/org/chromium/ui/widget/RoundedCornerImageView.java b/ui/android/java/src/org/chromium/ui/widget/RoundedCornerImageView.java
index f974c6b5..9d42d7c 100644
--- a/ui/android/java/src/org/chromium/ui/widget/RoundedCornerImageView.java
+++ b/ui/android/java/src/org/chromium/ui/widget/RoundedCornerImageView.java
@@ -9,6 +9,7 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapShader;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Shader;
 import android.graphics.drawable.BitmapDrawable;
@@ -41,6 +42,8 @@
     private BitmapShader mShader;
     private Paint mPaint;
 
+    private Paint mFillPaint;
+
     // Whether or not to apply the shader, if we have one. This might be set to false if the image
     // is smaller than the view and does not need to have the corners rounded.
     private boolean mApplyShader;
@@ -66,6 +69,11 @@
                     R.styleable.RoundedCornerImageView_cornerRadiusBottomStart, 0);
             int cornerRadiusBottomEnd = a.getDimensionPixelSize(
                     R.styleable.RoundedCornerImageView_cornerRadiusBottomEnd, 0);
+            if (a.hasValue(R.styleable.RoundedCornerImageView_roundedfillColor)) {
+                mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+                mFillPaint.setColor(a.getColor(
+                        R.styleable.RoundedCornerImageView_roundedfillColor, Color.WHITE));
+            }
             a.recycle();
 
             setRoundedCorners(cornerRadiusTopStart, cornerRadiusTopEnd, cornerRadiusBottomStart,
@@ -179,12 +187,17 @@
         Drawable drawable = getDrawable();
         Shape localRoundedRect = mRoundedRectangle;
         Paint localPaint = mPaint;
-        if (drawable == null || localPaint == null || localRoundedRect == null) {
-            super.onDraw(canvas);
-            return;
-        }
 
-        if (!isSupportedDrawable(drawable)) {
+        boolean drawFill = mFillPaint != null && localRoundedRect != null
+                && !(drawable instanceof ColorDrawable);
+        boolean drawContent = drawable != null && localPaint != null && localRoundedRect != null
+                && isSupportedDrawable(drawable);
+
+        if (drawFill || drawContent) localRoundedRect.resize(getWidth(), getHeight());
+
+        if (drawFill) localRoundedRect.draw(canvas, mFillPaint);
+
+        if (!drawContent) {
             super.onDraw(canvas);
             return;
         }
@@ -199,7 +212,6 @@
             localPaint.setShader(mShader);
         }
 
-        localRoundedRect.resize(getWidth(), getHeight());
         localRoundedRect.draw(canvas, localPaint);
     }
 
diff --git a/ui/aura/mus/input_method_mus.cc b/ui/aura/mus/input_method_mus.cc
index 14d413f..cf1328a 100644
--- a/ui/aura/mus/input_method_mus.cc
+++ b/ui/aura/mus/input_method_mus.cc
@@ -19,6 +19,21 @@
 using ws::mojom::EventResult;
 
 namespace aura {
+namespace {
+
+void CallEventResultCallback(InputMethodMus::EventResultCallback ack_callback,
+                             bool handled) {
+  // |ack_callback| can be null if the standard form of DispatchKeyEvent() is
+  // called instead of the version which provides a callback. In mus+ash we
+  // use the version with callback, but some unittests use the standard form.
+  if (!ack_callback)
+    return;
+
+  std::move(ack_callback)
+      .Run(handled ? EventResult::HANDLED : EventResult::UNHANDLED);
+}
+
+}  // namespace
 
 ////////////////////////////////////////////////////////////////////////////////
 // InputMethodMus, public:
@@ -50,13 +65,9 @@
 
   // If no text input client, do nothing.
   if (!GetTextInputClient()) {
-    ui::EventDispatchDetails dispatch_details = DispatchKeyEventPostIME(event);
-    if (ack_callback) {
-      std::move(ack_callback)
-          .Run(event->handled() ? EventResult::HANDLED
-                                : EventResult::UNHANDLED);
-    }
-    return dispatch_details;
+    return DispatchKeyEventPostIME(
+        event,
+        base::BindOnce(&CallEventResultCallback, std::move(ack_callback)));
   }
 
   return SendKeyEventToInputMethod(*event, std::move(ack_callback));
@@ -136,7 +147,8 @@
     // This code path is hit in tests that don't connect to the server.
     DCHECK(!ack_callback);
     std::unique_ptr<ui::Event> event_clone = ui::Event::Clone(event);
-    return DispatchKeyEventPostIME(event_clone->AsKeyEvent());
+    return DispatchKeyEventPostIME(event_clone->AsKeyEvent(),
+                                   base::NullCallback());
   }
 
   // IME driver will notify us whether it handled the event or not by calling
@@ -216,14 +228,7 @@
   DCHECK(!pending_callbacks_.empty());
   EventResultCallback ack_callback = std::move(pending_callbacks_.front());
   pending_callbacks_.pop_front();
-
-  // |ack_callback| can be null if the standard form of DispatchKeyEvent() is
-  // called instead of the version which provides a callback. In mus+ash we
-  // use the version with callback, but some unittests use the standard form.
-  if (ack_callback) {
-    std::move(ack_callback)
-        .Run(handled ? EventResult::HANDLED : EventResult::UNHANDLED);
-  }
+  CallEventResultCallback(std::move(ack_callback), handled);
 }
 
 }  // namespace aura
diff --git a/ui/aura/mus/input_method_mus_unittest.cc b/ui/aura/mus/input_method_mus_unittest.cc
index 0d2a9b7..b70f263 100644
--- a/ui/aura/mus/input_method_mus_unittest.cc
+++ b/ui/aura/mus/input_method_mus_unittest.cc
@@ -21,12 +21,22 @@
   TestInputMethodDelegate() {}
   ~TestInputMethodDelegate() override {}
 
+  bool was_dispatch_key_event_post_ime_called() const {
+    return was_dispatch_key_event_post_ime_called_;
+  }
+
   // ui::internal::InputMethodDelegate:
-  ui::EventDispatchDetails DispatchKeyEventPostIME(ui::KeyEvent* key) override {
+  ui::EventDispatchDetails DispatchKeyEventPostIME(
+      ui::KeyEvent* key,
+      base::OnceCallback<void(bool)> ack_callback) override {
+    was_dispatch_key_event_post_ime_called_ = true;
+    CallDispatchKeyEventPostIMEAck(key, std::move(ack_callback));
     return ui::EventDispatchDetails();
   }
 
  private:
+  bool was_dispatch_key_event_post_ime_called_ = false;
+
   DISALLOW_COPY_AND_ASSIGN(TestInputMethodDelegate);
 };
 
@@ -106,7 +116,7 @@
 TEST_F(InputMethodMusTest, PendingCallbackRunFromDestruction) {
   bool was_event_result_callback_run = false;
   ws::mojom::EventResult event_result = ws::mojom::EventResult::UNHANDLED;
-  // Create an InputMethodMus and foward an event to it.
+  // Create an InputMethodMus and forward an event to it.
   {
     TestInputMethodDelegate input_method_delegate;
     InputMethodMus input_method_mus(&input_method_delegate, nullptr);
@@ -143,7 +153,7 @@
   bool was_event_result_callback_run = false;
   ws::mojom::EventResult event_result = ws::mojom::EventResult::UNHANDLED;
   ui::DummyTextInputClient test_input_client;
-  // Create an InputMethodMus and foward an event to it.
+  // Create an InputMethodMus and forward an event to it.
   TestInputMethodDelegate input_method_delegate;
   InputMethodMus input_method_mus(&input_method_delegate, nullptr);
   TestInputMethod test_input_method;
@@ -173,7 +183,7 @@
        PendingCallbackRunFromOnDidChangeFocusedClientToNull) {
   bool was_event_result_callback_run = false;
   ws::mojom::EventResult event_result = ws::mojom::EventResult::UNHANDLED;
-  // Create an InputMethodMus and foward an event to it.
+  // Create an InputMethodMus and forward an event to it.
   TestInputMethodDelegate input_method_delegate;
   InputMethodMus input_method_mus(&input_method_delegate, nullptr);
   TestInputMethod test_input_method;
@@ -216,9 +226,12 @@
   }
 
   // ui::internal::InputMethodDelegate:
-  ui::EventDispatchDetails DispatchKeyEventPostIME(ui::KeyEvent* key) override {
+  ui::EventDispatchDetails DispatchKeyEventPostIME(
+      ui::KeyEvent* key,
+      base::OnceCallback<void(bool)> ack_callback) override {
     was_dispatch_key_event_post_ime_called_ = true;
     input_method_mus_->SetFocusedTextInputClient(text_input_client_);
+    CallDispatchKeyEventPostIMEAck(key, std::move(ack_callback));
     return ui::EventDispatchDetails();
   }
 
@@ -237,7 +250,7 @@
   bool was_event_result_callback_run = false;
   ws::mojom::EventResult event_result = ws::mojom::EventResult::UNHANDLED;
   ui::DummyTextInputClient test_input_client;
-  // Create an InputMethodMus and foward an event to it.
+  // Create an InputMethodMus and forward an event to it.
   TestInputMethodDelegate2 input_method_delegate;
   InputMethodMus input_method_mus(&input_method_delegate, nullptr);
   input_method_delegate.SetInputMethodAndClient(&input_method_mus,
@@ -262,6 +275,7 @@
   // Callback should have been run.
   EXPECT_TRUE(was_event_result_callback_run);
   EXPECT_EQ(ws::mojom::EventResult::HANDLED, event_result);
+  EXPECT_FALSE(input_method_delegate.was_dispatch_key_event_post_ime_called());
 }
 
 // Calling OnTextInputTypeChanged from unfocused client should
@@ -332,4 +346,37 @@
   EXPECT_TRUE(test_input_method.was_show_virtual_keyboard_if_enabled_called());
 }
 
+TEST_F(InputMethodMusTest, AckUnhandledCallsDispatchKeyEventPostUnhandled) {
+  bool was_event_result_callback_run = false;
+  ws::mojom::EventResult event_result = ws::mojom::EventResult::UNHANDLED;
+  // Create an InputMethodMus and forward an event to it.
+  TestInputMethodDelegate input_method_delegate;
+  InputMethodMus input_method_mus(&input_method_delegate, nullptr);
+  TestInputMethod test_input_method;
+  InputMethodMusTestApi::SetInputMethod(&input_method_mus, &test_input_method);
+  EventResultCallback callback =
+      base::BindOnce(&RunFunctionWithEventResult,
+                     &was_event_result_callback_run, &event_result);
+  const ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_RETURN, 0);
+  ignore_result(InputMethodMusTestApi::CallSendKeyEventToInputMethod(
+      &input_method_mus, key_event, std::move(callback)));
+  // The event should have been queued.
+  ASSERT_EQ(1u, test_input_method.process_key_event_callbacks()->size());
+  // Callback should not have been run yet.
+  EXPECT_FALSE(was_event_result_callback_run);
+  const bool handled = false;
+  std::move((*test_input_method.process_key_event_callbacks())[0]).Run(handled);
+  test_input_method.process_key_event_callbacks()->clear();
+
+  // Callback should have been run.
+  EXPECT_TRUE(was_event_result_callback_run);
+  EXPECT_EQ(ws::mojom::EventResult::UNHANDLED, event_result);
+
+  // DispatchKeyEventPostIME() should not have beeen called. In production
+  // the following calls result:
+  // InputMethodChromeOS -> RemoteTextInputClient -> TextInputClientImpl ->
+  // InputMethodMus's delegate.
+  EXPECT_FALSE(input_method_delegate.was_dispatch_key_event_post_ime_called());
+}
+
 }  // namespace aura
diff --git a/ui/aura/mus/text_input_client_impl.cc b/ui/aura/mus/text_input_client_impl.cc
index 0ce8eb1..29121c5 100644
--- a/ui/aura/mus/text_input_client_impl.cc
+++ b/ui/aura/mus/text_input_client_impl.cc
@@ -52,9 +52,8 @@
     std::unique_ptr<ui::Event> event,
     DispatchKeyEventPostIMECallback callback) {
   if (delegate_) {
-    delegate_->DispatchKeyEventPostIME(event->AsKeyEvent());
-    if (callback && !callback.is_null())
-      std::move(callback).Run(event->stopped_propagation());
+    delegate_->DispatchKeyEventPostIME(event->AsKeyEvent(),
+                                       std::move(callback));
   }
 }
 
diff --git a/ui/aura/window_tree_host.cc b/ui/aura/window_tree_host.cc
index f9a2c60..639b64d 100644
--- a/ui/aura/window_tree_host.cc
+++ b/ui/aura/window_tree_host.cc
@@ -243,7 +243,8 @@
 }
 
 ui::EventDispatchDetails WindowTreeHost::DispatchKeyEventPostIME(
-    ui::KeyEvent* event) {
+    ui::KeyEvent* event,
+    base::OnceCallback<void(bool)> ack_callback) {
   // If dispatch to IME is already disabled we shouldn't reach here.
   DCHECK(!dispatcher_->should_skip_ime());
   dispatcher_->set_skip_ime(true);
@@ -252,6 +253,7 @@
       event_sink()->OnEventFromSource(event);
   if (!dispatch_details.dispatcher_destroyed)
     dispatcher_->set_skip_ime(false);
+  CallDispatchKeyEventPostIMEAck(event, std::move(ack_callback));
   return dispatch_details;
 }
 
diff --git a/ui/aura/window_tree_host.h b/ui/aura/window_tree_host.h
index 47ba76f8..62954bf 100644
--- a/ui/aura/window_tree_host.h
+++ b/ui/aura/window_tree_host.h
@@ -171,7 +171,9 @@
   void SetSharedInputMethod(ui::InputMethod* input_method);
 
   // Overridden from ui::internal::InputMethodDelegate:
-  ui::EventDispatchDetails DispatchKeyEventPostIME(ui::KeyEvent* event) final;
+  ui::EventDispatchDetails DispatchKeyEventPostIME(
+      ui::KeyEvent* event,
+      base::OnceCallback<void(bool)> ack_callback) final;
 
   // Returns the id of the display. Default implementation queries Screen.
   virtual int64_t GetDisplayId();
diff --git a/ui/base/ime/BUILD.gn b/ui/base/ime/BUILD.gn
index 072e0a38..c443847d 100644
--- a/ui/base/ime/BUILD.gn
+++ b/ui/base/ime/BUILD.gn
@@ -55,6 +55,7 @@
     "input_method_base.h",
     "input_method_chromeos.cc",
     "input_method_chromeos.h",
+    "input_method_delegate.cc",
     "input_method_delegate.h",
     "input_method_factory.cc",
     "input_method_factory.h",
diff --git a/ui/base/ime/input_method_auralinux.cc b/ui/base/ime/input_method_auralinux.cc
index 90a806a..f7343b3 100644
--- a/ui/base/ime/input_method_auralinux.cc
+++ b/ui/base/ime/input_method_auralinux.cc
@@ -6,6 +6,7 @@
 
 #include "base/auto_reset.h"
 #include "base/bind.h"
+#include "base/bind_helpers.h"
 #include "base/environment.h"
 #include "ui/base/ime/ime_bridge.h"
 #include "ui/base/ime/ime_engine_handler_interface.h"
@@ -52,11 +53,12 @@
 
   // If no text input client, do nothing.
   if (!GetTextInputClient())
-    return DispatchKeyEventPostIME(event);
+    return DispatchKeyEventPostIME(event, base::NullCallback());
 
   if (!event->HasNativeEvent() && sending_key_event()) {
     // Faked key events that are sent from input.ime.sendKeyEvents.
-    ui::EventDispatchDetails details = DispatchKeyEventPostIME(event);
+    ui::EventDispatchDetails details =
+        DispatchKeyEventPostIME(event, base::NullCallback());
     if (details.dispatcher_destroyed || details.target_destroyed ||
         event->stopped_propagation()) {
       return details;
@@ -135,7 +137,7 @@
   ui::EventDispatchDetails details;
   if (event->type() == ui::ET_KEY_PRESSED && filtered) {
     if (NeedInsertChar())
-      details = DispatchKeyEventPostIME(event);
+      details = DispatchKeyEventPostIME(event, base::NullCallback());
     else if (HasInputMethodResult())
       details = SendFakeProcessKeyEvent(event);
     if (details.dispatcher_destroyed)
@@ -194,7 +196,7 @@
     composition_ = CompositionText();
 
   if (!filtered) {
-    details = DispatchKeyEventPostIME(event);
+    details = DispatchKeyEventPostIME(event, base::NullCallback());
     if (details.dispatcher_destroyed) {
       if (should_stop_propagation)
         event->StopPropagation();
@@ -441,7 +443,8 @@
 ui::EventDispatchDetails InputMethodAuraLinux::SendFakeProcessKeyEvent(
     ui::KeyEvent* event) const {
   KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_PROCESSKEY, event->flags());
-  ui::EventDispatchDetails details = DispatchKeyEventPostIME(&key_event);
+  ui::EventDispatchDetails details =
+      DispatchKeyEventPostIME(&key_event, base::NullCallback());
   if (key_event.stopped_propagation())
     event->StopPropagation();
   return details;
diff --git a/ui/base/ime/input_method_auralinux_unittest.cc b/ui/base/ime/input_method_auralinux_unittest.cc
index 23e6be6..24963915 100644
--- a/ui/base/ime/input_method_auralinux_unittest.cc
+++ b/ui/base/ime/input_method_auralinux_unittest.cc
@@ -171,7 +171,8 @@
   ~InputMethodDelegateForTesting() override {}
 
   ui::EventDispatchDetails DispatchKeyEventPostIME(
-      ui::KeyEvent* key_event) override {
+      ui::KeyEvent* key_event,
+      base::OnceCallback<void(bool)> ack_callback) override {
     std::string action;
     switch (key_event->type()) {
       case ET_KEY_PRESSED:
@@ -187,6 +188,7 @@
     ss << key_event->key_code();
     action += std::string(ss.str());
     TestResult::GetInstance()->RecordAction(base::ASCIIToUTF16(action));
+    CallDispatchKeyEventPostIMEAck(key_event, std::move(ack_callback));
     return ui::EventDispatchDetails();
   }
 
diff --git a/ui/base/ime/input_method_base.cc b/ui/base/ime/input_method_base.cc
index cfc89cd4..76f14cac 100644
--- a/ui/base/ime/input_method_base.cc
+++ b/ui/base/ime/input_method_base.cc
@@ -5,6 +5,7 @@
 #include "ui/base/ime/input_method_base.h"
 
 #include "base/bind.h"
+#include "base/bind_helpers.h"
 #include "base/logging.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
@@ -161,23 +162,10 @@
 }
 
 ui::EventDispatchDetails InputMethodBase::DispatchKeyEventPostIME(
-    ui::KeyEvent* event) const {
-  ui::EventDispatchDetails details;
-  if (delegate_)
-    details = delegate_->DispatchKeyEventPostIME(event);
-  return details;
-}
-
-ui::EventDispatchDetails InputMethodBase::DispatchKeyEventPostIME(
     ui::KeyEvent* event,
     base::OnceCallback<void(bool)> ack_callback) const {
-  if (delegate_) {
-    ui::EventDispatchDetails details =
-        delegate_->DispatchKeyEventPostIME(event);
-    if (ack_callback)
-      std::move(ack_callback).Run(event->stopped_propagation());
-    return details;
-  }
+  if (delegate_)
+    return delegate_->DispatchKeyEventPostIME(event, std::move(ack_callback));
 
   if (ack_callback)
     std::move(ack_callback).Run(false);
@@ -231,7 +219,7 @@
 bool InputMethodBase::SendFakeProcessKeyEvent(bool pressed) const {
   KeyEvent evt(pressed ? ET_KEY_PRESSED : ET_KEY_RELEASED,
                pressed ? VKEY_PROCESSKEY : VKEY_UNKNOWN, EF_IME_FABRICATED_KEY);
-  ignore_result(DispatchKeyEventPostIME(&evt));
+  ignore_result(DispatchKeyEventPostIME(&evt, base::NullCallback()));
   return evt.stopped_propagation();
 }
 
diff --git a/ui/base/ime/input_method_base.h b/ui/base/ime/input_method_base.h
index 31fa274..afd0342 100644
--- a/ui/base/ime/input_method_base.h
+++ b/ui/base/ime/input_method_base.h
@@ -106,11 +106,6 @@
   // input type is not TEXT_INPUT_TYPE_NONE.
   void OnInputMethodChanged() const;
 
-  // Convenience method to call delegate_->DispatchKeyEventPostIME().
-  // Returns true if the event was processed
-  ui::EventDispatchDetails DispatchKeyEventPostIME(ui::KeyEvent* event) const
-      WARN_UNUSED_RESULT;
-
   virtual ui::EventDispatchDetails DispatchKeyEventPostIME(
       ui::KeyEvent* event,
       base::OnceCallback<void(bool)> ack_callback) const WARN_UNUSED_RESULT;
diff --git a/ui/base/ime/input_method_chromeos.cc b/ui/base/ime/input_method_chromeos.cc
index 3478bcd..918bd6e 100644
--- a/ui/base/ime/input_method_chromeos.cc
+++ b/ui/base/ime/input_method_chromeos.cc
@@ -474,7 +474,7 @@
   if (stopped_propagation) {
     ResetContext();
     if (ack_callback)
-      std::move(ack_callback).Run(false);
+      std::move(ack_callback).Run(true);  // true matches |stopped_propagation|.
     return;
   }
 
diff --git a/ui/base/ime/input_method_chromeos_unittest.cc b/ui/base/ime/input_method_chromeos_unittest.cc
index 6be9f33..5b4a72a8 100644
--- a/ui/base/ime/input_method_chromeos_unittest.cc
+++ b/ui/base/ime/input_method_chromeos_unittest.cc
@@ -237,10 +237,12 @@
 
   // Overridden from ui::internal::InputMethodDelegate:
   ui::EventDispatchDetails DispatchKeyEventPostIME(
-      ui::KeyEvent* event) override {
+      ui::KeyEvent* event,
+      base::OnceCallback<void(bool)> ack_callback) override {
     dispatched_key_event_ = *event;
     if (stop_propagation_post_ime_)
       event->StopPropagation();
+    CallDispatchKeyEventPostIMEAck(event, std::move(ack_callback));
     return ui::EventDispatchDetails();
   }
 
diff --git a/ui/base/ime/input_method_delegate.cc b/ui/base/ime/input_method_delegate.cc
new file mode 100644
index 0000000..f2578a514
--- /dev/null
+++ b/ui/base/ime/input_method_delegate.cc
@@ -0,0 +1,22 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/base/ime/input_method_delegate.h"
+
+#include "base/callback.h"
+#include "ui/events/event.h"
+
+namespace ui {
+namespace internal {
+
+// static
+void InputMethodDelegate::CallDispatchKeyEventPostIMEAck(
+    KeyEvent* key_event,
+    base::OnceCallback<void(bool)> ack_callback) {
+  if (ack_callback)
+    std::move(ack_callback).Run(key_event->stopped_propagation());
+}
+
+}  // namespace internal
+}  // namespace ui
diff --git a/ui/base/ime/input_method_delegate.h b/ui/base/ime/input_method_delegate.h
index 7bfa01a2..4bfae03 100644
--- a/ui/base/ime/input_method_delegate.h
+++ b/ui/base/ime/input_method_delegate.h
@@ -5,13 +5,15 @@
 #ifndef UI_BASE_IME_INPUT_METHOD_DELEGATE_H_
 #define UI_BASE_IME_INPUT_METHOD_DELEGATE_H_
 
+#include "base/callback_forward.h"
 #include "ui/base/ime/ui_base_ime_export.h"
-#include "ui/events/event_dispatcher.h"
 
 namespace ui {
 
 class KeyEvent;
 
+struct EventDispatchDetails;
+
 namespace internal {
 
 // An interface implemented by the object that handles events sent back from an
@@ -20,10 +22,19 @@
  public:
   virtual ~InputMethodDelegate() {}
 
-  // Dispatch a key event already processed by the input method.
-  // Returns true if the event was processed.
-  virtual ui::EventDispatchDetails DispatchKeyEventPostIME(
-      ui::KeyEvent* key_event) = 0;
+  // Dispatch a key event already processed by the input method. Returns the
+  // status of processing, as well as running the callback |ack_callback| with
+  // the result of processing. |ack_callback| may be run asynchronously (if the
+  // delegate does processing async). |ack_callback| may not be null.
+  // Subclasses can use CallDispatchKeyEventPostIMEAck() to run the callback.
+  virtual EventDispatchDetails DispatchKeyEventPostIME(
+      KeyEvent* key_event,
+      base::OnceCallback<void(bool)> ack_callback) = 0;
+
+ protected:
+  static void CallDispatchKeyEventPostIMEAck(
+      KeyEvent* key_event,
+      base::OnceCallback<void(bool)> ack_callback);
 };
 
 }  // namespace internal
diff --git a/ui/base/ime/input_method_fuchsia.cc b/ui/base/ime/input_method_fuchsia.cc
index 02ed1a98..83f18d4 100644
--- a/ui/base/ime/input_method_fuchsia.cc
+++ b/ui/base/ime/input_method_fuchsia.cc
@@ -8,6 +8,7 @@
 #include <memory>
 #include <utility>
 
+#include "base/bind_helpers.h"
 #include "base/fuchsia/component_context.h"
 #include "ui/base/ime/fuchsia/input_method_keyboard_controller_fuchsia.h"
 #include "ui/base/ime/text_input_client.h"
@@ -38,10 +39,11 @@
 
   // If no text input client, do nothing.
   if (!GetTextInputClient())
-    return DispatchKeyEventPostIME(event);
+    return DispatchKeyEventPostIME(event, base::NullCallback());
 
   // Insert the character.
-  ui::EventDispatchDetails dispatch_details = DispatchKeyEventPostIME(event);
+  ui::EventDispatchDetails dispatch_details =
+      DispatchKeyEventPostIME(event, base::NullCallback());
   if (!event->stopped_propagation() && !dispatch_details.dispatcher_destroyed &&
       event->type() == ET_KEY_PRESSED && GetTextInputClient()) {
     const uint16_t ch = event->GetCharacter();
diff --git a/ui/base/ime/input_method_mac.mm b/ui/base/ime/input_method_mac.mm
index e42a251..f23bce9 100644
--- a/ui/base/ime/input_method_mac.mm
+++ b/ui/base/ime/input_method_mac.mm
@@ -6,6 +6,8 @@
 
 #import <Cocoa/Cocoa.h>
 
+#include "base/bind_helpers.h"
+
 namespace ui {
 
 InputMethodMac::InputMethodMac(internal::InputMethodDelegate* delegate)
@@ -16,7 +18,7 @@
 
 ui::EventDispatchDetails InputMethodMac::DispatchKeyEvent(ui::KeyEvent* event) {
   // This is used on Mac only to dispatch events post-IME.
-  return DispatchKeyEventPostIME(event);
+  return DispatchKeyEventPostIME(event, base::NullCallback());
 }
 
 void InputMethodMac::OnCaretBoundsChanged(const TextInputClient* client) {
diff --git a/ui/base/ime/input_method_minimal.cc b/ui/base/ime/input_method_minimal.cc
index 98b1cd0..fb00879 100644
--- a/ui/base/ime/input_method_minimal.cc
+++ b/ui/base/ime/input_method_minimal.cc
@@ -6,6 +6,7 @@
 
 #include <stdint.h>
 
+#include "base/bind_helpers.h"
 #include "ui/base/ime/text_input_client.h"
 #include "ui/events/event.h"
 #include "ui/events/event_constants.h"
@@ -23,10 +24,11 @@
 
   // If no text input client, do nothing.
   if (!GetTextInputClient())
-    return DispatchKeyEventPostIME(event);
+    return DispatchKeyEventPostIME(event, base::NullCallback());
 
   // Insert the character.
-  ui::EventDispatchDetails dispatch_details = DispatchKeyEventPostIME(event);
+  ui::EventDispatchDetails dispatch_details =
+      DispatchKeyEventPostIME(event, base::NullCallback());
   if (!event->stopped_propagation() && !dispatch_details.dispatcher_destroyed &&
       event->type() == ET_KEY_PRESSED && GetTextInputClient()) {
     const uint16_t ch = event->GetCharacter();
diff --git a/ui/base/ime/input_method_minimal_unittest.cc b/ui/base/ime/input_method_minimal_unittest.cc
index 2b9df961..9eadb6a 100644
--- a/ui/base/ime/input_method_minimal_unittest.cc
+++ b/ui/base/ime/input_method_minimal_unittest.cc
@@ -21,9 +21,11 @@
   ~InputMethodDelegateForTesting() override {}
 
   ui::EventDispatchDetails DispatchKeyEventPostIME(
-      ui::KeyEvent* key_event) override {
+      ui::KeyEvent* key_event,
+      base::OnceCallback<void(bool)> ack_callback) override {
     if (!propagation_post_ime_)
       key_event->StopPropagation();
+    CallDispatchKeyEventPostIMEAck(key_event, std::move(ack_callback));
     return ui::EventDispatchDetails();
   }
 
diff --git a/ui/base/ime/input_method_win_base.cc b/ui/base/ime/input_method_win_base.cc
index 046f0d05..333607e7 100644
--- a/ui/base/ime/input_method_win_base.cc
+++ b/ui/base/ime/input_method_win_base.cc
@@ -10,6 +10,7 @@
 
 #include "base/auto_reset.h"
 #include "base/bind.h"
+#include "base/bind_helpers.h"
 #include "base/command_line.h"
 #include "base/memory/ptr_util.h"
 #include "base/win/windows_version.h"
@@ -498,7 +499,8 @@
     ui::KeyEvent* event,
     const std::vector<MSG>* char_msgs) {
   DCHECK(event);
-  ui::EventDispatchDetails details = DispatchKeyEventPostIME(event);
+  ui::EventDispatchDetails details =
+      DispatchKeyEventPostIME(event, base::NullCallback());
   if (details.dispatcher_destroyed || details.target_destroyed ||
       event->stopped_propagation()) {
     return details;
diff --git a/ui/base/ime/mock_input_method.cc b/ui/base/ime/mock_input_method.cc
index 09cde9c..04ff47b 100644
--- a/ui/base/ime/mock_input_method.cc
+++ b/ui/base/ime/mock_input_method.cc
@@ -3,8 +3,10 @@
 // found in the LICENSE file.
 
 #include "ui/base/ime/mock_input_method.h"
-#include "build/build_config.h"
 
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "build/build_config.h"
 #include "ui/base/ime/input_method_delegate.h"
 #include "ui/events/event.h"
 
@@ -43,7 +45,7 @@
 
 ui::EventDispatchDetails MockInputMethod::DispatchKeyEvent(
     ui::KeyEvent* event) {
-  return delegate_->DispatchKeyEventPostIME(event);
+  return delegate_->DispatchKeyEventPostIME(event, base::NullCallback());
 }
 
 void MockInputMethod::OnFocus() {
diff --git a/ui/base/material_design/material_design_controller.cc b/ui/base/material_design/material_design_controller.cc
index 8874a25..fb2cd53 100644
--- a/ui/base/material_design/material_design_controller.cc
+++ b/ui/base/material_design/material_design_controller.cc
@@ -213,10 +213,8 @@
   return MATERIAL_REFRESH;
 #endif  // defined(OS_CHROMEOS)
 
-#if defined(OS_WIN) || defined(OS_LINUX)
+#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_MACOSX)
   return MATERIAL_REFRESH;
-#elif defined(OS_MACOSX) && BUILDFLAG(MAC_VIEWS_BROWSER)
-  return features::IsViewsBrowserCocoa() ? MATERIAL_NORMAL : MATERIAL_REFRESH;
 #else
   return MATERIAL_NORMAL;
 #endif
diff --git a/ui/events/event_unittest.cc b/ui/events/event_unittest.cc
index 7c7dbf9..6c40d31 100644
--- a/ui/events/event_unittest.cc
+++ b/ui/events/event_unittest.cc
@@ -25,6 +25,7 @@
 
 #if defined(USE_X11)
 #include "ui/events/test/events_test_utils_x11.h"
+#include "ui/events/x/events_x_utils.h"  // nogncheck
 #include "ui/gfx/x/x11.h"        // nogncheck
 #include "ui/gfx/x/x11_types.h"  // nogncheck
 #endif
@@ -430,6 +431,15 @@
 #if defined(USE_X11)
 namespace {
 
+class MockTimestampServer : public ui::TimestampServer {
+ public:
+  Time GetCurrentServerTime() override { return base_time_; }
+  void SetBaseTime(Time time) { base_time_ = time; }
+
+ private:
+  Time base_time_ = 0;
+};
+
 void SetKeyEventTimestamp(XEvent* event, int64_t time) {
   event->xkey.time = time & UINT32_MAX;
 }
@@ -440,7 +450,23 @@
 
 }  // namespace
 
-TEST(EventTest, AutoRepeat) {
+class X11EventTest : public testing::Test {
+ public:
+  X11EventTest() {}
+  ~X11EventTest() override {}
+
+  void SetUp() override { SetTimestampServer(&server_); }
+
+  void TearDown() override { SetTimestampServer(nullptr); }
+
+ protected:
+  MockTimestampServer server_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(X11EventTest);
+};
+
+TEST_F(X11EventTest, AutoRepeat) {
   const uint16_t kNativeCodeA =
       ui::KeycodeConverter::DomCodeToNativeKeycode(DomCode::US_A);
   const uint16_t kNativeCodeB =
@@ -468,6 +494,7 @@
 
   int64_t ticks_base =
       (base::TimeTicks::Now() - base::TimeTicks()).InMilliseconds() - 5000;
+  server_.SetBaseTime(static_cast<Time>(ticks_base));
   SetKeyEventTimestamp(native_event_a_pressed, ticks_base);
   SetKeyEventTimestamp(native_event_a_pressed_1500, ticks_base + 1500);
   SetKeyEventTimestamp(native_event_a_pressed_3000, ticks_base + 3000);
@@ -538,12 +565,14 @@
     EXPECT_FALSE(key_a4_pressed_nonstandard_state.is_repeat());
   }
 
+  // The nonstandard event from above was ignored, so the last event pressed was
+  // |native_event_a_pressed|. These are both repeats.
   {
     KeyEvent key_a1(native_event_a_pressed);
-    EXPECT_FALSE(key_a1.is_repeat());
+    EXPECT_TRUE(key_a1.is_repeat());
 
     KeyEvent key_a1_with_same_event(native_event_a_pressed);
-    EXPECT_FALSE(key_a1_with_same_event.is_repeat());
+    EXPECT_TRUE(key_a1_with_same_event.is_repeat());
   }
 }
 #endif  // USE_X11
diff --git a/ui/events/platform/x11/x11_event_source.cc b/ui/events/platform/x11/x11_event_source.cc
index 7e9f6df..a68188d 100644
--- a/ui/events/platform/x11/x11_event_source.cc
+++ b/ui/events/platform/x11/x11_event_source.cc
@@ -100,6 +100,7 @@
       distribution_(0, 999) {
   DCHECK(!instance_);
   instance_ = this;
+  SetTimestampServer(this);
 
   DCHECK(delegate_);
   DCHECK(display_);
@@ -110,6 +111,7 @@
 X11EventSource::~X11EventSource() {
   DCHECK_EQ(this, instance_);
   instance_ = nullptr;
+  SetTimestampServer(nullptr);
   if (dummy_initialized_)
     XDestroyWindow(display_, dummy_window_);
 }
diff --git a/ui/events/platform/x11/x11_event_source.h b/ui/events/platform/x11/x11_event_source.h
index e818c4a..c2ee795 100644
--- a/ui/events/platform/x11/x11_event_source.h
+++ b/ui/events/platform/x11/x11_event_source.h
@@ -14,6 +14,7 @@
 #include "base/optional.h"
 #include "build/build_config.h"
 #include "ui/events/events_export.h"
+#include "ui/events/x/events_x_utils.h"
 #include "ui/gfx/x/x11_types.h"
 
 using Time = unsigned long;
@@ -46,7 +47,7 @@
 
 // Receives X11 events and sends them to X11EventSourceDelegate. Handles
 // receiving, pre-process and post-processing XEvents.
-class EVENTS_EXPORT X11EventSource {
+class EVENTS_EXPORT X11EventSource : TimestampServer {
  public:
   X11EventSource(X11EventSourceDelegate* delegate, XDisplay* display);
   ~X11EventSource();
@@ -82,7 +83,7 @@
 
   // Explicitly asks the X11 server for the current timestamp, and updates
   // |last_seen_server_time_| with this value.
-  Time GetCurrentServerTime();
+  Time GetCurrentServerTime() override;
 
  protected:
   // Extracts cookie data from |xevent| if it's of GenericType, and dispatches
diff --git a/ui/events/x/events_x.cc b/ui/events/x/events_x.cc
index b8df7b0..6253031 100644
--- a/ui/events/x/events_x.cc
+++ b/ui/events/x/events_x.cc
@@ -81,9 +81,7 @@
 }
 
 base::TimeTicks EventTimeFromNative(const PlatformEvent& native_event) {
-  base::TimeTicks timestamp = EventTimeFromXEvent(*native_event);
-  ValidateEventTimeClock(&timestamp);
-  return timestamp;
+  return EventTimeFromXEvent(*native_event);
 }
 
 gfx::PointF EventLocationFromNative(const PlatformEvent& native_event) {
diff --git a/ui/events/x/events_x_unittest.cc b/ui/events/x/events_x_unittest.cc
index 897c5c5..3419309 100644
--- a/ui/events/x/events_x_unittest.cc
+++ b/ui/events/x/events_x_unittest.cc
@@ -76,6 +76,15 @@
   return rotation_angle;
 }
 
+class MockTimestampServer : public ui::TimestampServer {
+ public:
+  Time GetCurrentServerTime() override { return base_time_; }
+  void SetBaseTime(Time time) { base_time_ = time; }
+
+ private:
+  Time base_time_ = 0;
+};
+
 }  // namespace
 
 class EventsXTest : public testing::Test {
@@ -84,13 +93,15 @@
   ~EventsXTest() override {}
 
   void SetUp() override {
+    SetTimestampServer(&server_);
     DeviceDataManagerX11::CreateInstance();
     ui::TouchFactory::GetInstance()->ResetForTest();
-    ResetTimestampRolloverCountersForTesting();
   }
-  void TearDown() override { ResetTimestampRolloverCountersForTesting(); }
+
+  void TearDown() override { SetTimestampServer(nullptr); }
 
  private:
+  MockTimestampServer server_;
   DISALLOW_COPY_AND_ASSIGN(EventsXTest);
 };
 
@@ -542,52 +553,4 @@
   EXPECT_EQ(ui::ET_UNKNOWN, ui::EventTypeFromNative(xev));
 }
 
-namespace {
-
-// Returns a fake TimeTicks based on the given millisecond offset.
-base::TimeTicks TimeTicksFromMillis(int64_t millis) {
-  return base::TimeTicks() + base::TimeDelta::FromMilliseconds(millis);
-}
-
-}  // namespace
-
-TEST_F(EventsXTest, TimestampRolloverAndAdjustWhenDecreasing) {
-  XEvent event;
-  InitButtonEvent(&event, true, gfx::Point(5, 10), 1, 0);
-
-  test::ScopedEventTestTickClock clock;
-  clock.SetNowTicks(TimeTicksFromMillis(0x100000001));
-  ResetTimestampRolloverCountersForTesting();
-
-  event.xbutton.time = 0xFFFFFFFF;
-  EXPECT_EQ(TimeTicksFromMillis(0xFFFFFFFF), ui::EventTimeFromNative(&event));
-
-  clock.SetNowTicks(TimeTicksFromMillis(0x100000007));
-  ResetTimestampRolloverCountersForTesting();
-
-  event.xbutton.time = 3;
-  EXPECT_EQ(TimeTicksFromMillis(0x100000000 + 3),
-            ui::EventTimeFromNative(&event));
-}
-
-TEST_F(EventsXTest, NoTimestampRolloverWhenMonotonicIncreasing) {
-  XEvent event;
-  InitButtonEvent(&event, true, gfx::Point(5, 10), 1, 0);
-
-  test::ScopedEventTestTickClock clock;
-  clock.SetNowTicks(TimeTicksFromMillis(10));
-  ResetTimestampRolloverCountersForTesting();
-
-  event.xbutton.time = 6;
-  EXPECT_EQ(TimeTicksFromMillis(6), ui::EventTimeFromNative(&event));
-  event.xbutton.time = 7;
-  EXPECT_EQ(TimeTicksFromMillis(7), ui::EventTimeFromNative(&event));
-
-  clock.SetNowTicks(TimeTicksFromMillis(0x100000005));
-  ResetTimestampRolloverCountersForTesting();
-
-  event.xbutton.time = 0xFFFFFFFF;
-  EXPECT_EQ(TimeTicksFromMillis(0xFFFFFFFF), ui::EventTimeFromNative(&event));
-}
-
 }  // namespace ui
diff --git a/ui/events/x/events_x_utils.cc b/ui/events/x/events_x_utils.cc
index 86fbd19..be8256c 100644
--- a/ui/events/x/events_x_utils.cc
+++ b/ui/events/x/events_x_utils.cc
@@ -27,6 +27,24 @@
 
 namespace {
 
+ui::TimestampServer* g_timestamp_server = nullptr;
+
+// Clamps a TimeDelta to be within [0 seconds, 30 seconds].
+base::TimeDelta ClampDeltaFromExternalSource(const base::TimeDelta& delta) {
+  // Ignore pathologically long deltas. External source is probably having
+  // issues.
+  constexpr base::TimeDelta pathologically_long_duration =
+      base::TimeDelta::FromSeconds(30);
+  if (delta > pathologically_long_duration)
+    return base::TimeDelta();
+
+  // Ignore negative deltas. External source is probably having issues.
+  if (delta < base::TimeDelta())
+    return base::TimeDelta();
+
+  return delta;
+}
+
 // Scroll amount for each wheelscroll event. 53 is also the value used for GTK+.
 const int kWheelScrollAmount = 53;
 
@@ -308,40 +326,29 @@
   return true;
 }
 
-int64_t g_last_seen_timestamp_ms = 0;
-int64_t g_rollover_ms = 0;
-
-// Takes Xlib Time and returns a time delta that is immune to timer rollover.
-// This function is not thread safe as we do not use a lock.
 base::TimeTicks TimeTicksFromXEventTime(Time timestamp) {
-  int64_t timestamp64 = timestamp;
+  // There's no way to convert from an X time to a base::TimeTicks without
+  // knowing the current X server time.
+  if (!g_timestamp_server)
+    return base::TimeTicks();
 
-  if (!timestamp)
-    return ui::EventTimeForNow();
+  // X11 uses a uint32_t on the wire protocol. Xlib casts this to an unsigned
+  // long by prepending with 0s. We cast back to a uint32_t so that subtraction
+  // works properly when the timestamp overflows back to 0.
+  uint32_t event_server_time_ms = static_cast<uint32_t>(timestamp);
+  uint32_t current_server_time_ms =
+      static_cast<uint32_t>(g_timestamp_server->GetCurrentServerTime());
 
-  // If this is the first event that we get, assume the time stamp roll-over
-  // might have happened before the process was started.
-  // Register a rollover if the distance between last timestamp and current one
-  // is larger than half the width. This avoids false rollovers even in a case
-  // where X server delivers reasonably close events out-of-order.
-  bool had_recent_rollover =
-      !g_last_seen_timestamp_ms ||
-      g_last_seen_timestamp_ms - timestamp64 > (UINT32_MAX >> 1);
+  // On X11, event times are in X11 Server time. To convert to base::TimeTicks,
+  // we perform a round-trip to the X11 Server, subtract the two times to get a
+  // TimeDelta, and then subtract that from base::TimeTicks::Now(). Since we're
+  // working with units of time from an external source, we clamp the TimeDelta
+  // to reasonable values.
+  uint32_t delta_ms = current_server_time_ms - event_server_time_ms;
+  base::TimeDelta delta = base::TimeDelta::FromMilliseconds(delta_ms);
+  base::TimeDelta sanitized = ClampDeltaFromExternalSource(delta);
 
-  g_last_seen_timestamp_ms = timestamp64;
-  if (!had_recent_rollover)
-    return base::TimeTicks() +
-        base::TimeDelta::FromMilliseconds(g_rollover_ms + timestamp);
-
-  DCHECK(timestamp64 <= UINT32_MAX)
-      << "X11 Time does not roll over 32 bit, the below logic is likely wrong";
-
-  base::TimeTicks now_ticks = ui::EventTimeForNow();
-  int64_t now_ms = (now_ticks - base::TimeTicks()).InMilliseconds();
-
-  g_rollover_ms = now_ms & ~static_cast<int64_t>(UINT32_MAX);
-  uint32_t delta = static_cast<uint32_t>(now_ms - timestamp);
-  return base::TimeTicks() + base::TimeDelta::FromMilliseconds(now_ms - delta);
+  return base::TimeTicks::Now() - sanitized;
 }
 
 }  // namespace
@@ -831,9 +838,12 @@
   return XModifierStateWatcher::GetInstance()->state() & Mod1Mask;
 }
 
-void ResetTimestampRolloverCountersForTesting() {
-  g_last_seen_timestamp_ms = 0;
-  g_rollover_ms = 0;
+void SetTimestampServer(TimestampServer* server) {
+  // This method must be setting or unsetting a timestamp server. It should
+  // never replace an existing timestamp server, nor change from
+  // nullptr->nullptr.
+  CHECK(!!g_timestamp_server ^ !!server);
+  g_timestamp_server = server;
 }
 
 }  // namespace ui
diff --git a/ui/events/x/events_x_utils.h b/ui/events/x/events_x_utils.h
index 64814e8..df210f2 100644
--- a/ui/events/x/events_x_utils.h
+++ b/ui/events/x/events_x_utils.h
@@ -16,6 +16,8 @@
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/x/x11_types.h"
 
+using Time = unsigned long;
+
 namespace ui {
 
 // Gets the EventType from a XEvent.
@@ -91,6 +93,15 @@
 
 EVENTS_X_EXPORT void ResetTimestampRolloverCountersForTesting();
 
+// Conversion from X Time to base::TimeTicks requires checking the current X
+// Server Time. This functionality is provided by X11EventSource, but due to odd
+// layering that cannot be referenced directly.
+class TimestampServer {
+ public:
+  virtual Time GetCurrentServerTime() = 0;
+};
+EVENTS_X_EXPORT void SetTimestampServer(TimestampServer* server);
+
 }  // namespace ui
 
 #endif  // UI_EVENTS_X_EVENTS_X_UTILS_H_
diff --git a/ui/file_manager/file_manager/foreground/css/file_manager.css b/ui/file_manager/file_manager/foreground/css/file_manager.css
index 2e14849..fd01b7e 100644
--- a/ui/file_manager/file_manager/foreground/css/file_manager.css
+++ b/ui/file_manager/file_manager/foreground/css/file_manager.css
@@ -2251,8 +2251,13 @@
   z-index: 505; /* Must be above the scroll bar (500). */
 }
 
-/* Preventing FOUC */
-cr-input:unresolved {
+/*
+ * Preventing FOUC
+ * TODO(dpapad): Remove :unresolved when Polymer 2 migration is complete,
+ * (crbug.com/738611).
+ */
+cr-input:unresolved,
+cr-input:not(:defined) {
   display: none;
 }
 
diff --git a/ui/gfx/font_unittest.cc b/ui/gfx/font_unittest.cc
index 00945ae9..e9e8106 100644
--- a/ui/gfx/font_unittest.cc
+++ b/ui/gfx/font_unittest.cc
@@ -21,33 +21,6 @@
 
 using FontTest = testing::Test;
 
-#if defined(OS_WIN)
-class ScopedMinimumFontSizeCallback {
- public:
-  explicit ScopedMinimumFontSizeCallback(int minimum_size) {
-    minimum_size_ = minimum_size;
-    old_callback_ = PlatformFontWin::get_minimum_font_size_callback;
-    PlatformFontWin::get_minimum_font_size_callback = &GetMinimumFontSize;
-  }
-
-  ~ScopedMinimumFontSizeCallback() {
-    PlatformFontWin::get_minimum_font_size_callback = old_callback_;
-  }
-
- private:
-  static int GetMinimumFontSize() {
-    return minimum_size_;
-  }
-
-  PlatformFontWin::GetMinimumFontSizeCallback old_callback_;
-  static int minimum_size_;
-
-  DISALLOW_COPY_AND_ASSIGN(ScopedMinimumFontSizeCallback);
-};
-
-int ScopedMinimumFontSizeCallback::minimum_size_ = 0;
-#endif  // defined(OS_WIN)
-
 TEST_F(FontTest, LoadArial) {
   Font cf(kTestFontName, 16);
 #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_IOS)
@@ -151,8 +124,7 @@
 #if defined(OS_WIN)
 TEST_F(FontTest, DeriveResizesIfSizeTooSmall) {
   Font cf(kTestFontName, 8);
-  // The minimum font size is set to 5 in browser_main.cc.
-  ScopedMinimumFontSizeCallback minimum_size(5);
+  PlatformFontWin::SetGetMinimumFontSizeCallback([] { return 5; });
 
   Font derived_font = cf.Derive(-4, cf.GetStyle(), cf.GetWeight());
   EXPECT_EQ(5, derived_font.GetFontSize());
@@ -160,8 +132,7 @@
 
 TEST_F(FontTest, DeriveKeepsOriginalSizeIfHeightOk) {
   Font cf(kTestFontName, 8);
-  // The minimum font size is set to 5 in browser_main.cc.
-  ScopedMinimumFontSizeCallback minimum_size(5);
+  PlatformFontWin::SetGetMinimumFontSizeCallback([] { return 5; });
 
   Font derived_font = cf.Derive(-2, cf.GetStyle(), cf.GetWeight());
   EXPECT_EQ(6, derived_font.GetFontSize());
diff --git a/ui/gfx/platform_font_win.cc b/ui/gfx/platform_font_win.cc
index 187ffbd..c2568ae 100644
--- a/ui/gfx/platform_font_win.cc
+++ b/ui/gfx/platform_font_win.cc
@@ -13,10 +13,13 @@
 #include <wrl/client.h>
 
 #include <algorithm>
+#include <utility>
 
+#include "base/containers/flat_map.h"
 #include "base/debug/alias.h"
 #include "base/logging.h"
 #include "base/macros.h"
+#include "base/no_destructor.h"
 #include "base/strings/string16.h"
 #include "base/strings/string_util.h"
 #include "base/strings/sys_string_conversions.h"
@@ -35,11 +38,15 @@
 
 namespace {
 
+gfx::PlatformFontWin::AdjustFontCallback g_adjust_font_callback = nullptr;
+gfx::PlatformFontWin::GetMinimumFontSizeCallback
+    g_get_minimum_font_size_callback = nullptr;
+
 // Returns the minimum font size, using the minimum size callback, if set.
 int GetMinimumFontSize() {
   int min_font_size = 0;
-  if (gfx::PlatformFontWin::get_minimum_font_size_callback)
-    min_font_size = gfx::PlatformFontWin::get_minimum_font_size_callback();
+  if (g_get_minimum_font_size_callback)
+    min_font_size = g_get_minimum_font_size_callback();
   return min_font_size;
 }
 
@@ -247,6 +254,60 @@
   return hr;
 }
 
+class SystemFonts {
+ public:
+  SystemFonts() {
+    NONCLIENTMETRICS_XP metrics;
+    base::win::GetNonClientMetrics(&metrics);
+
+    AddFont(gfx::PlatformFontWin::SystemFont::kCaption, &metrics.lfCaptionFont);
+    AddFont(gfx::PlatformFontWin::SystemFont::kSmallCaption,
+            &metrics.lfSmCaptionFont);
+    AddFont(gfx::PlatformFontWin::SystemFont::kMenu, &metrics.lfMenuFont);
+    AddFont(gfx::PlatformFontWin::SystemFont::kMessage, &metrics.lfMessageFont);
+    AddFont(gfx::PlatformFontWin::SystemFont::kStatus, &metrics.lfStatusFont);
+
+    is_initialized_ = true;
+  }
+
+  const gfx::Font& GetFont(gfx::PlatformFontWin::SystemFont system_font) const {
+    auto it = system_fonts_.find(system_font);
+    DCHECK(it != system_fonts_.end())
+        << "System font #" << static_cast<int>(system_font) << " not found!";
+    DCHECK(it->second.GetNativeFont())
+        << "Font for system font #" << static_cast<int>(system_font)
+        << " has invalid handle.";
+    return it->second;
+  }
+
+  static SystemFonts* Instance() {
+    static base::NoDestructor<SystemFonts> instance;
+    return instance.get();
+  }
+
+  static bool IsInitialized() { return is_initialized_; }
+
+ private:
+  void AddFont(gfx::PlatformFontWin::SystemFont system_font, LOGFONT* logfont) {
+    if (g_adjust_font_callback)
+      g_adjust_font_callback(logfont);
+    logfont->lfHeight = AdjustFontSize(logfont->lfHeight, 0);
+    HFONT font = CreateFontIndirect(logfont);
+    DLOG_ASSERT(font);
+    system_fonts_.emplace(system_font, gfx::Font(font));
+  }
+
+  // Use a flat map for faster lookups.
+  base::flat_map<gfx::PlatformFontWin::SystemFont, gfx::Font> system_fonts_;
+
+  static bool is_initialized_;
+
+  DISALLOW_COPY_AND_ASSIGN(SystemFonts);
+};
+
+// static
+bool SystemFonts::is_initialized_ = false;
+
 }  // namespace
 
 namespace gfx {
@@ -254,12 +315,6 @@
 // static
 PlatformFontWin::HFontRef* PlatformFontWin::base_font_ref_;
 
-// static
-PlatformFontWin::AdjustFontCallback
-    PlatformFontWin::adjust_font_callback = nullptr;
-PlatformFontWin::GetMinimumFontSizeCallback
-    PlatformFontWin::get_minimum_font_size_callback = NULL;
-
 IDWriteFactory* PlatformFontWin::direct_write_factory_ = nullptr;
 
 // TODO(ananta)
@@ -303,6 +358,37 @@
   InitWithFontNameAndSize(font_name, font_size);
 }
 
+// static
+void PlatformFontWin::SetGetMinimumFontSizeCallback(
+    GetMinimumFontSizeCallback callback) {
+  DCHECK(!SystemFonts::IsInitialized());
+  g_get_minimum_font_size_callback = callback;
+}
+
+// static
+void PlatformFontWin::SetAdjustFontCallback(AdjustFontCallback callback) {
+  DCHECK(!SystemFonts::IsInitialized());
+  g_adjust_font_callback = callback;
+}
+
+// static
+void PlatformFontWin::SetDirectWriteFactory(IDWriteFactory* factory) {
+  // We grab a reference on the DirectWrite factory. This reference is
+  // leaked, which is ok because skia leaks it as well.
+  factory->AddRef();
+  direct_write_factory_ = factory;
+}
+
+// static
+bool PlatformFontWin::IsDirectWriteEnabled() {
+  return direct_write_factory_ != nullptr;
+}
+
+// static
+const Font& PlatformFontWin::GetSystemFont(SystemFont system_font) {
+  return SystemFonts::Instance()->GetFont(system_font);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // PlatformFontWin, PlatformFont implementation:
 
@@ -386,40 +472,6 @@
   return font_ref_->hfont();
 }
 
-// static
-void PlatformFontWin::SetDirectWriteFactory(IDWriteFactory* factory) {
-  // We grab a reference on the DirectWrite factory. This reference is
-  // leaked, which is ok because skia leaks it as well.
-  factory->AddRef();
-  direct_write_factory_ = factory;
-}
-
-// static
-bool PlatformFontWin::IsDirectWriteEnabled() {
-  return direct_write_factory_ != nullptr;
-}
-
-// static
-void PlatformFontWin::GetTextMetricsForFont(HDC hdc,
-                                            HFONT font,
-                                            TEXTMETRIC* text_metrics) {
-  base::win::ScopedSelectObject scoped_font(hdc, font);
-  GetTextMetrics(hdc, text_metrics);
-}
-
-// static
-int PlatformFontWin::GetFontSize(const LOGFONT& font_info) {
-  if (font_info.lfHeight < 0)
-    return -font_info.lfHeight;
-
-  base::win::ScopedGetDC screen_dc(NULL);
-  base::win::ScopedGDIObject<HFONT> font(CreateFontIndirect(&font_info));
-
-  TEXTMETRIC font_metrics = {0};
-  PlatformFontWin::GetTextMetricsForFont(screen_dc, font.get(), &font_metrics);
-  return font_metrics.tmAscent;
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 // Font, private:
 
@@ -443,20 +495,21 @@
 }
 
 // static
-PlatformFontWin::HFontRef* PlatformFontWin::GetBaseFontRef() {
-  if (base_font_ref_ == NULL) {
-    NONCLIENTMETRICS_XP metrics;
-    base::win::GetNonClientMetrics(&metrics);
+void PlatformFontWin::GetTextMetricsForFont(HDC hdc,
+                                            HFONT font,
+                                            TEXTMETRIC* text_metrics) {
+  base::win::ScopedSelectObject scoped_font(hdc, font);
+  GetTextMetrics(hdc, text_metrics);
+}
 
-    if (adjust_font_callback)
-      adjust_font_callback(&metrics.lfMessageFont);
-    metrics.lfMessageFont.lfHeight =
-        AdjustFontSize(metrics.lfMessageFont.lfHeight, 0);
-    HFONT font = CreateFontIndirect(&metrics.lfMessageFont);
-    DLOG_ASSERT(font);
-    base_font_ref_ = PlatformFontWin::CreateHFontRef(font);
-    // base_font_ref_ is global, up the ref count so it's never deleted.
-    base_font_ref_->AddRef();
+// static
+PlatformFontWin::HFontRef* PlatformFontWin::GetBaseFontRef() {
+  if (base_font_ref_ == nullptr) {
+    // We'll delegate to our SystemFonts instance to give us the default
+    // message font.
+    PlatformFontWin* message_font = static_cast<PlatformFontWin*>(
+        SystemFonts::Instance()->GetFont(SystemFont::kMessage).platform_font());
+    base_font_ref_ = message_font->font_ref_.get();
   }
   return base_font_ref_;
 }
diff --git a/ui/gfx/platform_font_win.h b/ui/gfx/platform_font_win.h
index 487d4677b..5f6ae19 100644
--- a/ui/gfx/platform_font_win.h
+++ b/ui/gfx/platform_font_win.h
@@ -5,6 +5,8 @@
 #ifndef UI_GFX_PLATFORM_FONT_WIN_H_
 #define UI_GFX_PLATFORM_FONT_WIN_H_
 
+#include <windows.h>
+
 #include <string>
 
 #include "base/compiler_specific.h"
@@ -14,8 +16,6 @@
 #include "ui/gfx/gfx_export.h"
 #include "ui/gfx/platform_font.h"
 
-#include <windows.h>
-
 struct IDWriteFactory;
 struct IDWriteFont;
 
@@ -23,6 +23,14 @@
 
 class GFX_EXPORT PlatformFontWin : public PlatformFont {
  public:
+  enum class SystemFont : int {
+    kCaption = 0,
+    kSmallCaption,
+    kMenu,
+    kStatus,
+    kMessage
+  };
+
   PlatformFontWin();
   explicit PlatformFontWin(NativeFont native_font);
   PlatformFontWin(const std::string& font_name, int font_size);
@@ -39,14 +47,15 @@
   // Callback that returns the minimum height that should be used for
   // gfx::Fonts. Optional. If not specified, the minimum font size is 0.
   typedef int (*GetMinimumFontSizeCallback)();
-  static GetMinimumFontSizeCallback get_minimum_font_size_callback;
+  static void SetGetMinimumFontSizeCallback(
+      GetMinimumFontSizeCallback callback);
 
   // Callback that adjusts a LOGFONT to meet suitability requirements of the
   // embedding application. Optional. If not specified, no adjustments are
   // performed other than clamping to a minimum font height if
   // |get_minimum_font_size_callback| is specified.
   typedef void (*AdjustFontCallback)(LOGFONT* lf);
-  static AdjustFontCallback adjust_font_callback;
+  static void SetAdjustFontCallback(AdjustFontCallback callback);
 
   // Returns the font name for the system locale. Some fonts, particularly
   // East Asian fonts, have different names per locale. If the localized font
@@ -75,13 +84,7 @@
 
   static bool IsDirectWriteEnabled();
 
-  // Returns the GDI metrics for the font passed in.
-  static void GetTextMetricsForFont(HDC hdc,
-                                    HFONT font,
-                                    TEXTMETRIC* text_metrics);
-
-  // Returns the size of the font based on the font information passed in.
-  static int GetFontSize(const LOGFONT& font_info);
+  static const Font& GetSystemFont(SystemFont system_font);
 
  private:
   FRIEND_TEST_ALL_PREFIXES(RenderTextHarfBuzzTest, HarfBuzz_UniscribeFallback);
@@ -168,6 +171,11 @@
   void InitWithFontNameAndSize(const std::string& font_name,
                                int font_size);
 
+  // Returns the GDI metrics for the font passed in.
+  static void GetTextMetricsForFont(HDC hdc,
+                                    HFONT font,
+                                    TEXTMETRIC* text_metrics);
+
   // Returns the base font ref. This should ONLY be invoked on the
   // UI thread.
   static HFontRef* GetBaseFontRef();
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
index 4794633..5135d30 100644
--- a/ui/views/BUILD.gn
+++ b/ui/views/BUILD.gn
@@ -592,6 +592,7 @@
     public += [
       "accessibility/ax_aura_obj_cache.h",
       "accessibility/ax_aura_obj_wrapper.h",
+      "accessibility/ax_root_obj_wrapper.h",
       "accessibility/ax_tree_source_views.h",
       "accessibility/ax_view_obj_wrapper.h",
       "accessibility/ax_widget_obj_wrapper.h",
@@ -623,6 +624,7 @@
     sources += [
       "accessibility/ax_aura_obj_cache.cc",
       "accessibility/ax_aura_obj_wrapper.cc",
+      "accessibility/ax_root_obj_wrapper.cc",
       "accessibility/ax_tree_source_views.cc",
       "accessibility/ax_view_obj_wrapper.cc",
       "accessibility/ax_widget_obj_wrapper.cc",
diff --git a/chrome/browser/ui/aura/accessibility/ax_root_obj_wrapper.cc b/ui/views/accessibility/ax_root_obj_wrapper.cc
similarity index 87%
rename from chrome/browser/ui/aura/accessibility/ax_root_obj_wrapper.cc
rename to ui/views/accessibility/ax_root_obj_wrapper.cc
index eb3b539a..8f16302 100644
--- a/chrome/browser/ui/aura/accessibility/ax_root_obj_wrapper.cc
+++ b/ui/views/accessibility/ax_root_obj_wrapper.cc
@@ -1,12 +1,13 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright 2018 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/aura/accessibility/ax_root_obj_wrapper.h"
+#include "ui/views/accessibility/ax_root_obj_wrapper.h"
+
+#include <utility>
 
 #include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
-#include "chrome/browser/ui/aura/accessibility/automation_manager_aura.h"
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/aura/env.h"
@@ -16,16 +17,20 @@
 #include "ui/views/accessibility/ax_aura_obj_cache.h"
 #include "ui/views/accessibility/ax_window_obj_wrapper.h"
 
-AXRootObjWrapper::AXRootObjWrapper()
-    : alert_window_(std::make_unique<aura::Window>(nullptr)) {
+AXRootObjWrapper::AXRootObjWrapper(views::AXAuraObjCache::Delegate* delegate)
+    : alert_window_(std::make_unique<aura::Window>(nullptr)),
+      delegate_(delegate) {
   alert_window_->Init(ui::LAYER_NOT_DRAWN);
+#if !defined(IS_CHROMECAST)
   aura::Env::GetInstance()->AddObserver(this);
 
   if (display::Screen::GetScreen())
     display::Screen::GetScreen()->AddObserver(this);
+#endif
 }
 
 AXRootObjWrapper::~AXRootObjWrapper() {
+#if !defined(IS_CHROMECAST)
   if (display::Screen::GetScreen())
     display::Screen::GetScreen()->RemoveObserver(this);
 
@@ -35,6 +40,7 @@
     return;
 
   aura::Env::GetInstance()->RemoveObserver(this);
+#endif
   alert_window_.reset();
 }
 
@@ -73,6 +79,8 @@
 void AXRootObjWrapper::Serialize(ui::AXNodeData* out_node_data) {
   out_node_data->id = unique_id_.Get();
   out_node_data->role = ax::mojom::Role::kDesktop;
+
+#if !defined(IS_CHROMECAST)
   display::Screen* screen = display::Screen::GetScreen();
   if (!screen)
     return;
@@ -87,6 +95,7 @@
     out_node_data->AddState(ax::mojom::State::kHorizontal);
   else
     out_node_data->AddState(ax::mojom::State::kVertical);
+#endif
 }
 
 const ui::AXUniqueId& AXRootObjWrapper::GetUniqueId() const {
@@ -95,8 +104,7 @@
 
 void AXRootObjWrapper::OnDisplayMetricsChanged(const display::Display& display,
                                                uint32_t changed_metrics) {
-  AutomationManagerAura::GetInstance()->OnEvent(
-      this, ax::mojom::Event::kLocationChanged);
+  delegate_->OnEvent(this, ax::mojom::Event::kLocationChanged);
 }
 
 void AXRootObjWrapper::OnWindowInitialized(aura::Window* window) {}
diff --git a/chrome/browser/ui/aura/accessibility/ax_root_obj_wrapper.h b/ui/views/accessibility/ax_root_obj_wrapper.h
similarity index 69%
rename from chrome/browser/ui/aura/accessibility/ax_root_obj_wrapper.h
rename to ui/views/accessibility/ax_root_obj_wrapper.h
index a9bae656..f90f9853 100644
--- a/chrome/browser/ui/aura/accessibility/ax_root_obj_wrapper.h
+++ b/ui/views/accessibility/ax_root_obj_wrapper.h
@@ -1,32 +1,28 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright 2018 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_AURA_ACCESSIBILITY_AX_ROOT_OBJ_WRAPPER_H_
-#define CHROME_BROWSER_UI_AURA_ACCESSIBILITY_AX_ROOT_OBJ_WRAPPER_H_
+#ifndef UI_VIEWS_ACCESSIBILITY_AX_ROOT_OBJ_WRAPPER_H_
+#define UI_VIEWS_ACCESSIBILITY_AX_ROOT_OBJ_WRAPPER_H_
 
 #include <stdint.h>
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "base/macros.h"
 #include "ui/accessibility/platform/ax_unique_id.h"
 #include "ui/aura/env_observer.h"
 #include "ui/display/display_observer.h"
+#include "ui/views/accessibility/ax_aura_obj_cache.h"
 #include "ui/views/accessibility/ax_aura_obj_wrapper.h"
 
-namespace aura {
-class Window;
-}  // namespace aura
-
-// Provides the root AX desktop node for the chrome.automation.getDesktop() API
-// call. Each top-level desktop window is a child.
-class AXRootObjWrapper : public views::AXAuraObjWrapper,
-                         display::DisplayObserver,
-                         aura::EnvObserver {
+class VIEWS_EXPORT AXRootObjWrapper : public views::AXAuraObjWrapper,
+                                      display::DisplayObserver,
+                                      aura::EnvObserver {
  public:
-  AXRootObjWrapper();
+  explicit AXRootObjWrapper(views::AXAuraObjCache::Delegate* delegate);
   ~AXRootObjWrapper() override;
 
   // Returns an AXAuraObjWrapper for an alert window with title set to |text|.
@@ -56,7 +52,9 @@
 
   std::unique_ptr<aura::Window> alert_window_;
 
+  views::AXAuraObjCache::Delegate* delegate_;
+
   DISALLOW_COPY_AND_ASSIGN(AXRootObjWrapper);
 };
 
-#endif  // CHROME_BROWSER_UI_AURA_ACCESSIBILITY_AX_ROOT_OBJ_WRAPPER_H_
+#endif  // UI_VIEWS_ACCESSIBILITY_AX_ROOT_OBJ_WRAPPER_H_
diff --git a/ui/views/cocoa/bridged_native_widget_host_impl.h b/ui/views/cocoa/bridged_native_widget_host_impl.h
index 8f9dcdf..ad5ae771 100644
--- a/ui/views/cocoa/bridged_native_widget_host_impl.h
+++ b/ui/views/cocoa/bridged_native_widget_host_impl.h
@@ -281,7 +281,9 @@
   void OnDidChangeFocus(View* focused_before, View* focused_now) override;
 
   // ui::internal::InputMethodDelegate:
-  ui::EventDispatchDetails DispatchKeyEventPostIME(ui::KeyEvent* key) override;
+  ui::EventDispatchDetails DispatchKeyEventPostIME(
+      ui::KeyEvent* key,
+      base::OnceCallback<void(bool)> ack_callback) override;
 
   // ui::LayerDelegate:
   void OnPaintLayer(const ui::PaintContext& context) override;
diff --git a/ui/views/cocoa/bridged_native_widget_host_impl.mm b/ui/views/cocoa/bridged_native_widget_host_impl.mm
index 637db633..ebeed25 100644
--- a/ui/views/cocoa/bridged_native_widget_host_impl.mm
+++ b/ui/views/cocoa/bridged_native_widget_host_impl.mm
@@ -952,12 +952,14 @@
 // BridgedNativeWidgetImpl, internal::InputMethodDelegate:
 
 ui::EventDispatchDetails BridgedNativeWidgetHostImpl::DispatchKeyEventPostIME(
-    ui::KeyEvent* key) {
+    ui::KeyEvent* key,
+    base::OnceCallback<void(bool)> ack_callback) {
   DCHECK(focus_manager_);
   if (!focus_manager_->OnKeyEvent(*key))
     key->StopPropagation();
   else
     native_widget_mac_->GetWidget()->OnKeyEvent(key);
+  CallDispatchKeyEventPostIMEAck(key, std::move(ack_callback));
   return ui::EventDispatchDetails();
 }
 
diff --git a/ui/views/controls/menu/menu_config_win.cc b/ui/views/controls/menu/menu_config_win.cc
index 15f168d..7266551 100644
--- a/ui/views/controls/menu/menu_config_win.cc
+++ b/ui/views/controls/menu/menu_config_win.cc
@@ -10,10 +10,8 @@
 
 #include "base/logging.h"
 #include "base/win/scoped_gdi_object.h"
-#include "base/win/win_client_metrics.h"
-#include "ui/base/l10n/l10n_util_win.h"
-#include "ui/display/win/dpi.h"
 #include "ui/gfx/color_utils.h"
+#include "ui/gfx/platform_font_win.h"
 #include "ui/native_theme/native_theme_win.h"
 
 using ui::NativeTheme;
@@ -22,16 +20,9 @@
 
 void MenuConfig::Init() {
   arrow_color = color_utils::GetSysSkColor(COLOR_MENUTEXT);
+  font_list = gfx::FontList(gfx::PlatformFontWin::GetSystemFont(
+      gfx::PlatformFontWin::SystemFont::kMenu));
 
-  NONCLIENTMETRICS_XP metrics;
-  base::win::GetNonClientMetrics(&metrics);
-  display::win::AdjustFontForAccessibility(&metrics.lfMenuFont);
-  l10n_util::AdjustUIFont(&(metrics.lfMenuFont));
-  {
-    base::win::ScopedHFONT new_font(CreateFontIndirect(&metrics.lfMenuFont));
-    DLOG_ASSERT(new_font.is_valid());
-    font_list = gfx::FontList(gfx::Font(new_font.get()));
-  }
   NativeTheme::ExtraParams extra;
   gfx::Size arrow_size = NativeTheme::GetInstanceForNativeUi()->GetPartSize(
       NativeTheme::kMenuPopupArrow, NativeTheme::kNormal, extra);
diff --git a/ui/views/controls/tabbed_pane/tabbed_pane.cc b/ui/views/controls/tabbed_pane/tabbed_pane.cc
index 04e572c2..96309bc 100644
--- a/ui/views/controls/tabbed_pane/tabbed_pane.cc
+++ b/ui/views/controls/tabbed_pane/tabbed_pane.cc
@@ -407,7 +407,7 @@
     layout->set_cross_axis_alignment(BoxLayout::CROSS_AXIS_ALIGNMENT_END);
   } else {
     const int kTabStripEdgePadding = 8;
-    const int kTabSpacing = 16;
+    const int kTabSpacing = 8;
     layout = std::make_unique<BoxLayout>(
         BoxLayout::kVertical, gfx::Insets(kTabStripEdgePadding, 0, 0, 0),
         kTabSpacing);
diff --git a/ui/views/controls/textfield/textfield_unittest.cc b/ui/views/controls/textfield/textfield_unittest.cc
index f8a8292..685c5ae 100644
--- a/ui/views/controls/textfield/textfield_unittest.cc
+++ b/ui/views/controls/textfield/textfield_unittest.cc
@@ -11,6 +11,7 @@
 #include <string>
 #include <vector>
 
+#include "base/bind_helpers.h"
 #include "base/command_line.h"
 #include "base/format_macros.h"
 #include "base/i18n/rtl.h"
@@ -157,7 +158,7 @@
 // which trigger the appropriate NSResponder action messages for composition.
 #if defined(OS_MACOSX)
   if (key->is_char())
-    return DispatchKeyEventPostIME(key);
+    return DispatchKeyEventPostIME(key, base::NullCallback());
 #endif
 
   // Checks whether the key event is from EventGenerator on Windows which will
@@ -177,9 +178,9 @@
     ui::KeyEvent mock_key(ui::ET_KEY_PRESSED,
                           ui::VKEY_PROCESSKEY,
                           key->flags());
-    dispatch_details = DispatchKeyEventPostIME(&mock_key);
+    dispatch_details = DispatchKeyEventPostIME(&mock_key, base::NullCallback());
   } else {
-    dispatch_details = DispatchKeyEventPostIME(key);
+    dispatch_details = DispatchKeyEventPostIME(key, base::NullCallback());
   }
 
   if (key->handled() || dispatch_details.dispatcher_destroyed)
diff --git a/ui/views/mus/ax_remote_host.cc b/ui/views/mus/ax_remote_host.cc
index 6fa87f1..ff18dd8 100644
--- a/ui/views/mus/ax_remote_host.cc
+++ b/ui/views/mus/ax_remote_host.cc
@@ -28,11 +28,6 @@
 
 namespace views {
 
-const ui::AXTreeID& RemoteAXTreeID() {
-  static const base::NoDestructor<ui::AXTreeID> remote_ax_tree_id("-2");
-  return *remote_ax_tree_id;
-}
-
 AXRemoteHost::AXRemoteHost() {
   AXAuraObjCache::GetInstance()->SetDelegate(this);
 }
@@ -74,7 +69,7 @@
   View* contents_view = widget_->widget_delegate()->GetContentsView();
   AXAuraObjWrapper* contents_wrapper = cache->GetOrCreate(contents_view);
 
-  tree_source_ = std::make_unique<AXTreeSourceMus>(contents_wrapper);
+  tree_source_ = std::make_unique<AXTreeSourceMus>(contents_wrapper, tree_id_);
   tree_serializer_ = std::make_unique<AuraAXTreeSerializer>(tree_source_.get());
 
   // Inform the serializer of the display device scale factor.
@@ -180,7 +175,17 @@
 void AXRemoteHost::BindAndSetRemote() {
   ax::mojom::AXRemoteHostPtr remote;
   binding_.Bind(mojo::MakeRequest(&remote));
-  ax_host_ptr_->SetRemoteHost(std::move(remote));
+  ax_host_ptr_->SetRemoteHost(
+      std::move(remote), base::BindOnce(&AXRemoteHost::SetRemoteHostCallback,
+                                        base::Unretained(this)));
+}
+
+void AXRemoteHost::SetRemoteHostCallback(const ui::AXTreeID& tree_id,
+                                         bool enabled) {
+  tree_id_ = tree_id;
+
+  // Set the initial enabled state and send the AX tree if necessary.
+  OnAutomationEnabled(enabled);
 }
 
 void AXRemoteHost::Enable() {
@@ -243,7 +248,7 @@
   event.event_type = event_type;
   // Other fields are not used.
 
-  ax_host_ptr_->HandleAccessibilityEvent(RemoteAXTreeID(), updates, event);
+  ax_host_ptr_->HandleAccessibilityEvent(tree_id_, updates, event);
 }
 
 void AXRemoteHost::PerformHitTest(const ui::AXActionData& action) {
diff --git a/ui/views/mus/ax_remote_host.h b/ui/views/mus/ax_remote_host.h
index 8170e60..65b72c01 100644
--- a/ui/views/mus/ax_remote_host.h
+++ b/ui/views/mus/ax_remote_host.h
@@ -35,10 +35,6 @@
 class View;
 class Widget;
 
-// Well-known tree ID for the remote client.
-// TODO(jamescook): Support different IDs for different clients.
-VIEWS_MUS_EXPORT extern const ui::AXTreeID& RemoteAXTreeID();
-
 // Manages a tree of automation nodes for a mojo app outside the browser process
 // (e.g. the keyboard shortcut viewer app).
 class VIEWS_MUS_EXPORT AXRemoteHost : public ax::mojom::AXRemoteHost,
@@ -87,6 +83,9 @@
   // Registers this object as a remote host for the parent AXHost.
   void BindAndSetRemote();
 
+  // Callback for initial state from AXHost.
+  void SetRemoteHostCallback(const ui::AXTreeID& tree_id, bool enabled);
+
   void Enable();
   void Disable();
 
@@ -103,6 +102,9 @@
 
   mojo::Binding<ax::mojom::AXRemoteHost> binding_{this};
 
+  // ID to use for the AX tree.
+  ui::AXTreeID tree_id_;
+
   // Whether accessibility automation support is enabled.
   bool enabled_ = false;
 
diff --git a/ui/views/mus/ax_remote_host_unittest.cc b/ui/views/mus/ax_remote_host_unittest.cc
index 359d057..dbdde9d 100644
--- a/ui/views/mus/ax_remote_host_unittest.cc
+++ b/ui/views/mus/ax_remote_host_unittest.cc
@@ -43,9 +43,11 @@
   }
 
   // ax::mojom::AXHost:
-  void SetRemoteHost(ax::mojom::AXRemoteHostPtr client) override {
+  void SetRemoteHost(ax::mojom::AXRemoteHostPtr client,
+                     SetRemoteHostCallback cb) override {
     ++add_client_count_;
-    client->OnAutomationEnabled(automation_enabled_);
+    const ui::AXTreeID tree_id("123");
+    std::move(cb).Run(tree_id, automation_enabled_);
     client.FlushForTesting();
   }
   void HandleAccessibilityEvent(const std::string& tree_id,
diff --git a/ui/views/mus/ax_tree_source_mus.cc b/ui/views/mus/ax_tree_source_mus.cc
index 7de7691..c84c1801 100644
--- a/ui/views/mus/ax_tree_source_mus.cc
+++ b/ui/views/mus/ax_tree_source_mus.cc
@@ -11,14 +11,17 @@
 
 namespace views {
 
-AXTreeSourceMus::AXTreeSourceMus(AXAuraObjWrapper* root) : root_(root) {
+AXTreeSourceMus::AXTreeSourceMus(AXAuraObjWrapper* root,
+                                 const ui::AXTreeID& tree_id)
+    : root_(root), tree_id_(tree_id) {
   DCHECK(root_);
+  DCHECK(!tree_id_.empty());
 }
 
 AXTreeSourceMus::~AXTreeSourceMus() = default;
 
 bool AXTreeSourceMus::GetTreeData(ui::AXTreeData* tree_data) const {
-  tree_data->tree_id = RemoteAXTreeID();
+  tree_data->tree_id = tree_id_;
   return AXTreeSourceViews::GetTreeData(tree_data);
 }
 
diff --git a/ui/views/mus/ax_tree_source_mus.h b/ui/views/mus/ax_tree_source_mus.h
index 6e3681e0..6c91707 100644
--- a/ui/views/mus/ax_tree_source_mus.h
+++ b/ui/views/mus/ax_tree_source_mus.h
@@ -6,6 +6,7 @@
 #define UI_VIEWS_MUS_AX_TREE_SOURCE_MUS_H_
 
 #include "base/macros.h"
+#include "ui/accessibility/ax_tree_id.h"
 #include "ui/views/accessibility/ax_tree_source_views.h"
 #include "ui/views/mus/mus_export.h"
 
@@ -20,7 +21,7 @@
 class VIEWS_MUS_EXPORT AXTreeSourceMus : public AXTreeSourceViews {
  public:
   // |root| must outlive this object.
-  explicit AXTreeSourceMus(AXAuraObjWrapper* root);
+  AXTreeSourceMus(AXAuraObjWrapper* root, const ui::AXTreeID& tree_id);
   ~AXTreeSourceMus() override;
 
   void set_device_scale_factor(float scale) { device_scale_factor_ = scale; }
@@ -35,6 +36,9 @@
   // The top-level object to use for the AX tree.
   AXAuraObjWrapper* root_;
 
+  // ID to use for the AX tree.
+  const ui::AXTreeID tree_id_;
+
   // The display device scale factor to use while serializing this update.
   float device_scale_factor_ = 1.f;
 
diff --git a/ui/views/mus/ax_tree_source_mus_unittest.cc b/ui/views/mus/ax_tree_source_mus_unittest.cc
index ab052a169..9fbf307 100644
--- a/ui/views/mus/ax_tree_source_mus_unittest.cc
+++ b/ui/views/mus/ax_tree_source_mus_unittest.cc
@@ -51,6 +51,7 @@
 
   std::unique_ptr<Widget> widget_;
   Label* label_ = nullptr;  // Owned by views hierarchy.
+  const ui::AXTreeID ax_tree_id_{"123"};
 
  private:
   DISALLOW_COPY_AND_ASSIGN(AXTreeSourceMusTest);
@@ -59,17 +60,17 @@
 TEST_F(AXTreeSourceMusTest, GetTreeData) {
   AXAuraObjWrapper* root =
       AXAuraObjCache::GetInstance()->GetOrCreate(widget_->GetContentsView());
-  AXTreeSourceMus tree(root);
+  AXTreeSourceMus tree(root, ax_tree_id_);
   ui::AXTreeData tree_data;
   tree.GetTreeData(&tree_data);
-  EXPECT_EQ(RemoteAXTreeID(), tree_data.tree_id);
+  EXPECT_EQ(ax_tree_id_, tree_data.tree_id);
 }
 
 TEST_F(AXTreeSourceMusTest, Serialize) {
   AXAuraObjCache* cache = AXAuraObjCache::GetInstance();
   AXAuraObjWrapper* root = cache->GetOrCreate(widget_->GetContentsView());
 
-  AXTreeSourceMus tree(root);
+  AXTreeSourceMus tree(root, ax_tree_id_);
   EXPECT_EQ(root, tree.GetRoot());
 
   // Serialize the root.
@@ -93,7 +94,7 @@
   AXAuraObjWrapper* root = cache->GetOrCreate(widget_->GetContentsView());
 
   // Simulate serializing a widget on a high-dpi display.
-  AXTreeSourceMus tree(root);
+  AXTreeSourceMus tree(root, ax_tree_id_);
   tree.set_device_scale_factor(2.f);
 
   // Serialize the root.
diff --git a/ui/views/mus/views_mus_test_suite.cc b/ui/views/mus/views_mus_test_suite.cc
index 40dafe4..1beddbf9 100644
--- a/ui/views/mus/views_mus_test_suite.cc
+++ b/ui/views/mus/views_mus_test_suite.cc
@@ -14,7 +14,6 @@
 #include "base/run_loop.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/threading/simple_thread.h"
-#include "base/threading/thread.h"
 #include "mojo/core/embedder/embedder.h"
 #include "mojo/core/embedder/scoped_ipc_support.h"
 #include "services/catalog/catalog.h"
@@ -68,24 +67,18 @@
 class ServiceManagerConnection {
  public:
   ServiceManagerConnection()
-      : thread_("Persistent service_manager connections"),
-        ipc_thread_("IPC thread") {
+      : thread_("Persistent service_manager connections") {
     catalog::Catalog::LoadDefaultCatalogManifest(
         base::FilePath(kCatalogFilename));
-    mojo::core::Init();
-    ipc_thread_.StartWithOptions(
-        base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
-    ipc_support_ = std::make_unique<mojo::core::ScopedIPCSupport>(
-        ipc_thread_.task_runner(),
-        mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN);
-
     base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::AUTOMATIC,
                              base::WaitableEvent::InitialState::NOT_SIGNALED);
     base::Thread::Options options;
     thread_.StartWithOptions(options);
     thread_.task_runner()->PostTask(
-        FROM_HERE, base::BindOnce(&ServiceManagerConnection::SetUpConnections,
-                                  base::Unretained(this), &wait));
+        FROM_HERE,
+        base::BindOnce(
+            &ServiceManagerConnection::SetUpConnectionsOnBackgroundThread,
+            base::Unretained(this), &wait));
     wait.Wait();
   }
 
@@ -94,8 +87,9 @@
                              base::WaitableEvent::InitialState::NOT_SIGNALED);
     thread_.task_runner()->PostTask(
         FROM_HERE,
-        base::BindOnce(&ServiceManagerConnection::TearDownConnections,
-                       base::Unretained(this), &wait));
+        base::BindOnce(
+            &ServiceManagerConnection::TearDownConnectionsOnBackgroundThread,
+            base::Unretained(this), &wait));
     wait.Wait();
   }
 
@@ -124,7 +118,7 @@
     wait->Signal();
   }
 
-  void SetUpConnections(base::WaitableEvent* wait) {
+  void SetUpConnectionsOnBackgroundThread(base::WaitableEvent* wait) {
     background_service_manager_ =
         std::make_unique<service_manager::BackgroundServiceManager>(nullptr,
                                                                     nullptr);
@@ -140,8 +134,9 @@
     wait->Signal();
   }
 
-  void TearDownConnections(base::WaitableEvent* wait) {
+  void TearDownConnectionsOnBackgroundThread(base::WaitableEvent* wait) {
     context_.reset();
+    background_service_manager_.reset();
     wait->Signal();
   }
 
@@ -155,8 +150,6 @@
   }
 
   base::Thread thread_;
-  base::Thread ipc_thread_;
-  std::unique_ptr<mojo::core::ScopedIPCSupport> ipc_support_;
   std::unique_ptr<service_manager::BackgroundServiceManager>
       background_service_manager_;
   std::unique_ptr<service_manager::ServiceContext> context_;
@@ -226,7 +219,7 @@
 }  // namespace
 
 ViewsMusTestSuite::ViewsMusTestSuite(int argc, char** argv)
-    : ViewsTestSuite(argc, argv) {}
+    : ViewsTestSuite(argc, argv), ipc_thread_("IPC thread") {}
 
 ViewsMusTestSuite::~ViewsMusTestSuite() {}
 
@@ -248,6 +241,13 @@
       switches::kEnableFeatures, features::kMash.name);
 
   PlatformTestHelper::set_factory(base::Bind(&CreatePlatformTestHelper));
+
+  mojo::core::Init();
+  ipc_thread_.StartWithOptions(
+      base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
+  ipc_support_ = std::make_unique<mojo::core::ScopedIPCSupport>(
+      ipc_thread_.task_runner(),
+      mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN);
 }
 
 void ViewsMusTestSuite::InitializeEnv() {
diff --git a/ui/views/mus/views_mus_test_suite.h b/ui/views/mus/views_mus_test_suite.h
index 4dc9d09..8490ab9 100644
--- a/ui/views/mus/views_mus_test_suite.h
+++ b/ui/views/mus/views_mus_test_suite.h
@@ -9,8 +9,15 @@
 
 #include "base/macros.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/threading/thread.h"
 #include "ui/views/views_test_suite.h"
 
+namespace mojo {
+namespace core {
+class ScopedIPCSupport;
+}
+}  // namespace mojo
+
 namespace views {
 
 class ViewsMusTestSuite : public ViewsTestSuite {
@@ -24,6 +31,9 @@
   void InitializeEnv() override;
   void DestroyEnv() override;
 
+  base::Thread ipc_thread_;
+  std::unique_ptr<mojo::core::ScopedIPCSupport> ipc_support_;
+
   base::test::ScopedFeatureList feature_list_;
 
   std::unique_ptr<aura::Env> env_;
diff --git a/ui/views/widget/native_widget_aura.cc b/ui/views/widget/native_widget_aura.cc
index 85787ad..dc9c3c6 100644
--- a/ui/views/widget/native_widget_aura.cc
+++ b/ui/views/widget/native_widget_aura.cc
@@ -4,6 +4,9 @@
 
 #include "ui/views/widget/native_widget_aura.h"
 
+#include <memory>
+#include <utility>
+
 #include "base/bind.h"
 #include "base/location.h"
 #include "base/single_thread_task_runner.h"
@@ -57,9 +60,7 @@
 
 #if defined(OS_WIN)
 #include "base/win/scoped_gdi_object.h"
-#include "base/win/win_client_metrics.h"
-#include "ui/base/l10n/l10n_util_win.h"
-#include "ui/display/win/dpi.h"
+#include "ui/gfx/platform_font_win.h"
 #include "ui/views/widget/desktop_aura/desktop_window_tree_host_win.h"
 #endif
 
@@ -1227,12 +1228,8 @@
 // static
 gfx::FontList NativeWidgetPrivate::GetWindowTitleFontList() {
 #if defined(OS_WIN)
-  NONCLIENTMETRICS_XP ncm;
-  base::win::GetNonClientMetrics(&ncm);
-  display::win::AdjustFontForAccessibility(&(ncm.lfCaptionFont));
-  l10n_util::AdjustUIFont(&(ncm.lfCaptionFont));
-  base::win::ScopedHFONT caption_font(CreateFontIndirect(&(ncm.lfCaptionFont)));
-  return gfx::FontList(gfx::Font(caption_font.get()));
+  return gfx::FontList(gfx::PlatformFontWin::GetSystemFont(
+      gfx::PlatformFontWin::SystemFont::kCaption));
 #else
   return gfx::FontList();
 #endif
diff --git a/ui/webui/resources/cr_components/chromeos/multidevice_setup/mojo_api.html b/ui/webui/resources/cr_components/chromeos/multidevice_setup/mojo_api.html
index 0b76207..2bcb1bbc 100644
--- a/ui/webui/resources/cr_components/chromeos/multidevice_setup/mojo_api.html
+++ b/ui/webui/resources/cr_components/chromeos/multidevice_setup/mojo_api.html
@@ -1,9 +1,7 @@
 <link rel="import" href="chrome://resources/html/cr.html">
 <script src="chrome://resources/js/mojo_bindings.js"></script>
-</script>
 <script src="chrome://resources/js/time.mojom.js"></script>
-<script src="chrome://resources/js/chromeos/device_sync.mojom.js">
-</script>
+<script src="chrome://resources/js/chromeos/device_sync.mojom.js"></script>
 <script src="chrome://resources/js/chromeos/multidevice_setup.mojom.js">
 </script>
 <script src="chrome://resources/js/chromeos/multidevice_setup_constants.mojom.js">
diff --git a/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.js b/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.js
index 255876ac..864898e 100644
--- a/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.js
+++ b/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.js
@@ -69,7 +69,6 @@
 
   listeners: {
     'pointerdown': 'onPointerdown_',
-    'keydown': 'onKeydown_',
   },
 
   /** @private {?IntersectionObserver} */
@@ -109,11 +108,6 @@
 
     // In some cases dialog already has the 'open' attribute by this point.
     mutationObserverCallback();
-
-    // Sometimes <body> is key event's target and in that case the event
-    // will bypass cr-dialog. We should consume those events too in order to
-    // behave modally. This prevents accidentally triggering command manager.
-    document.body.addEventListener('keydown', this.onKeydown_.bind(this));
   },
 
   /** @override */
@@ -271,21 +265,6 @@
     }
   },
 
-  /**
-   * @param {!Event} e
-   * @private
-   */
-  onKeydown_: function(e) {
-    if (!this.getNative().open)
-      return;
-
-    if (this.ignoreEnterKey && e.key == 'Enter')
-      return;
-
-    // Stop propagation to behave modally.
-    e.stopPropagation();
-  },
-
   /** @param {!PointerEvent} e */
   onPointerdown_: function(e) {
     // Only show pulse animation if user left-clicked outside of the dialog
diff --git a/url/BUILD.gn b/url/BUILD.gn
index 1365fce..605ee82c 100644
--- a/url/BUILD.gn
+++ b/url/BUILD.gn
@@ -66,6 +66,7 @@
   deps = [
     "//base",
     "//base/third_party/dynamic_annotations",
+    "//ipc:param_traits",
   ]
 
   if (is_win) {
diff --git a/url/mojom/BUILD.gn b/url/mojom/BUILD.gn
index 1f77a2f..55796a9 100644
--- a/url/mojom/BUILD.gn
+++ b/url/mojom/BUILD.gn
@@ -17,6 +17,7 @@
 
   public_deps = [
     ":url_mojom_gurl",
+    "//mojo/public/mojom/base",
   ]
 
   check_includes_blink = false
diff --git a/url/mojom/origin.mojom b/url/mojom/origin.mojom
index 884357ba..5a9e319 100644
--- a/url/mojom/origin.mojom
+++ b/url/mojom/origin.mojom
@@ -4,9 +4,15 @@
 
 module url.mojom;
 
+import "mojo/public/mojom/base/unguessable_token.mojom";
+
 struct Origin {
   string scheme;
   string host;
   uint16 port;
-  bool unique;
+
+  // When a nonce is provided, this origin is opaque. The scheme/host/port do
+  // not need to be valid, but if they are, they identify the tuple origin
+  // from which this opaque origin is derived.
+  mojo_base.mojom.UnguessableToken? nonce_if_opaque;
 };
diff --git a/url/mojom/origin_mojom_traits.h b/url/mojom/origin_mojom_traits.h
index e4482376..ac34fe9 100644
--- a/url/mojom/origin_mojom_traits.h
+++ b/url/mojom/origin_mojom_traits.h
@@ -6,6 +6,8 @@
 #define URL_MOJO_ORIGIN_MOJOM_TRAITS_H_
 
 #include "base/strings/string_piece.h"
+#include "base/unguessable_token.h"
+#include "mojo/public/cpp/base/unguessable_token_mojom_traits.h"
 #include "url/mojom/origin.mojom.h"
 #include "url/origin.h"
 
@@ -13,27 +15,38 @@
 
 template <>
 struct StructTraits<url::mojom::OriginDataView, url::Origin> {
-  static const std::string& scheme(const url::Origin& r) { return r.scheme(); }
-  static const std::string& host(const url::Origin& r) { return r.host(); }
-  static uint16_t port(const url::Origin& r) { return r.port(); }
-  static bool unique(const url::Origin& r) { return r.unique(); }
+  static const std::string& scheme(const url::Origin& r) {
+    return r.GetTupleOrPrecursorTupleIfOpaque().scheme();
+  }
+  static const std::string& host(const url::Origin& r) {
+    return r.GetTupleOrPrecursorTupleIfOpaque().host();
+  }
+  static uint16_t port(const url::Origin& r) {
+    return r.GetTupleOrPrecursorTupleIfOpaque().port();
+  }
+  static const base::Optional<base::UnguessableToken> nonce_if_opaque(
+      const url::Origin& r) {
+    // TODO(nasko): Consider returning a const reference here.
+    return r.GetNonceForSerialization();
+  }
   static bool Read(url::mojom::OriginDataView data, url::Origin* out) {
-    if (data.unique()) {
-      *out = url::Origin();
-    } else {
-      base::StringPiece scheme, host;
-      if (!data.ReadScheme(&scheme) || !data.ReadHost(&host))
-        return false;
+    base::StringPiece scheme, host;
+    base::Optional<base::UnguessableToken> nonce_if_opaque;
+    if (!data.ReadScheme(&scheme) || !data.ReadHost(&host) ||
+        !data.ReadNonceIfOpaque(&nonce_if_opaque))
+      return false;
 
-      base::Optional<url::Origin> origin =
-          url::Origin::UnsafelyCreateTupleOriginWithoutNormalization(
-              scheme, host, data.port());
-      if (!origin.has_value())
-        return false;
+    base::Optional<url::Origin> creation_result =
+        nonce_if_opaque
+            ? url::Origin::UnsafelyCreateOpaqueOriginWithoutNormalization(
+                  scheme, host, data.port(),
+                  url::Origin::Nonce(*nonce_if_opaque))
+            : url::Origin::UnsafelyCreateTupleOriginWithoutNormalization(
+                  scheme, host, data.port());
+    if (!creation_result)
+      return false;
 
-      *out = origin.value();
-    }
-
+    *out = std::move(creation_result.value());
     return true;
   }
 };
diff --git a/url/mojom/url_gurl_mojom_traits_unittest.cc b/url/mojom/url_gurl_mojom_traits_unittest.cc
index 69cbbcc..cdacd51 100644
--- a/url/mojom/url_gurl_mojom_traits_unittest.cc
+++ b/url/mojom/url_gurl_mojom_traits_unittest.cc
@@ -80,14 +80,26 @@
   EXPECT_EQ(non_unique, output);
   EXPECT_FALSE(output.unique());
 
-  Origin unique;
-  EXPECT_TRUE(proxy->BounceOrigin(unique, &output));
+  Origin unique1;
+  Origin unique2 = non_unique.DeriveNewOpaqueOrigin();
+  EXPECT_NE(unique1, unique2);
+  EXPECT_NE(unique2, unique1);
+  EXPECT_NE(unique2, non_unique);
+  EXPECT_TRUE(proxy->BounceOrigin(unique1, &output));
   EXPECT_TRUE(output.unique());
+  EXPECT_EQ(unique1, output);
+  Origin output2;
+  EXPECT_TRUE(proxy->BounceOrigin(unique2, &output2));
+  EXPECT_EQ(unique2, output2);
+  EXPECT_NE(unique2, output);
+  EXPECT_NE(unique1, output2);
 
   Origin normalized =
       Origin::CreateFromNormalizedTuple("http", "www.google.com", 80);
+  EXPECT_EQ(normalized, non_unique);
   EXPECT_TRUE(proxy->BounceOrigin(normalized, &output));
   EXPECT_EQ(normalized, output);
+  EXPECT_EQ(non_unique, output);
   EXPECT_FALSE(output.unique());
 }
 
diff --git a/url/origin.h b/url/origin.h
index 5306803..4e1d349 100644
--- a/url/origin.h
+++ b/url/origin.h
@@ -15,6 +15,7 @@
 #include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 #include "base/unguessable_token.h"
+#include "ipc/ipc_param_traits.h"
 #include "url/scheme_host_port.h"
 #include "url/third_party/mozilla/url_parse.h"
 #include "url/url_canon.h"
@@ -28,8 +29,17 @@
 struct FuzzTraits;
 }  // namespace ipc_fuzzer
 
+namespace mojo {
+template <typename DataViewType, typename T>
+struct StructTraits;
+}  // namespace mojo
+
 namespace url {
 
+namespace mojom {
+class OriginDataView;
+}  // namespace mojom
+
 // Per https://html.spec.whatwg.org/multipage/origin.html#origin, an origin is
 // either:
 // - a tuple origin of (scheme, host, port) as described in RFC 6454.
@@ -245,7 +255,9 @@
 
  private:
   friend class OriginTest;
+  friend IPC::ParamTraits<url::Origin>;
   friend struct ipc_fuzzer::FuzzTraits<Origin>;
+  friend struct mojo::StructTraits<url::mojom::OriginDataView, url::Origin>;
   friend URL_EXPORT std::ostream& operator<<(std::ostream& out,
                                              const Origin& origin);