diff --git a/AUTHORS b/AUTHORS
index 4452ee1..766abd5 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -191,6 +191,7 @@
 Chris Vasselli <clindsay@gmail.com>
 Christophe Dumez <ch.dumez@samsung.com>
 Christopher Dale <chrelad@gmail.com>
+Chunbo Hua <chunbo.hua@intel.com>
 Claudio DeSouza <claudiomdsjr@gmail.com>
 Clemens Fruhwirth <clemens@endorphin.org>
 Clement Scheelfeldt Skau <clementskau@gmail.com>
diff --git a/DEPS b/DEPS
index 86d2279..a7e31d4 100644
--- a/DEPS
+++ b/DEPS
@@ -175,7 +175,7 @@
   # 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': '8ce842d38d0b32149e874d6855c91e8c68ba65a7',
+  'skia_revision': 'c89ca0ba09ed6e13da7d201933d41ec1f183dafd',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -187,11 +187,11 @@
   # 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': '93040c8d0d4a90b9edb9a16993812988ceb50027',
+  'angle_revision': '390ef29999bc0b1c1b976c1428b5914718477f4e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': 'f99302c4efe6f32297a619d407b4410ec3ee6412',
+  'swiftshader_revision': '2ce5fda2fea8027ba5b4ab86f12e790a291a11ee',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -246,7 +246,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': '76336cdd0411308cf1971b723614fb0477046435',
+  'devtools_frontend_revision': '2af328044cf7e30650c101e6ebac2e209e40ead4',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -521,7 +521,7 @@
   },
 
   'src/ios/third_party/material_components_ios/src': {
-      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '13b63828612cce8ddf54cad6aebc1b4361ec3532',
+      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '4a53cc44c270affe12c4e749c652d5f12d61925e',
       'condition': 'checkout_ios',
   },
 
@@ -1228,7 +1228,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'f5a94cc6837a834edde4d65e991c004a7fe99d4b',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '79679990ff54ed82d894a23a720536c54aeed234',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1458,7 +1458,7 @@
   },
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '52e2dbf1c11578414ffb5b155bf97a84b6ad647f',
+    Var('webrtc_git') + '/src.git' + '@' + 'f687d90f4219946afeb722d4f5e3b48e45c59865',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1533,7 +1533,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@331b83f6375688a0b157a7ce6c01a37fcd779639',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@b70ef7756a4d9317d3f2e28e1db628c616fb044c',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/WATCHLISTS b/WATCHLISTS
index 9ac2791d..3cc74af 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -2696,6 +2696,7 @@
     'source_idls': ['jmedley+watch@chromium.org'],
     'speed_metrics_changelog': ['igrigorik@chromium.org',
                                 'kayce@chromium.org',
+                                'lighthouse-eng+watch-speed-metrics@google.com',
                                 'rviscomi@chromium.org',
                                 'sullivan@chromium.org'],
     'spellcheck': ['rlp+watch@chromium.org',
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwAutofillTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwAutofillTest.java
index fe70bab..39e794d 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwAutofillTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwAutofillTest.java
@@ -5,6 +5,7 @@
 package org.chromium.android_webview.test;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -313,7 +314,23 @@
         public void setId(int id, String packageName, String typeName, String entryName) {}
 
         @Override
-        public void setDimens(int left, int top, int scrollX, int scrollY, int width, int height) {}
+        public void setDimens(int left, int top, int scrollX, int scrollY, int width, int height) {
+            mDimensRect = new Rect(left, top, width + left, height + top);
+            mDimensScrollX = scrollX;
+            mDimensScrollY = scrollY;
+        }
+
+        public Rect getDimensRect() {
+            return mDimensRect;
+        }
+
+        public int getDimensScrollX() {
+            return mDimensScrollX;
+        }
+
+        public int getDimensScrollY() {
+            return mDimensScrollY;
+        }
 
         @Override
         public void setElevation(float elevation) {}
@@ -423,6 +440,9 @@
         private boolean mDataIsSensitive;
         private AwHtmlInfo mAwHtmlInfo;
         private boolean mChecked;
+        private Rect mDimensRect;
+        private int mDimensScrollX;
+        private int mDimensScrollY;
     }
 
     // crbug.com/776230: On Android L, declaring variables of unsupported classes causes an error.
@@ -841,6 +861,10 @@
             assertEquals("placeholder@placeholder.com", child0.getHint());
             assertEquals("name", child0.getAutofillHints()[0]);
             assertEquals("given-name", child0.getAutofillHints()[1]);
+            assertFalse(child0.getDimensRect().isEmpty());
+            // The field has no scroll, should always be zero.
+            assertEquals(0, child0.getDimensScrollX());
+            assertEquals(0, child0.getDimensScrollY());
             TestViewStructure.AwHtmlInfo htmlInfo0 = child0.getHtmlInfo();
             assertEquals("text", htmlInfo0.getAttribute("type"));
             assertEquals("text1", htmlInfo0.getAttribute("id"));
@@ -854,6 +878,10 @@
             assertEquals(View.AUTOFILL_TYPE_TOGGLE, child1.getAutofillType());
             assertEquals("", child1.getHint());
             assertNull(child1.getAutofillHints());
+            assertFalse(child1.getDimensRect().isEmpty());
+            // The field has no scroll, should always be zero.
+            assertEquals(0, child1.getDimensScrollX());
+            assertEquals(0, child1.getDimensScrollY());
             TestViewStructure.AwHtmlInfo htmlInfo1 = child1.getHtmlInfo();
             assertEquals("checkbox", htmlInfo1.getAttribute("type"));
             assertEquals("checkbox1", htmlInfo1.getAttribute("id"));
@@ -867,6 +895,10 @@
             assertEquals(View.AUTOFILL_TYPE_LIST, child2.getAutofillType());
             assertEquals("", child2.getHint());
             assertNull(child2.getAutofillHints());
+            assertFalse(child2.getDimensRect().isEmpty());
+            // The field has no scroll, should always be zero.
+            assertEquals(0, child2.getDimensScrollX());
+            assertEquals(0, child2.getDimensScrollY());
             TestViewStructure.AwHtmlInfo htmlInfo2 = child2.getHtmlInfo();
             assertEquals("month", htmlInfo2.getAttribute("name"));
             assertEquals("select1", htmlInfo2.getAttribute("id"));
@@ -879,6 +911,10 @@
             assertEquals(View.AUTOFILL_TYPE_TEXT, child3.getAutofillType());
             assertEquals("", child3.getHint());
             assertNull(child3.getAutofillHints());
+            assertFalse(child3.getDimensRect().isEmpty());
+            // The field has no scroll, should always be zero.
+            assertEquals(0, child3.getDimensScrollX());
+            assertEquals(0, child3.getDimensScrollY());
             TestViewStructure.AwHtmlInfo htmlInfo3 = child3.getHtmlInfo();
             assertEquals("textarea1", htmlInfo3.getAttribute("name"));
 
@@ -1351,10 +1387,25 @@
             assertEquals(1, values.size());
             assertTrue(values.get(0).second.isList());
             assertEquals(1, values.get(0).second.getListValue());
+
+            // Verify the autofill session started by select control has dimens filled.
+            invokeOnProvideAutoFillVirtualStructure();
+            TestViewStructure viewStructure = mTestValues.testViewStructure;
+            assertNotNull(viewStructure);
+            assertEquals(2, viewStructure.getChildCount());
+            assertFalse(viewStructure.getChild(0).getDimensRect().isEmpty());
+            // The field has no scroll, should always be zero.
+            assertEquals(0, viewStructure.getChild(0).getDimensScrollX());
+            assertEquals(0, viewStructure.getChild(0).getDimensScrollY());
+            assertFalse(viewStructure.getChild(1).getDimensRect().isEmpty());
+            // The field has no scroll, should always be zero.
+            assertEquals(0, viewStructure.getChild(1).getDimensScrollX());
+            assertEquals(0, viewStructure.getChild(1).getDimensScrollY());
         } finally {
             webServer.shutdown();
         }
     }
+
     @Test
     @SmallTest
     @Feature({"AndroidWebView"})
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 6017265..e50427d4 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -736,6 +736,10 @@
     "system/accessibility/floating_menu_button.h",
     "system/accessibility/select_to_speak_tray.cc",
     "system/accessibility/select_to_speak_tray.h",
+    "system/accessibility/switch_access_back_button_view.cc",
+    "system/accessibility/switch_access_back_button_view.h",
+    "system/accessibility/switch_access_bubble_controller.cc",
+    "system/accessibility/switch_access_bubble_controller.h",
     "system/accessibility/tray_accessibility.cc",
     "system/accessibility/tray_accessibility.h",
     "system/accessibility/unified_accessibility_detailed_view_controller.cc",
@@ -1949,6 +1953,7 @@
     "system/accessibility/autoclick_menu_bubble_controller_unittest.cc",
     "system/accessibility/dictation_button_tray_unittest.cc",
     "system/accessibility/select_to_speak_tray_unittest.cc",
+    "system/accessibility/switch_access_bubble_controller_unittest.cc",
     "system/accessibility/tray_accessibility_unittest.cc",
     "system/bluetooth/bluetooth_notification_controller_unittest.cc",
     "system/bluetooth/bluetooth_power_controller_unittest.cc",
@@ -2143,6 +2148,7 @@
     "//base/util/values:values_util",
     "//build:branding_buildflags",
     "//chromeos:test_support",
+    "//chromeos/services/assistant/public/cpp:interaction_subscriber",
     "//chromeos/strings:strings_grit",
 
     # TODO(https://crbug.com/644336): Make CrasAudioHandler Chrome or Ash only.
@@ -2486,6 +2492,7 @@
     "//base:i18n",
     "//base/test:test_support",
     "//cc:test_support",
+    "//chromeos/services/assistant/public/cpp:interaction_subscriber",
     "//chromeos/services/assistant/public/cpp:prefs",
     "//ui/platform_window/common",
 
diff --git a/ash/accessibility/accessibility_controller_impl.cc b/ash/accessibility/accessibility_controller_impl.cc
index d8fa5c30..9faa3e059b 100644
--- a/ash/accessibility/accessibility_controller_impl.cc
+++ b/ash/accessibility/accessibility_controller_impl.cc
@@ -32,6 +32,7 @@
 #include "ash/sticky_keys/sticky_keys_controller.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/system/accessibility/accessibility_feature_disable_dialog.h"
+#include "ash/system/accessibility/switch_access_bubble_controller.h"
 #include "ash/system/power/backlights_forced_off_setter.h"
 #include "ash/system/power/power_status.h"
 #include "ash/system/power/scoped_backlights_forced_off.h"
@@ -1659,6 +1660,7 @@
   if (disable_dialog_accepted) {
     // The pref was already disabled, but we left switch access on until they
     // accepted the dialog.
+    switch_access_bubble_controller_.reset();
     switch_access_event_handler_.reset();
     NotifyAccessibilityStatusChanged();
   } else {
@@ -1812,7 +1814,7 @@
         ShowAccessibilityNotification(A11yNotificationType::kNone);
 
         if (no_switch_access_disable_confirmation_dialog_for_testing_) {
-          switch_access_event_handler_.reset();
+          SwitchAccessDisableDialogClosed(true);
         } else {
           new AccessibilityFeatureDisableDialog(
               IDS_ASH_SWITCH_ACCESS_DISABLE_CONFIRMATION_TITLE,
@@ -1823,9 +1825,11 @@
               base::BindOnce(
                   &AccessibilityControllerImpl::SwitchAccessDisableDialogClosed,
                   weak_ptr_factory_.GetWeakPtr(), false));
-          return;
         }
+        return;
       } else {
+        switch_access_bubble_controller_ =
+            std::make_unique<SwitchAccessBubbleController>();
         MaybeCreateSwitchAccessEventHandler();
         ShowAccessibilityNotification(
             A11yNotificationType::kSwitchAccessEnabled);
diff --git a/ash/accessibility/accessibility_controller_impl.h b/ash/accessibility/accessibility_controller_impl.h
index fc927df..11803bd 100644
--- a/ash/accessibility/accessibility_controller_impl.h
+++ b/ash/accessibility/accessibility_controller_impl.h
@@ -38,6 +38,7 @@
 class AccessibilityObserver;
 class ScopedBacklightsForcedOff;
 class SelectToSpeakEventHandler;
+class SwitchAccessBubbleController;
 class SwitchAccessEventHandler;
 
 enum AccessibilityNotificationVisibility {
@@ -382,6 +383,9 @@
 
   // Test helpers:
   SwitchAccessEventHandler* GetSwitchAccessEventHandlerForTest();
+  SwitchAccessBubbleController* GetSwitchAccessBubbleControllerForTest() {
+    return switch_access_bubble_controller_.get();
+  }
   void no_switch_access_disable_confirmation_dialog_for_testing(
       bool skip_dialog) {
     no_switch_access_disable_confirmation_dialog_for_testing_ = skip_dialog;
@@ -444,6 +448,8 @@
 
   // List of key codes that Switch Access should capture.
   std::vector<int> switch_access_keys_to_capture_;
+  std::unique_ptr<SwitchAccessBubbleController>
+      switch_access_bubble_controller_;
   std::unique_ptr<SwitchAccessEventHandler> switch_access_event_handler_;
   SwitchAccessEventHandlerDelegate* switch_access_event_handler_delegate_ =
       nullptr;
diff --git a/ash/ambient/ambient_controller.cc b/ash/ambient/ambient_controller.cc
index 704fe95..c8a58f1 100644
--- a/ash/ambient/ambient_controller.cc
+++ b/ash/ambient/ambient_controller.cc
@@ -51,9 +51,6 @@
     // devices.
     registry->RegisterBooleanPref(ash::ambient::prefs::kAmbientModeEnabled,
                                   true);
-    registry->RegisterIntegerPref(
-        ash::ambient::prefs::kAmbientModeTopicSource,
-        static_cast<int>(ash::ambient::prefs::TopicSource::kArtGallery));
   }
 }
 
diff --git a/ash/app_list/BUILD.gn b/ash/app_list/BUILD.gn
index c0d6767..6947e41 100644
--- a/ash/app_list/BUILD.gn
+++ b/ash/app_list/BUILD.gn
@@ -237,6 +237,7 @@
     "//base",
     "//base/test:test_support",
     "//chromeos/constants",
+    "//chromeos/services/assistant/public/cpp:interaction_subscriber",
     "//mojo/core/embedder",
     "//mojo/public/cpp/bindings",
     "//services/content/public/cpp",
diff --git a/ash/app_list/views/assistant/assistant_page_view_unittest.cc b/ash/app_list/views/assistant/assistant_page_view_unittest.cc
index 7040867..ed36310d 100644
--- a/ash/app_list/views/assistant/assistant_page_view_unittest.cc
+++ b/ash/app_list/views/assistant/assistant_page_view_unittest.cc
@@ -8,7 +8,7 @@
 #include "ash/assistant/ui/main_stage/suggestion_chip_view.h"
 #include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
-#include "chromeos/services/assistant/public/mojom/assistant.mojom-shared.h"
+#include "chromeos/services/assistant/public/cpp/default_assistant_interaction_subscriber.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/events/event.h"
 #include "ui/views/controls/textfield/textfield.h"
@@ -208,12 +208,11 @@
 
 // Counts the number of Assistant interactions that are started.
 class AssistantInteractionCounter
-    : private chromeos::assistant::mojom::AssistantInteractionSubscriber {
+    : private chromeos::assistant::DefaultAssistantInteractionSubscriber {
  public:
   explicit AssistantInteractionCounter(
       chromeos::assistant::mojom::Assistant* service) {
-    service->AddAssistantInteractionSubscriber(
-        receiver_.BindNewPipeAndPassRemote());
+    service->AddAssistantInteractionSubscriber(BindNewPipeAndPassRemote());
   }
   AssistantInteractionCounter(AssistantInteractionCounter&) = delete;
   AssistantInteractionCounter& operator=(AssistantInteractionCounter&) = delete;
@@ -222,35 +221,12 @@
   int interaction_count() const { return interaction_count_; }
 
  private:
-  // AssistantInteractionSubscriber implementation:
+  // DefaultAssistantInteractionSubscriber implementation:
   void OnInteractionStarted(
       chromeos::assistant::mojom::AssistantInteractionMetadataPtr) override {
     interaction_count_++;
   }
-  void OnInteractionFinished(
-      chromeos::assistant::mojom::AssistantInteractionResolution) override {}
-  void OnHtmlResponse(const std::string& response,
-                      const std::string& fallback) override {}
-  void OnSuggestionsResponse(
-      std::vector<chromeos::assistant::mojom::AssistantSuggestionPtr> response)
-      override {}
-  void OnTextResponse(const std::string& response) override {}
-  void OnTimersResponse(const std::vector<std::string>& timer_ids) override {}
-  void OnOpenUrlResponse(const ::GURL& url, bool in_background) override {}
-  void OnOpenAppResponse(chromeos::assistant::mojom::AndroidAppInfoPtr app_info,
-                         OnOpenAppResponseCallback callback) override {}
-  void OnSpeechRecognitionStarted() override {}
-  void OnSpeechRecognitionIntermediateResult(
-      const std::string& high_confidence_text,
-      const std::string& low_confidence_text) override {}
-  void OnSpeechRecognitionEndOfUtterance() override {}
-  void OnSpeechRecognitionFinalResult(
-      const std::string& final_result) override {}
-  void OnSpeechLevelUpdated(float speech_level) override {}
-  void OnTtsStarted(bool due_to_error) override {}
-  void OnWaitStarted() override {}
 
-  mojo::Receiver<AssistantInteractionSubscriber> receiver_{this};
   int interaction_count_ = 0;
 };
 
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index a4a8cf17d..907a566 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -676,6 +676,9 @@
       <message name="IDS_ASH_AUTOCLICK_DISABLE_CONFIRMATION_BODY" desc="The message in the modal dialog shown when the user disables automatic clicks, to confirm they meant to disable the feature">
         Are you sure you want to turn off automatic clicks?
       </message>
+      <message name="IDS_ASH_SWITCH_ACCESS_BACK_BUTTON_DESCRIPTION" desc="The tooltip text for the Switch Access back button. The back button allows users to exit the feature's current focus area.">
+        Back button
+      </message>
       <message name="IDS_ASH_STATUS_TRAY_ACCESSIBILITY_VIRTUAL_KEYBOARD" desc="The label used in the accessibility menu of the system tray to toggle on/off the onscreen keyboard.">
         On-screen keyboard
       </message>
diff --git a/ash/ash_strings_grd/IDS_ASH_SWITCH_ACCESS_BACK_BUTTON_DESCRIPTION.png.sha1 b/ash/ash_strings_grd/IDS_ASH_SWITCH_ACCESS_BACK_BUTTON_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..cf9f00e
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_SWITCH_ACCESS_BACK_BUTTON_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+f2a93c0d02d7dd47a5379b2b7afb9bc8450350ec
\ No newline at end of file
diff --git a/ash/assistant/test/test_assistant_service.cc b/ash/assistant/test/test_assistant_service.cc
index 699891d..034d372 100644
--- a/ash/assistant/test/test_assistant_service.cc
+++ b/ash/assistant/test/test_assistant_service.cc
@@ -10,11 +10,12 @@
 
 #include "ash/assistant/assistant_interaction_controller.h"
 #include "base/unguessable_token.h"
-#include "chromeos/services/assistant/public/mojom/assistant.mojom.h"
+#include "chromeos/services/assistant/public/cpp/default_assistant_interaction_subscriber.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace ash {
 
+using chromeos::assistant::DefaultAssistantInteractionSubscriber;
 using chromeos::assistant::mojom::AssistantInteractionMetadata;
 using chromeos::assistant::mojom::AssistantInteractionMetadataPtr;
 using chromeos::assistant::mojom::AssistantInteractionResolution;
@@ -28,17 +29,12 @@
 //    - A conversation is finished before starting a new one.
 //    - No responses (text, card, ...) are sent before starting or after
 //    finishing an interaction.
-class SanityCheckSubscriber : public AssistantInteractionSubscriber {
+class SanityCheckSubscriber : public DefaultAssistantInteractionSubscriber {
  public:
-  SanityCheckSubscriber() : receiver_(this) {}
+  SanityCheckSubscriber() = default;
   ~SanityCheckSubscriber() override = default;
 
-  mojo::PendingRemote<AssistantInteractionSubscriber>
-  BindNewPipeAndPassRemote() {
-    return receiver_.BindNewPipeAndPassRemote();
-  }
-
-  // AssistantInteractionSubscriber implementation:
+  // DefaultAssistantInteractionSubscriber implementation:
   void OnInteractionStarted(AssistantInteractionMetadataPtr metadata) override {
     if (current_state_ == ConversationState::kInProgress) {
       ADD_FAILURE()
@@ -81,23 +77,6 @@
     CheckResponse();
   }
 
-  void OnSpeechRecognitionStarted() override {}
-
-  void OnSpeechRecognitionIntermediateResult(
-      const std::string& high_confidence_text,
-      const std::string& low_confidence_text) override {}
-
-  void OnSpeechRecognitionEndOfUtterance() override {}
-
-  void OnSpeechRecognitionFinalResult(
-      const std::string& final_result) override {}
-
-  void OnSpeechLevelUpdated(float speech_level) override {}
-
-  void OnTtsStarted(bool due_to_error) override {}
-
-  void OnWaitStarted() override {}
-
  private:
   void CheckResponse() {
     if (current_state_ == ConversationState::kNotStarted)
@@ -115,26 +94,21 @@
   };
 
   ConversationState current_state_ = ConversationState::kNotStarted;
-  mojo::Receiver<AssistantInteractionSubscriber> receiver_;
 
   DISALLOW_COPY_AND_ASSIGN(SanityCheckSubscriber);
 };
 
 // Subscriber that tracks the current interaction.
-class CurrentInteractionSubscriber : public AssistantInteractionSubscriber {
+class CurrentInteractionSubscriber
+    : public DefaultAssistantInteractionSubscriber {
  public:
-  CurrentInteractionSubscriber() : receiver_(this) {}
+  CurrentInteractionSubscriber() = default;
   CurrentInteractionSubscriber(CurrentInteractionSubscriber&) = delete;
   CurrentInteractionSubscriber& operator=(CurrentInteractionSubscriber&) =
       delete;
   ~CurrentInteractionSubscriber() override = default;
 
-  mojo::PendingRemote<AssistantInteractionSubscriber>
-  BindNewPipeAndPassRemote() {
-    return receiver_.BindNewPipeAndPassRemote();
-  }
-
-  // AssistantInteractionSubscriber implementation:
+  // DefaultAssistantInteractionSubscriber implementation:
   void OnInteractionStarted(AssistantInteractionMetadataPtr metadata) override {
     current_interaction_ = *metadata;
   }
@@ -144,27 +118,6 @@
     current_interaction_ = base::nullopt;
   }
 
-  void OnHtmlResponse(const std::string& response,
-                      const std::string& fallback) override {}
-  void OnSuggestionsResponse(
-      std::vector<chromeos::assistant::mojom::AssistantSuggestionPtr> response)
-      override {}
-  void OnTextResponse(const std::string& response) override {}
-  void OnTimersResponse(const std::vector<std::string>& timer_ids) override {}
-  void OnOpenUrlResponse(const ::GURL& url, bool in_background) override {}
-  void OnOpenAppResponse(chromeos::assistant::mojom::AndroidAppInfoPtr app_info,
-                         OnOpenAppResponseCallback callback) override {}
-  void OnSpeechRecognitionStarted() override {}
-  void OnSpeechRecognitionIntermediateResult(
-      const std::string& high_confidence_text,
-      const std::string& low_confidence_text) override {}
-  void OnSpeechRecognitionEndOfUtterance() override {}
-  void OnSpeechRecognitionFinalResult(
-      const std::string& final_result) override {}
-  void OnSpeechLevelUpdated(float speech_level) override {}
-  void OnTtsStarted(bool due_to_error) override {}
-  void OnWaitStarted() override {}
-
   base::Optional<AssistantInteractionMetadata> current_interaction() {
     return current_interaction_;
   }
@@ -172,7 +125,6 @@
  private:
   base::Optional<AssistantInteractionMetadata> current_interaction_ =
       base::nullopt;
-  mojo::Receiver<AssistantInteractionSubscriber> receiver_;
 };
 
 class InteractionResponse::Response {
diff --git a/ash/autoclick/autoclick_ring_handler.cc b/ash/autoclick/autoclick_ring_handler.cc
index 4680897e..2666822 100644
--- a/ash/autoclick/autoclick_ring_handler.cc
+++ b/ash/autoclick/autoclick_ring_handler.cc
@@ -112,12 +112,6 @@
     SchedulePaint();
   }
 
-  void UpdateWithShrinkAnimation(gfx::Animation* animation) {
-    current_angle_ = animation->CurrentValueBetween(
-        kAutoclickRingAngleStartValue, kAutoclickRingAngleEndValue);
-    SchedulePaint();
-  }
-
   void SetSize(int radius) { radius_ = radius; }
 
  private:
@@ -166,7 +160,7 @@
   ring_widget_ = widget;
   current_animation_type_ = AnimationType::GROW_ANIMATION;
   animation_duration_ = duration;
-  StartAnimation(base::TimeDelta());
+  StartAnimation(animation_duration_);
 }
 
 void AutoclickRingHandler::StopGesture() {
@@ -197,13 +191,6 @@
       Start();
       break;
     }
-    case AnimationType::SHRINK_ANIMATION: {
-      view_.reset(
-          new AutoclickRingView(tap_down_location_, ring_widget_, radius_));
-      SetDuration(delay);
-      Start();
-      break;
-    }
     case AnimationType::NONE:
       NOTREACHED();
       break;
@@ -226,10 +213,6 @@
       view_->SetLocation(tap_down_location_);
       view_->UpdateWithGrowAnimation(this);
       break;
-    case AnimationType::SHRINK_ANIMATION:
-      view_->SetLocation(tap_down_location_);
-      view_->UpdateWithShrinkAnimation(this);
-      break;
     case AnimationType::NONE:
       NOTREACHED();
       break;
@@ -239,14 +222,10 @@
 void AutoclickRingHandler::AnimationStopped() {
   switch (current_animation_type_) {
     case AnimationType::GROW_ANIMATION:
-      current_animation_type_ = AnimationType::SHRINK_ANIMATION;
-      StartAnimation(animation_duration_);
-      break;
-    case AnimationType::SHRINK_ANIMATION:
       current_animation_type_ = AnimationType::NONE;
       break;
     case AnimationType::NONE:
-      // fall through to reset the view.
+      // Fall through to reset the view.
       view_.reset();
       break;
   }
diff --git a/ash/autoclick/autoclick_ring_handler.h b/ash/autoclick/autoclick_ring_handler.h
index a7c9fe0..2526c0b 100644
--- a/ash/autoclick/autoclick_ring_handler.h
+++ b/ash/autoclick/autoclick_ring_handler.h
@@ -14,8 +14,8 @@
 namespace ash {
 
 // AutoclickRingHandler displays an animated affordance that is shown
-// on autoclick gesture. The animation sequence consists of two circles which
-// shrink towards the spot the autoclick will generate a mouse event.
+// on autoclick gesture. The animation is a semi-transparent ring which
+// fills with white.
 class AutoclickRingHandler : public gfx::LinearAnimation {
  public:
   AutoclickRingHandler();
@@ -36,7 +36,6 @@
   enum class AnimationType {
     NONE,
     GROW_ANIMATION,
-    SHRINK_ANIMATION,
   };
 
   void StartAnimation(base::TimeDelta duration);
diff --git a/ash/public/cpp/ambient/ambient_prefs.cc b/ash/public/cpp/ambient/ambient_prefs.cc
index a296bf6..a42b1d7 100644
--- a/ash/public/cpp/ambient/ambient_prefs.cc
+++ b/ash/public/cpp/ambient/ambient_prefs.cc
@@ -14,8 +14,6 @@
 
 constexpr char kAmbientModeEnabled[] = "settings.ambient_mode.enabled";
 
-constexpr char kAmbientModeTopicSource[] = "settings.ambient_mode.topic_source";
-
 }  // namespace prefs
 }  // namespace ambient
 }  // namespace ash
diff --git a/ash/public/cpp/ambient/ambient_prefs.h b/ash/public/cpp/ambient/ambient_prefs.h
index 68a045a..3e614377 100644
--- a/ash/public/cpp/ambient/ambient_prefs.h
+++ b/ash/public/cpp/ambient/ambient_prefs.h
@@ -11,27 +11,12 @@
 namespace ambient {
 namespace prefs {
 
-// Enumeration of the topic source, i.e. where the photos come from.
-// Values need to stay in sync with the |topicSource_| in ambient_mode_page.js.
-// Art gallery is a super set of art related topic sources in Backdrop service.
-// These values are registered in prefs.
-// Entries should not be renumbered and numeric values should never be reused.
-// Only append to this enum is allowed if more source will be added.
-enum class TopicSource {
-  kGooglePhotos = 0,
-  kArtGallery = 1,
-  kMaxValue = kArtGallery,
-};
-
 // A GUID for backdrop client.
 ASH_PUBLIC_EXPORT extern const char kAmbientBackdropClientId[];
 
 // Boolean pref for whether ambient mode is enabled.
 ASH_PUBLIC_EXPORT extern const char kAmbientModeEnabled[];
 
-// Integer pref for ambient topic source, value is one of TopicSource.
-ASH_PUBLIC_EXPORT extern const char kAmbientModeTopicSource[];
-
 }  // namespace prefs
 }  // namespace ambient
 }  // namespace ash
diff --git a/ash/resources/vector_icons/BUILD.gn b/ash/resources/vector_icons/BUILD.gn
index 67243b37..f11a149d 100644
--- a/ash/resources/vector_icons/BUILD.gn
+++ b/ash/resources/vector_icons/BUILD.gn
@@ -146,6 +146,7 @@
     "shelf_sign_out_button.icon",
     "shelf_unlock_button.icon",
     "switch_access.icon",
+    "switch_access_back.icon",
     "system_menu_accessibility.icon",
     "system_menu_accessibility_auto_click.icon",
     "system_menu_accessibility_chromevox.icon",
diff --git a/ash/resources/vector_icons/switch_access_back.icon b/ash/resources/vector_icons/switch_access_back.icon
new file mode 100644
index 0000000..52a91696
--- /dev/null
+++ b/ash/resources/vector_icons/switch_access_back.icon
@@ -0,0 +1,19 @@
+// Copyright 2020 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.
+
+CANVAS_DIMENSIONS, 20,
+MOVE_TO, 4.17f, 7.5f,
+LINE_TO, 9.17f, 2.5f,
+LINE_TO, 10.35f, 3.68f,
+LINE_TO, 7.36f, 6.67f,
+LINE_TO, 16.67f, 6.67f,
+LINE_TO, 16.67f, 16.67f,
+LINE_TO, 15, 16.67f,
+LINE_TO, 15, 8.33f,
+LINE_TO, 7.36f, 8.33f,
+LINE_TO, 10.35f, 11.32f,
+LINE_TO, 9.17f, 12.5f,
+CLOSE,
+MOVE_TO, 4.17f, 7.5f,
+CLOSE
diff --git a/ash/system/accessibility/select_to_speak_tray.h b/ash/system/accessibility/select_to_speak_tray.h
index 778cad0..8921041 100644
--- a/ash/system/accessibility/select_to_speak_tray.h
+++ b/ash/system/accessibility/select_to_speak_tray.h
@@ -29,9 +29,11 @@
   // TrayBackgroundView:
   base::string16 GetAccessibleNameForTray() override;
   const char* GetClassName() const override;
+  bool PerformAction(const ui::Event& event) override;
+  // The SelectToSpeakTray does not have a bubble, so these functions are
+  // no-ops.
   void HideBubbleWithView(const TrayBubbleView* bubble_view) override {}
   void ClickedOutsideBubble() override {}
-  bool PerformAction(const ui::Event& event) override;
 
   // AccessibilityObserver:
   void OnAccessibilityStatusChanged() override;
diff --git a/ash/system/accessibility/select_to_speak_tray_unittest.cc b/ash/system/accessibility/select_to_speak_tray_unittest.cc
index 32f1f36..8d8c9b1 100644
--- a/ash/system/accessibility/select_to_speak_tray_unittest.cc
+++ b/ash/system/accessibility/select_to_speak_tray_unittest.cc
@@ -118,4 +118,12 @@
       GetInactiveImage().BackedBySameObjectAs(GetImageView()->GetImage()));
 }
 
+// Trivial test to increase coverage of select_to_speak_tray.h. The
+// SelectToSpeakTray does not have a bubble, so these are empty functions.
+// Without this test, coverage of select_to_speak_tray.h is 0%.
+TEST_F(SelectToSpeakTrayTest, OverriddenFunctionsDoNothing) {
+  GetTray()->HideBubbleWithView(nullptr);
+  GetTray()->ClickedOutsideBubble();
+}
+
 }  // namespace ash
diff --git a/ash/system/accessibility/switch_access_back_button_view.cc b/ash/system/accessibility/switch_access_back_button_view.cc
new file mode 100644
index 0000000..329ac5e
--- /dev/null
+++ b/ash/system/accessibility/switch_access_back_button_view.cc
@@ -0,0 +1,56 @@
+// Copyright 2020 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 "ash/system/accessibility/switch_access_back_button_view.h"
+
+#include "ash/resources/vector_icons/vector_icons.h"
+#include "ash/strings/grit/ash_strings.h"
+#include "ash/system/accessibility/floating_menu_button.h"
+#include "ash/system/tray/tray_constants.h"
+#include "cc/paint/paint_flags.h"
+#include "ui/events/event.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/color_palette.h"
+#include "ui/gfx/geometry/point_f.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/views/layout/box_layout.h"
+
+namespace ash {
+
+SwitchAccessBackButtonView::SwitchAccessBackButtonView(int diameter)
+    : diameter_(diameter),
+      back_button_(
+          new FloatingMenuButton(this,
+                                 kSwitchAccessBackIcon,
+                                 IDS_ASH_SWITCH_ACCESS_BACK_BUTTON_DESCRIPTION,
+                                 /*flip_for_rtl=*/false,
+                                 diameter)) {
+  std::unique_ptr<views::BoxLayout> layout = std::make_unique<views::BoxLayout>(
+      views::BoxLayout::Orientation::kHorizontal, gfx::Insets());
+  SetLayoutManager(std::move(layout));
+  AddChildView(back_button_);
+}
+
+void SwitchAccessBackButtonView::ButtonPressed(views::Button* sender,
+                                               const ui::Event& event) {
+  // This code should not be called presently, as the button is never shown.
+  // TODO(crbug/973719): Transition to using new back button and menu which
+  //                     will be implemented using views/.
+  LOG(WARNING) << "Implementation of back button pressed not finished.";
+}
+
+const char* SwitchAccessBackButtonView::GetClassName() const {
+  return "SwitchAccessBackButtonView";
+}
+
+void SwitchAccessBackButtonView::OnPaint(gfx::Canvas* canvas) {
+  gfx::Rect rect(GetContentsBounds());
+  cc::PaintFlags flags;
+  flags.setAntiAlias(true);
+  flags.setColor(gfx::kGoogleGrey800);
+  flags.setStyle(cc::PaintFlags::kFill_Style);
+  canvas->DrawCircle(gfx::PointF(rect.CenterPoint()), diameter_ / 2.f, flags);
+}
+
+}  // namespace ash
diff --git a/ash/system/accessibility/switch_access_back_button_view.h b/ash/system/accessibility/switch_access_back_button_view.h
new file mode 100644
index 0000000..2f9155b
--- /dev/null
+++ b/ash/system/accessibility/switch_access_back_button_view.h
@@ -0,0 +1,42 @@
+// Copyright 2020 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 ASH_SYSTEM_ACCESSIBILITY_SWITCH_ACCESS_BACK_BUTTON_VIEW_H_
+#define ASH_SYSTEM_ACCESSIBILITY_SWITCH_ACCESS_BACK_BUTTON_VIEW_H_
+
+#include "ui/views/controls/button/button.h"
+#include "ui/views/view.h"
+
+namespace ash {
+
+class FloatingMenuButton;
+
+// View for the Switch Access Back Button.
+class SwitchAccessBackButtonView : public views::View,
+                                   public views::ButtonListener {
+ public:
+  explicit SwitchAccessBackButtonView(int button_size);
+  ~SwitchAccessBackButtonView() override = default;
+
+  SwitchAccessBackButtonView(const SwitchAccessBackButtonView&) = delete;
+  SwitchAccessBackButtonView& operator=(const SwitchAccessBackButtonView&) =
+      delete;
+
+  // views::ButtonListener:
+  void ButtonPressed(views::Button* sender, const ui::Event& event) override;
+
+  // views::View:
+  const char* GetClassName() const override;
+  void OnPaint(gfx::Canvas* canvas) override;
+
+ private:
+  int diameter_;
+
+  // Owned by views hierarchy.
+  FloatingMenuButton* back_button_;
+};
+
+}  // namespace ash
+
+#endif  // ASH_SYSTEM_ACCESSIBILITY_SWITCH_ACCESS_BACK_BUTTON_VIEW_H_
diff --git a/ash/system/accessibility/switch_access_bubble_controller.cc b/ash/system/accessibility/switch_access_bubble_controller.cc
new file mode 100644
index 0000000..219e7a9
--- /dev/null
+++ b/ash/system/accessibility/switch_access_bubble_controller.cc
@@ -0,0 +1,77 @@
+// Copyright 2020 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 "ash/system/accessibility/switch_access_bubble_controller.h"
+
+#include "ash/public/cpp/shell_window_ids.h"
+#include "ash/shell.h"
+#include "ash/system/accessibility/switch_access_back_button_view.h"
+#include "ash/system/tray/tray_background_view.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/unified/unified_system_tray_view.h"
+
+namespace ash {
+
+namespace {
+constexpr int kBackButtonRadiusDip = 18;
+constexpr int kBackButtonDiameterDip = 2 * kBackButtonRadiusDip;
+}  // namespace
+
+SwitchAccessBubbleController::SwitchAccessBubbleController() {}
+
+SwitchAccessBubbleController::~SwitchAccessBubbleController() {
+  if (back_button_widget_ && !back_button_widget_->IsClosed())
+    back_button_widget_->CloseNow();
+}
+
+void SwitchAccessBubbleController::ShowBackButton(const gfx::Rect& anchor) {
+  if (back_button_widget_) {
+    DCHECK(back_button_bubble_view_);
+    back_button_bubble_view_->ChangeAnchorRect(anchor);
+    return;
+  }
+
+  TrayBubbleView::InitParams init_params;
+  init_params.delegate = this;
+  // Anchor within the overlay container.
+  init_params.parent_window = Shell::GetContainer(
+      Shell::GetPrimaryRootWindow(), kShellWindowId_OverlayContainer);
+  init_params.anchor_mode = TrayBubbleView::AnchorMode::kRect;
+  init_params.anchor_rect = anchor;
+  init_params.has_shadow = false;
+
+  // The back button is a circle, so the max/min width and height are the
+  // diameter, and the corner radius is the circle radius.
+  init_params.corner_radius = kBackButtonRadiusDip;
+  init_params.min_width = kBackButtonDiameterDip;
+  init_params.max_width = kBackButtonDiameterDip;
+  init_params.max_height = kBackButtonDiameterDip;
+
+  back_button_bubble_view_ = new TrayBubbleView(init_params);
+
+  back_button_view_ = new SwitchAccessBackButtonView(kBackButtonDiameterDip);
+  back_button_view_->SetBackground(UnifiedSystemTrayView::CreateBackground());
+  back_button_bubble_view_->AddChildView(back_button_view_);
+  back_button_bubble_view_->set_color(SK_ColorTRANSPARENT);
+  back_button_bubble_view_->layer()->SetFillsBoundsOpaquely(false);
+
+  back_button_widget_ =
+      views::BubbleDialogDelegateView::CreateBubble(back_button_bubble_view_);
+  TrayBackgroundView::InitializeBubbleAnimations(back_button_widget_);
+  back_button_bubble_view_->InitializeAndShowBubble();
+}
+
+void SwitchAccessBubbleController::CloseBubble() {
+  if (back_button_widget_ && !back_button_widget_->IsClosed())
+    back_button_widget_->CloseWithReason(
+        views::Widget::ClosedReason::kLostFocus);
+}
+
+void SwitchAccessBubbleController::BubbleViewDestroyed() {
+  back_button_view_ = nullptr;
+  back_button_bubble_view_ = nullptr;
+  back_button_widget_ = nullptr;
+}
+
+}  // namespace ash
diff --git a/ash/system/accessibility/switch_access_bubble_controller.h b/ash/system/accessibility/switch_access_bubble_controller.h
new file mode 100644
index 0000000..00ae8c9
--- /dev/null
+++ b/ash/system/accessibility/switch_access_bubble_controller.h
@@ -0,0 +1,45 @@
+// Copyright 2020 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 ASH_SYSTEM_ACCESSIBILITY_SWITCH_ACCESS_BUBBLE_CONTROLLER_H_
+#define ASH_SYSTEM_ACCESSIBILITY_SWITCH_ACCESS_BUBBLE_CONTROLLER_H_
+
+#include "ash/system/tray/tray_bubble_view.h"
+
+namespace ash {
+
+class SwitchAccessBackButtonView;
+
+// Manages the Switch Access back button bubble.
+class ASH_EXPORT SwitchAccessBubbleController
+    : public TrayBubbleView::Delegate {
+ public:
+  SwitchAccessBubbleController();
+  ~SwitchAccessBubbleController() override;
+
+  SwitchAccessBubbleController(const SwitchAccessBubbleController&) = delete;
+  SwitchAccessBubbleController& operator=(const SwitchAccessBubbleController&) =
+      delete;
+
+  void ShowBackButton(const gfx::Rect& anchor);
+  void CloseBubble();
+
+  SwitchAccessBackButtonView* GetBackButtonViewForTesting() {
+    return back_button_view_;
+  }
+
+  // TrayBubbleView::Delegate:
+  void BubbleViewDestroyed() override;
+
+ private:
+  // Owned by views hierarchy.
+  SwitchAccessBackButtonView* back_button_view_ = nullptr;
+  TrayBubbleView* back_button_bubble_view_ = nullptr;
+
+  views::Widget* back_button_widget_ = nullptr;
+};
+
+}  // namespace ash
+
+#endif  // ASH_SYSTEM_ACCESSIBILITY_SWITCH_ACCESS_BUBBLE_CONTROLLER_H_
diff --git a/ash/system/accessibility/switch_access_bubble_controller_unittest.cc b/ash/system/accessibility/switch_access_bubble_controller_unittest.cc
new file mode 100644
index 0000000..c9ee20c
--- /dev/null
+++ b/ash/system/accessibility/switch_access_bubble_controller_unittest.cc
@@ -0,0 +1,60 @@
+// Copyright 2020 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 "ash/system/accessibility/switch_access_bubble_controller.h"
+
+#include "ash/accessibility/accessibility_controller_impl.h"
+#include "ash/shell.h"
+#include "ash/system/accessibility/switch_access_back_button_view.h"
+#include "ash/test/ash_test_base.h"
+#include "base/command_line.h"
+#include "ui/accessibility/accessibility_switches.h"
+
+namespace ash {
+
+class SwitchAccessBubbleControllerTest : public AshTestBase {
+ public:
+  SwitchAccessBubbleControllerTest() = default;
+  ~SwitchAccessBubbleControllerTest() override = default;
+
+  SwitchAccessBubbleControllerTest(const SwitchAccessBubbleControllerTest&) =
+      delete;
+  SwitchAccessBubbleControllerTest& operator=(
+      const SwitchAccessBubbleControllerTest&) = delete;
+
+  // testing::Test:
+  void SetUp() override {
+    base::CommandLine::ForCurrentProcess()->AppendSwitch(
+        ::switches::kEnableExperimentalAccessibilitySwitchAccess);
+    AshTestBase::SetUp();
+    Shell::Get()->accessibility_controller()->SetSwitchAccessEnabled(true);
+  }
+
+  SwitchAccessBubbleController* GetBubbleController() {
+    return Shell::Get()
+        ->accessibility_controller()
+        ->GetSwitchAccessBubbleControllerForTest();
+  }
+
+  gfx::Rect GetBackButtonBounds() {
+    SwitchAccessBubbleController* bubble_controller = GetBubbleController();
+    if (bubble_controller && bubble_controller->GetBackButtonViewForTesting())
+      return bubble_controller->GetBackButtonViewForTesting()
+          ->GetBoundsInScreen();
+    return gfx::Rect();
+  }
+};
+
+// TODO(anastasi): Add more tests for closing and repositioning the button.
+TEST_F(SwitchAccessBubbleControllerTest, ShowBackButton) {
+  EXPECT_TRUE(GetBubbleController());
+  gfx::Rect anchor_rect(100, 100, 0, 0);
+  GetBubbleController()->ShowBackButton(anchor_rect);
+
+  gfx::Rect bounds = GetBackButtonBounds();
+  EXPECT_EQ(bounds.width(), 36);
+  EXPECT_EQ(bounds.height(), 36);
+}
+
+}  // namespace ash
diff --git a/ash/wm/desks/desk_mini_view.cc b/ash/wm/desks/desk_mini_view.cc
index ad77b65..86f521c2 100644
--- a/ash/wm/desks/desk_mini_view.cc
+++ b/ash/wm/desks/desk_mini_view.cc
@@ -191,14 +191,20 @@
 }
 
 void DeskMiniView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
-  views::View::GetAccessibleNodeData(node_data);
+  desk_preview_->GetAccessibleNodeData(node_data);
 
   // Note that the desk may have already been destroyed.
-  if (desk_ && desk_->is_active()) {
-    node_data->AddStringAttribute(
-        ax::mojom::StringAttribute::kValue,
-        l10n_util::GetStringUTF8(
-            IDS_ASH_DESKS_ACTIVE_DESK_MINIVIEW_A11Y_EXTRA_TIP));
+  if (desk_) {
+    // Announce desk name.
+    node_data->AddStringAttribute(ax::mojom::StringAttribute::kName,
+                                  base::UTF16ToUTF8(desk_->name()));
+
+    if (desk_->is_active()) {
+      node_data->AddStringAttribute(
+          ax::mojom::StringAttribute::kValue,
+          l10n_util::GetStringUTF8(
+              IDS_ASH_DESKS_ACTIVE_DESK_MINIVIEW_A11Y_EXTRA_TIP));
+    }
   }
 
   if (DesksController::Get()->CanRemoveDesks()) {
@@ -278,8 +284,22 @@
   if (!desk_)
     return;
 
+  // Avoid copying new_contents if we don't need to trim it below.
+  const base::string16* new_text = &new_contents;
+
+  // To avoid potential security and memory issues, we don't allow desk names to
+  // have an unbounded length. Therefore we trim if needed at kMaxLength UTF-16
+  // boundary. Note that we don't care about code point boundaries in this case.
+  base::string16 trimmed_new_contents;
+  if (new_contents.size() > DeskNameView::kMaxLength) {
+    trimmed_new_contents = new_contents;
+    trimmed_new_contents.resize(DeskNameView::kMaxLength);
+    new_text = &trimmed_new_contents;
+    desk_name_view_->SetText(trimmed_new_contents);
+  }
+
   desk_->SetName(
-      base::CollapseWhitespace(new_contents,
+      base::CollapseWhitespace(*new_text,
                                /*trim_sequences_with_line_breaks=*/false),
       /*set_by_user=*/true);
 }
diff --git a/ash/wm/desks/desk_name_view.cc b/ash/wm/desks/desk_name_view.cc
index 17198ce..80c5ba9 100644
--- a/ash/wm/desks/desk_name_view.cc
+++ b/ash/wm/desks/desk_name_view.cc
@@ -52,6 +52,9 @@
 DeskNameView::~DeskNameView() = default;
 
 // static
+constexpr size_t DeskNameView::kMaxLength;
+
+// static
 void DeskNameView::CommitChanges(views::Widget* widget) {
   DCHECK(IsDesksBarWidget(widget));
 
diff --git a/ash/wm/desks/desk_name_view.h b/ash/wm/desks/desk_name_view.h
index 7cdbffd8..321d68d 100644
--- a/ash/wm/desks/desk_name_view.h
+++ b/ash/wm/desks/desk_name_view.h
@@ -5,6 +5,7 @@
 #ifndef ASH_WM_DESKS_DESK_NAME_VIEW_H_
 #define ASH_WM_DESKS_DESK_NAME_VIEW_H_
 
+#include "ash/ash_export.h"
 #include "ash/wm/overview/overview_highlight_controller.h"
 #include "ash/wm/wm_highlight_item_border.h"
 #include "ui/views/controls/textfield/textfield.h"
@@ -15,7 +16,7 @@
 // corresponding desk. When it's not focused, it looks like a normal label. It
 // can be highlighted and activated by the OverviewHighlightController, and it
 // provides an API to elide long desk names.
-class DeskNameView
+class ASH_EXPORT DeskNameView
     : public views::Textfield,
       public OverviewHighlightController::OverviewHighlightableView {
  public:
@@ -24,6 +25,9 @@
   DeskNameView& operator=(const DeskNameView&) = delete;
   ~DeskNameView() override;
 
+  // The max number of characters (UTF-16) allowed for desks' names.
+  static constexpr size_t kMaxLength = 300;
+
   // Commits an on-going desk name change (if any) by bluring the focus away
   // from any view on |widget|, where |widget| should be the desks bar widget.
   static void CommitChanges(views::Widget* widget);
diff --git a/ash/wm/desks/desks_unittests.cc b/ash/wm/desks/desks_unittests.cc
index 43355928..d69093fe 100644
--- a/ash/wm/desks/desks_unittests.cc
+++ b/ash/wm/desks/desks_unittests.cc
@@ -61,6 +61,8 @@
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/client/window_parenting_client.h"
 #include "ui/aura/test/test_window_delegate.h"
+#include "ui/base/clipboard/clipboard_buffer.h"
+#include "ui/base/clipboard/scoped_clipboard_writer.h"
 #include "ui/base/ui_base_types.h"
 #include "ui/chromeos/events/event_rewriter_chromeos.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
@@ -1876,6 +1878,47 @@
   ASSERT_TRUE(Shell::Get()->overview_controller()->InOverviewSession());
 }
 
+TEST_F(DesksEditableNamesTest, MaxLength) {
+  ASSERT_EQ(2u, controller()->desks().size());
+  auto* overview_controller = Shell::Get()->overview_controller();
+  ASSERT_TRUE(overview_controller->InOverviewSession());
+
+  ClickOnDeskNameViewAtIndex(0);
+  // Select all and delete.
+  SendKey(ui::VKEY_A, ui::EF_CONTROL_DOWN);
+  SendKey(ui::VKEY_BACK);
+
+  // Simulate user is typing text beyond the max length.
+  base::string16 expected_desk_name(DeskNameView::kMaxLength, L'a');
+  for (size_t i = 0; i < DeskNameView::kMaxLength + 10; ++i)
+    SendKey(ui::VKEY_A);
+  SendKey(ui::VKEY_RETURN);
+
+  // Desk name has been trimmed.
+  auto* desk_1 = controller()->desks()[0].get();
+  EXPECT_EQ(DeskNameView::kMaxLength, desk_1->name().size());
+  EXPECT_EQ(expected_desk_name, desk_1->name());
+  EXPECT_TRUE(desk_1->is_name_set_by_user());
+
+  // Test that pasting a large amount of text is trimmed at the max length.
+  base::string16 clipboard_text(DeskNameView::kMaxLength + 10, L'b');
+  expected_desk_name = base::string16(DeskNameView::kMaxLength, L'b');
+  EXPECT_GT(clipboard_text.size(), DeskNameView::kMaxLength);
+  ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
+      .WriteText(clipboard_text);
+
+  ClickOnDeskNameViewAtIndex(0);
+  // Select all and delete.
+  SendKey(ui::VKEY_A, ui::EF_CONTROL_DOWN);
+  SendKey(ui::VKEY_BACK);
+
+  // Paste text.
+  SendKey(ui::VKEY_V, ui::EF_CONTROL_DOWN);
+  SendKey(ui::VKEY_RETURN);
+  EXPECT_EQ(DeskNameView::kMaxLength, desk_1->name().size());
+  EXPECT_EQ(expected_desk_name, desk_1->name());
+}
+
 class TabletModeDesksTest : public DesksTest {
  public:
   TabletModeDesksTest() = default;
diff --git a/base/metrics/metrics_hashes_unittest.cc b/base/metrics/metrics_hashes_unittest.cc
index ec3446f2..cc6247e5 100644
--- a/base/metrics/metrics_hashes_unittest.cc
+++ b/base/metrics/metrics_hashes_unittest.cc
@@ -16,6 +16,7 @@
 
 // Make sure our ID hashes are the same as what we see on the server side.
 TEST(MetricsUtilTest, HashMetricName) {
+  // The cases must match those in //tools/metrics/ukm/codegen_test.py.
   static const struct {
     std::string input;
     std::string output;
diff --git a/base/threading/thread_restrictions.h b/base/threading/thread_restrictions.h
index 5f03090..08aca46 100644
--- a/base/threading/thread_restrictions.h
+++ b/base/threading/thread_restrictions.h
@@ -206,6 +206,7 @@
 }
 }
 namespace printing {
+class LocalPrinterHandlerDefault;
 class PrintJobWorker;
 class PrinterQuery;
 }
@@ -372,6 +373,7 @@
   friend class memory_instrumentation::OSMetrics;
   friend class module_installer::ScopedAllowModulePakLoad;
   friend class mojo::CoreLibraryInitializer;
+  friend class printing::LocalPrinterHandlerDefault;
   friend class printing::PrintJobWorker;
   friend class resource_coordinator::TabManagerDelegate;  // crbug.com/778703
   friend class web::WebSubThread;
diff --git a/build/android/apk_operations.py b/build/android/apk_operations.py
index 64a1188c..fddf9fff 100755
--- a/build/android/apk_operations.py
+++ b/build/android/apk_operations.py
@@ -1620,7 +1620,7 @@
           raise Exception(
               'Cannot print full certificate because apk is not V1 signed.')
 
-        cmd = [keytool, '-printcert', '-jarfile', '-rfc', self.apk_helper.path]
+        cmd = [keytool, '-printcert', '-jarfile', self.apk_helper.path, '-rfc']
         # Redirect stderr to hide a keytool warning about using non-standard
         # keystore format.
         full_output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
@@ -1816,7 +1816,8 @@
 
 def _RunInternal(parser, output_directory=None, bundle_generation_info=None):
   colorama.init()
-  parser.set_defaults(output_directory=output_directory)
+  parser.set_defaults(
+      additional_apk_paths=None, output_directory=output_directory)
   from_wrapper_script = bool(output_directory)
   args = _ParseArgs(parser, from_wrapper_script, bool(bundle_generation_info))
   run_tests_helper.SetLogLevel(args.verbose_count)
diff --git a/build/config/fuchsia/symbol_archive.gni b/build/config/fuchsia/symbol_archive.gni
index 28fa248..9dcb53c 100644
--- a/build/config/fuchsia/symbol_archive.gni
+++ b/build/config/fuchsia/symbol_archive.gni
@@ -8,6 +8,7 @@
 # ".build_ids" convention used by the symbolizer and GNU GDB.
 #
 # Parameters:
+#   deps: Must all be cr_fuchsia_package() or fuchsia_package() targets.
 #   ids_txt: The "ids.txt" file which lists the relative paths to unstripped
 #            executables and libraries, along with their build IDs.
 #   archive_name: The path to the compressed tarball that will be generated.
@@ -24,7 +25,16 @@
 
     outputs = [ _build_ids ]
 
-    deps = invoker.deps
+    # For each package in |deps| it is necessary to additionally depend upon
+    # the corresponding archive-manifest target, which is what creates the
+    # ids.txt file.
+    deps = []
+    foreach(package, invoker.deps) {
+      deps += [
+        package,
+        package + "__archive-manifest",
+      ]
+    }
 
     args = [
       rebase_path(_ids_txt),
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 77cf61c..9ca4d87 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-0.20200403.0.1
\ No newline at end of file
+0.20200403.1.1
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 51acab0f..9ca4d87 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-0.20200402.3.1
\ No newline at end of file
+0.20200403.1.1
\ No newline at end of file
diff --git a/build/fuchsia/run_package.py b/build/fuchsia/run_package.py
index 8e18a57e..e6d3978 100644
--- a/build/fuchsia/run_package.py
+++ b/build/fuchsia/run_package.py
@@ -161,18 +161,18 @@
   system_logger = (
       _AttachKernelLogReader(target) if args.system_logging else None)
   try:
-    with target.GetAmberRepo():
-      if system_logger:
-        # Spin up a thread to asynchronously dump the system log to stdout
-        # for easier diagnoses of early, pre-execution failures.
-        log_output_quit_event = multiprocessing.Event()
-        log_output_thread = threading.Thread(
-            target=
-            lambda: _DrainStreamToStdout(system_logger.stdout, log_output_quit_event)
-        )
-        log_output_thread.daemon = True
-        log_output_thread.start()
+    if system_logger:
+      # Spin up a thread to asynchronously dump the system log to stdout
+      # for easier diagnoses of early, pre-execution failures.
+      log_output_quit_event = multiprocessing.Event()
+      log_output_thread = threading.Thread(
+          target=
+          lambda: _DrainStreamToStdout(system_logger.stdout, log_output_quit_event)
+      )
+      log_output_thread.daemon = True
+      log_output_thread.start()
 
+    with target.GetAmberRepo():
       target.InstallPackage(package_paths)
 
       if system_logger:
diff --git a/cc/layers/layer.cc b/cc/layers/layer.cc
index f651c0b2..06254d8 100644
--- a/cc/layers/layer.cc
+++ b/cc/layers/layer.cc
@@ -1112,16 +1112,6 @@
   SetNeedsCommit();
 }
 
-void Layer::SetDoubleSided(bool double_sided) {
-  DCHECK(IsPropertyChangeAllowed());
-  if (inputs_.double_sided == double_sided)
-    return;
-  inputs_.double_sided = double_sided;
-  SetNeedsCommit();
-  SetPropertyTreesNeedRebuild();
-  SetSubtreePropertyChanged();
-}
-
 void Layer::SetTransformTreeIndex(int index) {
   DCHECK(IsPropertyChangeAllowed());
   if (transform_tree_index_ == index)
diff --git a/cc/layers/layer.h b/cc/layers/layer.h
index 6b7e264..f51af62 100644
--- a/cc/layers/layer.h
+++ b/cc/layers/layer.h
@@ -475,22 +475,6 @@
     return force_render_surface_for_testing_;
   }
 
-  // Set or get if this layer should continue to be visible when rotated such
-  // that its back face is facing toward the camera. If false, the layer will
-  // disappear when its back face is visible, but if true, the mirror image of
-  // its front face will be shown. For instance, with a 180deg rotation around
-  // the middle of the layer on the Y axis, if this is false then nothing is
-  // visible. But if true, the layer is seen with its contents flipped along the
-  // Y axis. Being single-sided applies transitively to the subtree of this
-  // layer. If it is hidden because of its back face being visible, then its
-  // subtree will be too (even if a subtree layer's front face would have been
-  // visible).
-  //
-  // Note that should_check_backface_visibility() is the final computed value
-  // for back face visibility, which is only for internal use.
-  void SetDoubleSided(bool double_sided);
-  bool double_sided() const { return inputs_.double_sided; }
-
   // When true the layer may contribute to the compositor's output. When false,
   // it does not. This property does not apply to children of the layer, they
   // may contribute while this layer does not. The layer itself will determine
diff --git a/cc/layers/layer_perftest.cc b/cc/layers/layer_perftest.cc
index 60f73ee4..5218685 100644
--- a/cc/layers/layer_perftest.cc
+++ b/cc/layers/layer_perftest.cc
@@ -74,7 +74,6 @@
   float transform_origin_z = 0;
   bool scrollable = true;
   bool contents_opaque = true;
-  bool double_sided = true;
   bool hide_layer_and_subtree = true;
   bool masks_to_bounds = true;
 
@@ -84,7 +83,6 @@
     test_layer->SetNeedsDisplayRect(gfx::Rect(5, 5));
     test_layer->SetTransformOrigin(gfx::Point3F(0.f, 0.f, transform_origin_z));
     test_layer->SetContentsOpaque(contents_opaque);
-    test_layer->SetDoubleSided(double_sided);
     test_layer->SetHideLayerAndSubtree(hide_layer_and_subtree);
     test_layer->SetMasksToBounds(masks_to_bounds);
     test_layer->PushPropertiesTo(impl_layer.get());
@@ -92,7 +90,6 @@
     transform_origin_z += 0.01f;
     scrollable = !scrollable;
     contents_opaque = !contents_opaque;
-    double_sided = !double_sided;
     hide_layer_and_subtree = !hide_layer_and_subtree;
     masks_to_bounds = !masks_to_bounds;
 
diff --git a/cc/layers/layer_unittest.cc b/cc/layers/layer_unittest.cc
index eb64652..c49c99b 100644
--- a/cc/layers/layer_unittest.cc
+++ b/cc/layers/layer_unittest.cc
@@ -360,14 +360,6 @@
       grand_child->PushPropertiesTo(grand_child_impl.get()));
 
   EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(1);
-  EXECUTE_AND_VERIFY_SUBTREE_CHANGED(top->SetDoubleSided(false));
-  EXECUTE_AND_VERIFY_SUBTREE_CHANGES_RESET(
-      top->PushPropertiesTo(top_impl.get());
-      child->PushPropertiesTo(child_impl.get());
-      child2->PushPropertiesTo(child2_impl.get());
-      grand_child->PushPropertiesTo(grand_child_impl.get()));
-
-  EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(1);
   EXECUTE_AND_VERIFY_SUBTREE_CHANGED(top->SetHideLayerAndSubtree(true));
   EXECUTE_AND_VERIFY_SUBTREE_CHANGES_RESET(
       top->PushPropertiesTo(top_impl.get());
@@ -937,7 +929,6 @@
       Region(gfx::Rect(1, 1, 2, 2))));
   EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetTransform(
       gfx::Transform(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)));
-  EXPECT_SET_NEEDS_COMMIT(1, test_layer->SetDoubleSided(false));
   TouchActionRegion touch_action_region;
   touch_action_region.Union(TouchAction::kNone, gfx::Rect(10, 10));
   EXPECT_SET_NEEDS_COMMIT(
diff --git a/cc/metrics/compositor_frame_reporter.cc b/cc/metrics/compositor_frame_reporter.cc
index 6859250..ef0d4a2 100644
--- a/cc/metrics/compositor_frame_reporter.cc
+++ b/cc/metrics/compositor_frame_reporter.cc
@@ -160,8 +160,10 @@
     static_cast<int>(ui::EventType::ET_LAST);
 constexpr int kEventLatencyScrollTypeCount =
     static_cast<int>(ScrollInputType::kMaxValue) + 1;
-constexpr int kMaxEventLatencyHistogramIndex =
+constexpr int kMaxEventLatencyHistogramBaseIndex =
     kEventLatencyEventTypeCount * kEventLatencyScrollTypeCount;
+constexpr int kMaxEventLatencyHistogramIndex =
+    kMaxEventLatencyHistogramBaseIndex * kStageTypeCount;
 constexpr int kEventLatencyHistogramMin = 1;
 constexpr int kEventLatencyHistogramMax = 5000000;
 constexpr int kEventLatencyHistogramBucketCount = 100;
@@ -543,7 +545,7 @@
         event_metrics.scroll_input_type()
             ? static_cast<int>(*event_metrics.scroll_input_type())
             : 0;
-    const int histogram_index =
+    const int histogram_base_index =
         event_type_index * kEventLatencyScrollTypeCount + scroll_type_index;
 
     // For scroll events, report total latency up to gpu-swap-end. This is
@@ -556,8 +558,8 @@
       const std::string swap_end_histogram_name =
           histogram_base_name + ".TotalLatencyToSwapEnd";
       STATIC_HISTOGRAM_POINTER_GROUP(
-          swap_end_histogram_name, histogram_index,
-          kMaxEventLatencyHistogramIndex,
+          swap_end_histogram_name, histogram_base_index,
+          kMaxEventLatencyHistogramBaseIndex,
           AddTimeMicrosecondsGranularity(swap_end_latency),
           base::Histogram::FactoryGet(
               swap_end_histogram_name, kEventLatencyHistogramMin,
@@ -565,53 +567,72 @@
               base::HistogramBase::kUmaTargetedHistogramFlag));
     }
 
-    base::TimeDelta total_latency =
-        frame_termination_time_ - event_metrics.time_stamp();
-    const std::string histogram_name = histogram_base_name + ".TotalLatency";
-    STATIC_HISTOGRAM_POINTER_GROUP(
-        histogram_name, histogram_index, kMaxEventLatencyHistogramIndex,
-        AddTimeMicrosecondsGranularity(total_latency),
-        base::Histogram::FactoryGet(
-            histogram_name, kEventLatencyHistogramMin,
-            kEventLatencyHistogramMax, kEventLatencyHistogramBucketCount,
-            base::HistogramBase::kUmaTargetedHistogramFlag));
-
     const auto trace_id = TRACE_ID_LOCAL(&event_metrics);
     TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1(
         "cc,input", "EventLatency", trace_id, event_metrics.time_stamp(),
         "event", event_metrics.GetTypeName());
 
-    // Report the breakdowns.
     // It is possible for an event to arrive in the compositor in the middle of
     // a frame (e.g. the browser received the event *after* renderer received a
     // begin-impl, and the event reached the compositor before that frame
     // ended). To handle such cases, find the first stage that happens after the
     // event's arrival in the browser.
-    // TODO(mohsen): Report the breakdowns in UMA too.
     size_t index = 0;
     for (; index < stage_history_.size(); ++index) {
       const auto& stage = stage_history_[index];
       if (stage.start_time > event_metrics.time_stamp()) {
+        const char stage_type_name[] = "BrowserToRendererCompositor";
+
+        const base::TimeDelta latency =
+            stage.start_time - event_metrics.time_stamp();
+        const std::string histogram_name =
+            base::StrCat({histogram_base_name, ".", stage_type_name});
+        STATIC_HISTOGRAM_POINTER_GROUP(
+            histogram_name, histogram_base_index,
+            kMaxEventLatencyHistogramBaseIndex,
+            AddTimeMicrosecondsGranularity(latency),
+            base::Histogram::FactoryGet(
+                histogram_name, kEventLatencyHistogramMin,
+                kEventLatencyHistogramMax, kEventLatencyHistogramBucketCount,
+                base::HistogramBase::kUmaTargetedHistogramFlag));
+
         TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
-            "cc,input", "BrowserToRendererCompositor", trace_id,
-            event_metrics.time_stamp());
+            "cc,input", stage_type_name, trace_id, event_metrics.time_stamp());
         TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(
-            "cc,input", "BrowserToRendererCompositor", trace_id,
-            stage.start_time);
+            "cc,input", stage_type_name, trace_id, stage.start_time);
         break;
       }
     }
 
     for (; index < stage_history_.size(); ++index) {
       const auto& stage = stage_history_[index];
-      if (stage.stage_type == StageType::kTotalLatency)
-        break;
-      TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
-          "cc,input", GetStageName(static_cast<int>(stage.stage_type)),
-          trace_id, stage.start_time);
-      TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(
-          "cc,input", GetStageName(static_cast<int>(stage.stage_type)),
-          trace_id, stage.end_time);
+
+      // Total latency is calculated since the event timestamp.
+      const base::TimeTicks start_time =
+          stage.stage_type == StageType::kTotalLatency
+              ? event_metrics.time_stamp()
+              : stage.start_time;
+      const base::TimeDelta latency = stage.end_time - start_time;
+      const int stage_type_index = static_cast<int>(stage.stage_type);
+      const char* stage_type_name = GetStageName(stage_type_index);
+      const std::string histogram_name =
+          base::StrCat({histogram_base_name, ".", stage_type_name});
+      const int histogram_index =
+          histogram_base_index * kStageTypeCount + stage_type_index;
+      STATIC_HISTOGRAM_POINTER_GROUP(
+          histogram_name, histogram_index, kMaxEventLatencyHistogramIndex,
+          AddTimeMicrosecondsGranularity(latency),
+          base::Histogram::FactoryGet(
+              histogram_name, kEventLatencyHistogramMin,
+              kEventLatencyHistogramMax, kEventLatencyHistogramBucketCount,
+              base::HistogramBase::kUmaTargetedHistogramFlag));
+
+      if (stage.stage_type != StageType::kTotalLatency) {
+        TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
+            "cc,input", stage_type_name, trace_id, stage.start_time);
+        TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(
+            "cc,input", stage_type_name, trace_id, stage.end_time);
+      }
     }
     TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(
         "cc,input", "EventLatency", trace_id, frame_termination_time_);
diff --git a/cc/metrics/compositor_frame_reporter_unittest.cc b/cc/metrics/compositor_frame_reporter_unittest.cc
index 1f4ffee..80b340d 100644
--- a/cc/metrics/compositor_frame_reporter_unittest.cc
+++ b/cc/metrics/compositor_frame_reporter_unittest.cc
@@ -39,24 +39,21 @@
   }
 
  protected:
-  void AdvanceNowByMs(int advance_ms) {
+  base::TimeTicks AdvanceNowByMs(int advance_ms) {
     now_ += base::TimeDelta::FromMicroseconds(advance_ms);
+    return now_;
   }
 
   base::TimeTicks Now() { return now_; }
 
   viz::FrameTimingDetails BuildFrameTimingDetails() {
     viz::FrameTimingDetails frame_timing_details;
-    AdvanceNowByMs(1);
-    frame_timing_details.received_compositor_frame_timestamp = Now();
-    AdvanceNowByMs(1);
-    frame_timing_details.draw_start_timestamp = Now();
-    AdvanceNowByMs(1);
-    frame_timing_details.swap_timings.swap_start = Now();
-    AdvanceNowByMs(1);
-    frame_timing_details.swap_timings.swap_end = Now();
-    AdvanceNowByMs(1);
-    frame_timing_details.presentation_feedback.timestamp = Now();
+    frame_timing_details.received_compositor_frame_timestamp =
+        AdvanceNowByMs(1);
+    frame_timing_details.draw_start_timestamp = AdvanceNowByMs(1);
+    frame_timing_details.swap_timings.swap_start = AdvanceNowByMs(1);
+    frame_timing_details.swap_timings.swap_end = AdvanceNowByMs(1);
+    frame_timing_details.presentation_feedback.timestamp = AdvanceNowByMs(1);
     return frame_timing_details;
   }
 
@@ -257,6 +254,116 @@
                                      latency_ms, 2);
 }
 
+// Tests that when a frame is presented to the user, event latency breakdown
+// metrics are reported properly.
+TEST_F(CompositorFrameReporterTest,
+       EventLatencyBreakdownsForPresentedFrameReported) {
+  base::HistogramTester histogram_tester;
+
+  const base::TimeTicks event_time = Now();
+  std::vector<EventMetrics> events_metrics = {
+      {ui::ET_TOUCH_PRESSED, event_time, base::nullopt},
+      {ui::ET_TOUCH_MOVED, event_time, base::nullopt},
+      {ui::ET_TOUCH_MOVED, event_time, base::nullopt},
+  };
+  EXPECT_THAT(events_metrics, ::testing::Each(IsWhitelisted()));
+
+  auto begin_impl_time = AdvanceNowByMs(2);
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::kBeginImplFrameToSendBeginMainFrame,
+      begin_impl_time);
+
+  auto begin_main_time = AdvanceNowByMs(3);
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::kSendBeginMainFrameToCommit,
+      begin_main_time);
+
+  auto begin_commit_time = AdvanceNowByMs(4);
+  pipeline_reporter_->StartStage(CompositorFrameReporter::StageType::kCommit,
+                                 begin_commit_time);
+
+  auto end_commit_time = AdvanceNowByMs(5);
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::kEndCommitToActivation,
+      end_commit_time);
+
+  auto begin_activation_time = AdvanceNowByMs(6);
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::kActivation, begin_activation_time);
+
+  auto end_activation_time = AdvanceNowByMs(7);
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::kEndActivateToSubmitCompositorFrame,
+      end_activation_time);
+
+  auto submit_time = AdvanceNowByMs(8);
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::
+          kSubmitCompositorFrameToPresentationCompositorFrame,
+      submit_time);
+  pipeline_reporter_->SetEventsMetrics(std::move(events_metrics));
+
+  auto presentation_time = AdvanceNowByMs(9);
+  pipeline_reporter_->TerminateFrame(
+      CompositorFrameReporter::FrameTerminationStatus::kPresentedFrame,
+      presentation_time);
+
+  pipeline_reporter_ = nullptr;
+
+  struct {
+    const char* name;
+    const base::TimeDelta latency;
+    const int count;
+  } expected_counts[] = {
+      {"EventLatency.TouchPressed.BrowserToRendererCompositor",
+       begin_impl_time - event_time, 1},
+      {"EventLatency.TouchPressed.BeginImplFrameToSendBeginMainFrame",
+       begin_main_time - begin_impl_time, 1},
+      {"EventLatency.TouchPressed.SendBeginMainFrameToCommit",
+       begin_commit_time - begin_main_time, 1},
+      {"EventLatency.TouchPressed.Commit", end_commit_time - begin_commit_time,
+       1},
+      {"EventLatency.TouchPressed.EndCommitToActivation",
+       begin_activation_time - end_commit_time, 1},
+      {"EventLatency.TouchPressed.Activation",
+       end_activation_time - begin_activation_time, 1},
+      {"EventLatency.TouchPressed.EndActivateToSubmitCompositorFrame",
+       submit_time - end_activation_time, 1},
+      {"EventLatency.TouchPressed."
+       "SubmitCompositorFrameToPresentationCompositorFrame",
+       presentation_time - submit_time, 1},
+      {"EventLatency.TouchPressed.TotalLatency", presentation_time - event_time,
+       1},
+      {"EventLatency.TouchMoved.BrowserToRendererCompositor",
+       begin_impl_time - event_time, 2},
+      {"EventLatency.TouchMoved.BeginImplFrameToSendBeginMainFrame",
+       begin_main_time - begin_impl_time, 2},
+      {"EventLatency.TouchMoved.SendBeginMainFrameToCommit",
+       begin_commit_time - begin_main_time, 2},
+      {"EventLatency.TouchMoved.Commit", end_commit_time - begin_commit_time,
+       2},
+      {"EventLatency.TouchMoved.EndCommitToActivation",
+       begin_activation_time - end_commit_time, 2},
+      {"EventLatency.TouchMoved.Activation",
+       end_activation_time - begin_activation_time, 2},
+      {"EventLatency.TouchMoved.EndActivateToSubmitCompositorFrame",
+       submit_time - end_activation_time, 2},
+      {"EventLatency.TouchMoved."
+       "SubmitCompositorFrameToPresentationCompositorFrame",
+       presentation_time - submit_time, 2},
+      {"EventLatency.TouchMoved.TotalLatency", presentation_time - event_time,
+       2},
+  };
+
+  for (const auto& expected_count : expected_counts) {
+    histogram_tester.ExpectTotalCount(expected_count.name,
+                                      expected_count.count);
+    histogram_tester.ExpectBucketCount(expected_count.name,
+                                       expected_count.latency.InMicroseconds(),
+                                       expected_count.count);
+  }
+}
+
 // Tests that when a frame is presented to the user, scroll event latency
 // metrics are reported properly.
 TEST_F(CompositorFrameReporterTest,
diff --git a/cc/trees/draw_properties_unittest.cc b/cc/trees/draw_properties_unittest.cc
index 538b330..382f4270 100644
--- a/cc/trees/draw_properties_unittest.cc
+++ b/cc/trees/draw_properties_unittest.cc
@@ -4481,10 +4481,13 @@
 
 TEST_F(DrawPropertiesTest, TransformAnimationUpdatesBackfaceVisibility) {
   LayerImpl* root = root_layer();
+  root->SetDrawsContent(true);
   LayerImpl* back_facing = AddLayer<LayerImpl>();
+  back_facing->SetDrawsContent(true);
   LayerImpl* render_surface1 = AddLayer<LayerImpl>();
+  render_surface1->SetDrawsContent(true);
   LayerImpl* render_surface2 = AddLayer<LayerImpl>();
-
+  render_surface2->SetDrawsContent(true);
   gfx::Transform rotate_about_y;
   rotate_about_y.RotateAboutYAxis(180.0);
 
@@ -4519,7 +4522,11 @@
   UpdateActiveTreeDrawProperties();
 
   EXPECT_TRUE(GetEffectNode(render_surface1)->hidden_by_backface_visibility);
+  EXPECT_EQ(gfx::Rect(), render_surface1->visible_layer_rect());
   EXPECT_TRUE(GetEffectNode(render_surface2)->hidden_by_backface_visibility);
+  EXPECT_EQ(gfx::Rect(), render_surface2->visible_layer_rect());
+
+  EXPECT_EQ(1u, GetRenderSurfaceList().size());
 
   root->layer_tree_impl()->SetTransformMutated(back_facing->element_id(),
                                                gfx::Transform());
@@ -4527,13 +4534,24 @@
                                                rotate_about_y);
   UpdateActiveTreeDrawProperties();
   EXPECT_FALSE(GetEffectNode(render_surface1)->hidden_by_backface_visibility);
+  EXPECT_EQ(gfx::Rect(0, 0, 30, 30), render_surface1->visible_layer_rect());
   EXPECT_TRUE(GetEffectNode(render_surface2)->hidden_by_backface_visibility);
+  EXPECT_EQ(gfx::Rect(), render_surface2->visible_layer_rect());
+
+  EXPECT_EQ(2u, GetRenderSurfaceList().size());
 
   root->layer_tree_impl()->SetTransformMutated(render_surface1->element_id(),
                                                rotate_about_y);
   UpdateActiveTreeDrawProperties();
   EXPECT_TRUE(GetEffectNode(render_surface1)->hidden_by_backface_visibility);
+  // Draw properties are only updated for visible layers, so this remains the
+  // cached value from last time. The expectation is commented out because
+  // this result is not required.
+  //  EXPECT_EQ(gfx::Rect(0, 0, 30, 30), render_surface1->visible_layer_rect());
   EXPECT_TRUE(GetEffectNode(render_surface2)->hidden_by_backface_visibility);
+  EXPECT_EQ(gfx::Rect(), render_surface2->visible_layer_rect());
+
+  EXPECT_EQ(1u, GetRenderSurfaceList().size());
 }
 
 TEST_F(DrawPropertiesTest, ScrollChildAndScrollParentDifferentTargets) {
@@ -6616,7 +6634,6 @@
   // A double sided render surface with backface visible should not be skipped
   ImplOf(grandchild)->set_visible_layer_rect(gfx::Rect());
   child->SetForceRenderSurfaceForTesting(true);
-  child->SetDoubleSided(true);
   child->SetTransform(rotate_back_and_translate);
   CommitAndActivate();
   EXPECT_EQ(gfx::Rect(10, 10), ImplOf(grandchild)->visible_layer_rect());
@@ -6817,12 +6834,10 @@
   child->SetBounds(gfx::Size(10, 10));
 
   gfx::Transform rotate;
-  child->SetDoubleSided(false);
   rotate.RotateAboutXAxis(180.f);
   child->SetTransform(rotate);
   CommitAndActivate();
   EXPECT_EQ(gfx::Rect(0, 0), ImplOf(child)->visible_layer_rect());
-  child->SetDoubleSided(true);
   child->SetTransform(gfx::Transform());
 
   child->SetOpacity(0.f);
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 45ed5c3..42cc0cf18 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -243,6 +243,30 @@
   }
 }
 
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class SourceIdConsistency : int {
+  kConsistent = 0,
+  kContainsInvalid = 1,
+  kNonUnique = 2,
+  kInvalidAndNonUnique = 3,
+  kMaxValue = kInvalidAndNonUnique,
+};
+
+void RecordSourceIdConsistency(bool all_valid, bool all_unique) {
+  SourceIdConsistency consistency =
+      all_unique ? (all_valid ? SourceIdConsistency::kConsistent
+                              : SourceIdConsistency::kContainsInvalid)
+                 : (all_valid ? SourceIdConsistency::kNonUnique
+                              : SourceIdConsistency::kInvalidAndNonUnique);
+
+  // TODO(crbug.com/1062764): we're sometimes seeing unexpected values for the
+  // ukm::SourceId. We'll use this histogram to track how often it happens so
+  // we can properly (de-)prioritize a fix.
+  UMA_HISTOGRAM_ENUMERATION("Event.LatencyInfo.Debug.SourceIdConsistency",
+                            consistency);
+}
+
 }  // namespace
 
 DEFINE_SCOPED_UMA_HISTOGRAM_TIMER(PendingTreeRasterDurationHistogramTimer,
@@ -2427,12 +2451,20 @@
         active_tree()->TakeForceSendMetadataRequest());
   }
 
-  if (!CommitToActiveTree()) {
+  if (!CommitToActiveTree() && !metadata.latency_info.empty()) {
     base::TimeTicks draw_time = base::TimeTicks::Now();
+
+    ukm::SourceId exemplar = metadata.latency_info.front().ukm_source_id();
+    bool all_valid = true;
+    bool all_unique = true;
     for (auto& latency : metadata.latency_info) {
       latency.AddLatencyNumberWithTimestamp(
           ui::INPUT_EVENT_LATENCY_RENDERER_SWAP_COMPONENT, draw_time);
+      all_valid &= latency.ukm_source_id() != ukm::kInvalidSourceId;
+      all_unique &= latency.ukm_source_id() == exemplar;
     }
+
+    RecordSourceIdConsistency(all_valid, all_unique);
   }
   ui::LatencyInfo::TraceIntermediateFlowEvents(
       metadata.latency_info,
diff --git a/cc/trees/layer_tree_host_unittest.cc b/cc/trees/layer_tree_host_unittest.cc
index bc6cc79..8cf85916 100644
--- a/cc/trees/layer_tree_host_unittest.cc
+++ b/cc/trees/layer_tree_host_unittest.cc
@@ -1107,7 +1107,7 @@
         root_->SetBounds(gfx::Size(20, 20));
         break;
       case 3:
-        child_->SetDoubleSided(false);
+        child_->SetOpacity(0.8f);
         break;
     }
   }
diff --git a/cc/trees/property_tree_builder.cc b/cc/trees/property_tree_builder.cc
index 6350605b..b036be2 100644
--- a/cc/trees/property_tree_builder.cc
+++ b/cc/trees/property_tree_builder.cc
@@ -470,7 +470,6 @@
   node->trilinear_filtering = layer->trilinear_filtering();
   node->has_potential_opacity_animation = has_potential_opacity_animation;
   node->has_potential_filter_animation = has_potential_filter_animation;
-  node->double_sided = layer->double_sided();
   node->subtree_hidden = layer->hide_layer_and_subtree();
   node->is_currently_animating_opacity =
       OpacityIsAnimating(mutator_host_, layer);
@@ -633,15 +632,6 @@
   layer->SetScrollTreeIndex(node_id);
 }
 
-void SetBackfaceVisibilityTransform(Layer* layer, bool created_transform_node) {
-  // A double-sided layer's backface can been shown when its visible.
-  // In addition, we need to check if (1) there might be a local 3D transform
-  // on the layer that might turn it to the backface, or (2) it is not drawn
-  // into a flattened space.
-  layer->SetShouldCheckBackfaceVisibility(!layer->double_sided() &&
-                                          created_transform_node);
-}
-
 void SetSafeOpaqueBackgroundColor(const DataForRecursion& data_from_ancestor,
                                   Layer* layer,
                                   DataForRecursion* data_for_children) {
@@ -673,7 +663,6 @@
 
   AddScrollNodeIfNeeded(data_from_parent, layer, &data_for_children);
 
-  SetBackfaceVisibilityTransform(layer, created_transform_node);
   SetSafeOpaqueBackgroundColor(data_from_parent, layer, &data_for_children);
 
   bool not_axis_aligned_since_last_clip =
diff --git a/cc/trees/property_tree_builder_unittest.cc b/cc/trees/property_tree_builder_unittest.cc
index f46440c6..af760bc 100644
--- a/cc/trees/property_tree_builder_unittest.cc
+++ b/cc/trees/property_tree_builder_unittest.cc
@@ -453,185 +453,6 @@
   EXPECT_EQ(layer_bounds_in_screen_space, gfx::RectF(11.f, 20.f, 100.f, 100.f));
 }
 
-// Verify the behavior of back-face culling when there are no preserve-3d
-// layers. Note that 3d transforms still apply in this case, but they are
-// "flattened" to each parent layer according to current W3C spec.
-TEST_F(PropertyTreeBuilderTest, BackFaceCullingWithoutPreserves3d) {
-  auto root = Layer::Create();
-  host()->SetRootLayer(root);
-  auto front_facing_child = Layer::Create();
-  root->AddChild(front_facing_child);
-  auto back_facing_child = Layer::Create();
-  root->AddChild(back_facing_child);
-  auto front_facing_surface = Layer::Create();
-  root->AddChild(front_facing_surface);
-  auto back_facing_surface = Layer::Create();
-  root->AddChild(back_facing_surface);
-  auto front_facing_child_of_front_facing_surface = Layer::Create();
-  front_facing_surface->AddChild(front_facing_child_of_front_facing_surface);
-  auto back_facing_child_of_front_facing_surface = Layer::Create();
-  front_facing_surface->AddChild(back_facing_child_of_front_facing_surface);
-  auto front_facing_child_of_back_facing_surface = Layer::Create();
-  back_facing_surface->AddChild(front_facing_child_of_back_facing_surface);
-  auto back_facing_child_of_back_facing_surface = Layer::Create();
-  back_facing_surface->AddChild(back_facing_child_of_back_facing_surface);
-
-  // Nothing is double-sided
-  front_facing_child->SetDoubleSided(false);
-  back_facing_child->SetDoubleSided(false);
-  front_facing_surface->SetDoubleSided(false);
-  back_facing_surface->SetDoubleSided(false);
-  front_facing_child_of_front_facing_surface->SetDoubleSided(false);
-  back_facing_child_of_front_facing_surface->SetDoubleSided(false);
-  front_facing_child_of_back_facing_surface->SetDoubleSided(false);
-  back_facing_child_of_back_facing_surface->SetDoubleSided(false);
-
-  // Everything draws content.
-  front_facing_child->SetIsDrawable(true);
-  back_facing_child->SetIsDrawable(true);
-  front_facing_surface->SetIsDrawable(true);
-  back_facing_surface->SetIsDrawable(true);
-  front_facing_child_of_front_facing_surface->SetIsDrawable(true);
-  back_facing_child_of_front_facing_surface->SetIsDrawable(true);
-  front_facing_child_of_back_facing_surface->SetIsDrawable(true);
-  back_facing_child_of_back_facing_surface->SetIsDrawable(true);
-
-  gfx::Transform backface_matrix;
-  backface_matrix.Translate(50.0, 50.0);
-  backface_matrix.RotateAboutYAxis(180.0);
-  backface_matrix.Translate(-50.0, -50.0);
-
-  root->SetBounds(gfx::Size(100, 100));
-  front_facing_child->SetBounds(gfx::Size(100, 100));
-  back_facing_child->SetBounds(gfx::Size(100, 100));
-  front_facing_surface->SetBounds(gfx::Size(100, 100));
-  back_facing_surface->SetBounds(gfx::Size(100, 100));
-  front_facing_child_of_front_facing_surface->SetBounds(gfx::Size(100, 100));
-  back_facing_child_of_front_facing_surface->SetBounds(gfx::Size(100, 100));
-  front_facing_child_of_back_facing_surface->SetBounds(gfx::Size(100, 100));
-  back_facing_child_of_back_facing_surface->SetBounds(gfx::Size(100, 100));
-
-  front_facing_surface->SetForceRenderSurfaceForTesting(true);
-  back_facing_surface->SetForceRenderSurfaceForTesting(true);
-
-  back_facing_child->SetTransform(backface_matrix);
-  back_facing_surface->SetTransform(backface_matrix);
-  back_facing_child_of_front_facing_surface->SetTransform(backface_matrix);
-  back_facing_child_of_back_facing_surface->SetTransform(backface_matrix);
-
-  // Note: No layers preserve 3d. According to current W3C CSS gfx::Transforms
-  // spec, these layers should blindly use their own local transforms to
-  // determine back-face culling.
-  CommitAndActivate();
-
-  // Verify which render surfaces were created.
-  EXPECT_EQ(GetRenderSurfaceImpl(front_facing_child),
-            GetRenderSurfaceImpl(root));
-  EXPECT_EQ(GetRenderSurfaceImpl(back_facing_child),
-            GetRenderSurfaceImpl(root));
-  EXPECT_NE(GetRenderSurfaceImpl(front_facing_surface),
-            GetRenderSurfaceImpl(root));
-  EXPECT_NE(GetRenderSurfaceImpl(back_facing_surface),
-            GetRenderSurfaceImpl(root));
-  EXPECT_NE(GetRenderSurfaceImpl(back_facing_surface),
-            GetRenderSurfaceImpl(front_facing_surface));
-  EXPECT_EQ(GetRenderSurfaceImpl(front_facing_child_of_front_facing_surface),
-            GetRenderSurfaceImpl(front_facing_surface));
-  EXPECT_EQ(GetRenderSurfaceImpl(back_facing_child_of_front_facing_surface),
-            GetRenderSurfaceImpl(front_facing_surface));
-  EXPECT_EQ(GetRenderSurfaceImpl(front_facing_child_of_back_facing_surface),
-            GetRenderSurfaceImpl(back_facing_surface));
-  EXPECT_EQ(GetRenderSurfaceImpl(back_facing_child_of_back_facing_surface),
-            GetRenderSurfaceImpl(back_facing_surface));
-
-  EXPECT_EQ(3u, update_layer_impl_list().size());
-  EXPECT_TRUE(UpdateLayerImplListContains(front_facing_child->id()));
-  EXPECT_TRUE(UpdateLayerImplListContains(front_facing_surface->id()));
-  EXPECT_TRUE(UpdateLayerImplListContains(
-      front_facing_child_of_front_facing_surface->id()));
-}
-
-// Verify that layers are appropriately culled when their back face is showing
-// and they are not double sided, while animations are going on.
-// Even layers that are animating get culled if their back face is showing and
-// they are not double sided.
-TEST_F(PropertyTreeBuilderTest, BackFaceCullingWithAnimatingTransforms) {
-  auto root = Layer::Create();
-  host()->SetRootLayer(root);
-  auto child = Layer::Create();
-  root->AddChild(child);
-  auto animating_surface = Layer::Create();
-  root->AddChild(animating_surface);
-  auto child_of_animating_surface = Layer::Create();
-  animating_surface->AddChild(child_of_animating_surface);
-  auto animating_child = Layer::Create();
-  root->AddChild(animating_child);
-  auto child2 = Layer::Create();
-  root->AddChild(child2);
-
-  // Nothing is double-sided
-  child->SetDoubleSided(false);
-  child2->SetDoubleSided(false);
-  animating_surface->SetDoubleSided(false);
-  child_of_animating_surface->SetDoubleSided(false);
-  animating_child->SetDoubleSided(false);
-
-  // Everything draws content.
-  child->SetIsDrawable(true);
-  child2->SetIsDrawable(true);
-  animating_surface->SetIsDrawable(true);
-  child_of_animating_surface->SetIsDrawable(true);
-  animating_child->SetIsDrawable(true);
-
-  gfx::Transform backface_matrix;
-  backface_matrix.Translate(50.0, 50.0);
-  backface_matrix.RotateAboutYAxis(180.0);
-  backface_matrix.Translate(-50.0, -50.0);
-
-  host()->SetElementIdsForTesting();
-
-  // Animate the transform on the render surface.
-  AddAnimatedTransformToElementWithAnimation(animating_surface->element_id(),
-                                             timeline(), 10.0, 30, 0);
-  // This is just an animating layer, not a surface.
-  AddAnimatedTransformToElementWithAnimation(animating_child->element_id(),
-                                             timeline(), 10.0, 30, 0);
-
-  root->SetBounds(gfx::Size(100, 100));
-  child->SetBounds(gfx::Size(100, 100));
-  child->SetTransform(backface_matrix);
-  animating_surface->SetBounds(gfx::Size(100, 100));
-  animating_surface->SetTransform(backface_matrix);
-  animating_surface->SetForceRenderSurfaceForTesting(true);
-  child_of_animating_surface->SetBounds(gfx::Size(100, 100));
-  child_of_animating_surface->SetTransform(backface_matrix);
-  animating_child->SetBounds(gfx::Size(100, 100));
-  animating_child->SetTransform(backface_matrix);
-  child2->SetBounds(gfx::Size(100, 100));
-
-  CommitAndActivate();
-
-  EXPECT_EQ(GetRenderSurfaceImpl(child), GetRenderSurfaceImpl(root));
-  EXPECT_TRUE(GetRenderSurfaceImpl(animating_surface));
-  EXPECT_EQ(GetRenderSurfaceImpl(child_of_animating_surface),
-            GetRenderSurfaceImpl(animating_surface));
-  EXPECT_EQ(GetRenderSurfaceImpl(animating_child), GetRenderSurfaceImpl(root));
-  EXPECT_EQ(GetRenderSurfaceImpl(child2), GetRenderSurfaceImpl(root));
-
-  EXPECT_EQ(1u, update_layer_impl_list().size());
-
-  // The back facing layers are culled from the layer list, and have an empty
-  // visible rect.
-  EXPECT_TRUE(UpdateLayerImplListContains(child2->id()));
-  EXPECT_TRUE(ImplOf(child)->visible_layer_rect().IsEmpty());
-  EXPECT_TRUE(ImplOf(animating_surface)->visible_layer_rect().IsEmpty());
-  EXPECT_TRUE(
-      ImplOf(child_of_animating_surface)->visible_layer_rect().IsEmpty());
-  EXPECT_TRUE(ImplOf(animating_child)->visible_layer_rect().IsEmpty());
-
-  EXPECT_EQ(gfx::Rect(100, 100), ImplOf(child2)->visible_layer_rect());
-}
-
 // Verify that having animated opacity but current opacity 1 still creates
 // a render surface.
 TEST_F(PropertyTreeBuilderTest, AnimatedOpacityCreatesRenderSurface) {
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 95b13fb..d966018 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -1302,6 +1302,7 @@
   "java/src/org/chromium/chrome/browser/payments/PaymentAppFactoryInterface.java",
   "java/src/org/chromium/chrome/browser/payments/PaymentAppFactoryParams.java",
   "java/src/org/chromium/chrome/browser/payments/PaymentAppService.java",
+  "java/src/org/chromium/chrome/browser/payments/PaymentAppServiceBridge.java",
   "java/src/org/chromium/chrome/browser/payments/PaymentManifestVerifier.java",
   "java/src/org/chromium/chrome/browser/payments/PaymentManifestWebDataService.java",
   "java/src/org/chromium/chrome/browser/payments/PaymentPreferencesUtil.java",
diff --git a/chrome/android/chrome_junit_test_java_sources.gni b/chrome/android/chrome_junit_test_java_sources.gni
index 011f399..f5e6276f6 100644
--- a/chrome/android/chrome_junit_test_java_sources.gni
+++ b/chrome/android/chrome_junit_test_java_sources.gni
@@ -184,6 +184,7 @@
   "junit/src/org/chromium/chrome/browser/password_manager/settings/TimedCallbackDelayerTest.java",
   "junit/src/org/chromium/chrome/browser/payments/AutofillContactTest.java",
   "junit/src/org/chromium/chrome/browser/payments/AutofillContactUnitTest.java",
+  "junit/src/org/chromium/chrome/browser/photo_picker/FileEnumWorkerTaskTest.java",
   "junit/src/org/chromium/chrome/browser/photo_picker/PickerBitmapViewTest.java",
   "junit/src/org/chromium/chrome/browser/preferences/PrefServiceBridgeTest.java",
   "junit/src/org/chromium/chrome/browser/privacy/settings/PrivacyPreferencesManagerTest.java",
diff --git a/chrome/android/features/cablev2_authenticator/config.gni b/chrome/android/features/cablev2_authenticator/config.gni
index 3e91dd8..1868223 100644
--- a/chrome/android/features/cablev2_authenticator/config.gni
+++ b/chrome/android/features/cablev2_authenticator/config.gni
@@ -5,5 +5,6 @@
 import("//build/config/android/channel.gni")
 
 declare_args() {
-  enable_android_cablev2_authenticator = false
+  enable_android_cablev2_authenticator =
+      android_channel == "default" || android_channel == "canary"
 }
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
index a21cc37f..b83d375 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
@@ -35,6 +35,8 @@
 import org.chromium.chrome.browser.feed.library.api.host.stream.StreamConfiguration;
 import org.chromium.chrome.browser.feed.library.api.host.stream.TooltipApi;
 import org.chromium.chrome.browser.feed.tooltip.BasicTooltipApi;
+import org.chromium.chrome.browser.feed.v2.FeedStreamSurface;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.native_page.ContextMenuManager;
 import org.chromium.chrome.browser.ntp.NewTabPageLayout;
 import org.chromium.chrome.browser.ntp.SnapScrollHelper;
@@ -63,6 +65,7 @@
     private final int mDefaultMargin;
     private final int mWideMargin;
     private final FeedSurfaceMediator mMediator;
+    private FeedStreamSurface mFeedStreamSurface;
 
     private UiConfig mUiConfig;
     private FrameLayout mRootView;
@@ -292,6 +295,12 @@
 
         // Mediator should be created before any Stream changes.
         mMediator = new FeedSurfaceMediator(this, snapScrollHelper);
+
+        // Native should already have been loaded because of FeedSurfaceMediator.
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.INTEREST_FEED_V2)) {
+            // TODO(iwells): Temporary. This should probably move to FeedSurfaceMediator.
+            mFeedStreamSurface = new FeedStreamSurface(mActivity);
+        }
     }
 
     public void destroy() {
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/library/piet/AdapterParameters.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/library/piet/AdapterParameters.java
index abbe9b44..d7effb3 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/library/piet/AdapterParameters.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/library/piet/AdapterParameters.java
@@ -42,7 +42,7 @@
     // Doesn't like passing "this" to the new ElementAdapterFactory; however, nothing in the
     // factory's construction will reference the elementAdapterFactory member of this, so should be
     // safe.
-    @SuppressWarnings("initialization")
+    @SuppressWarnings("nullness")
     public AdapterParameters(Context context, Supplier<ViewGroup> parentViewSupplier,
             HostProviders hostProviders, Clock clock, boolean allowLegacyRoundedCornerImpl,
             boolean allowOutlineRoundedCornerImpl) {
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/library/piet/ui/BorderDrawable.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/library/piet/ui/BorderDrawable.java
index c4e564c..9f62c65 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/library/piet/ui/BorderDrawable.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/library/piet/ui/BorderDrawable.java
@@ -34,7 +34,7 @@
     }
 
     // Doesn't like calls to getPaint()
-    @SuppressWarnings("initialization")
+    @SuppressWarnings("nullness")
     public BorderDrawable(Context context, Borders borders, float[] cornerRadii, boolean isRtL,
             int width, int height) {
         super(new RoundRectShape(cornerRadii, null, null));
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/library/piet/ui/GradientDrawable.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/library/piet/ui/GradientDrawable.java
index db6bc54..c9d4cef4 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/library/piet/ui/GradientDrawable.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/library/piet/ui/GradientDrawable.java
@@ -13,7 +13,7 @@
 /** Class to display gradients according to the Piet spec (angle + color stops at %ages) */
 public class GradientDrawable extends ShapeDrawable {
     // Doesn't like the call to setShape and setShaderFactory
-    @SuppressWarnings("initialization")
+    @SuppressWarnings("nullness")
     public GradientDrawable(LinearGradient gradient, Supplier<Boolean> rtlSupplier) {
         super();
 
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/library/piet/ui/RoundedCornerWrapperView.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/library/piet/ui/RoundedCornerWrapperView.java
index ac25191d..2db04a5 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/library/piet/ui/RoundedCornerWrapperView.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/library/piet/ui/RoundedCornerWrapperView.java
@@ -74,7 +74,7 @@
     private int mRoundedCornerRadius;
 
     // Doesn't like the call to setOutlineProvider
-    @SuppressWarnings("initialization")
+    @SuppressWarnings("nullness")
     public RoundedCornerWrapperView(Context context, RoundedCorners roundedCorners,
             RoundedCornerMaskCache maskCache, Supplier<Boolean> isRtLSupplier, int radiusOverride,
             Borders borders, boolean allowClipPath, boolean allowOutlineRounding) {
diff --git a/chrome/android/feed/merging.md b/chrome/android/feed/merging.md
index 47c8dc20..daa4998 100644
--- a/chrome/android/feed/merging.md
+++ b/chrome/android/feed/merging.md
@@ -8,4 +8,4 @@
 The hash below represents the last commit from that repo that was reviewed for
 the potential need to merge here.
 
-Last checked commit ID: a9662901062a748b7f018e5ef194f13708546292
\ No newline at end of file
+Last checked commit ID: f1bd7d30fd0bbb9fb9c8d7c0d99432be45330179
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/BrowserServicesIntentDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/BrowserServicesIntentDataProvider.java
index 94aa704b..7fd4722 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/BrowserServicesIntentDataProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/BrowserServicesIntentDataProvider.java
@@ -20,6 +20,7 @@
 import androidx.browser.trusted.sharing.ShareTarget;
 
 import org.chromium.chrome.browser.customtabs.CustomButtonParams;
+import org.chromium.chrome.browser.flags.ActivityType;
 import org.chromium.chrome.browser.webapps.WebApkExtras;
 import org.chromium.chrome.browser.webapps.WebappExtras;
 
@@ -29,9 +30,9 @@
 import java.util.List;
 
 /**
- * Interface for model classes which parses incoming intent for customization data.
+ * Base class for model classes which parse incoming intent for customization data.
  */
-public class BrowserServicesIntentDataProvider {
+public abstract class BrowserServicesIntentDataProvider {
     // The type of UI for Custom Tab to use.
     @IntDef({CustomTabsUiType.DEFAULT, CustomTabsUiType.MEDIA_VIEWER,
             CustomTabsUiType.PAYMENT_REQUEST, CustomTabsUiType.INFO_PAGE,
@@ -49,6 +50,11 @@
     }
 
     /**
+     * @return The type of the Activity;
+     */
+    public abstract @ActivityType int getActivityType();
+
+    /**
      * @return the Intent this instance was created with.
      */
     @Nullable
@@ -299,22 +305,23 @@
     /**
      * @return Whether the Activity should attempt to display a Trusted Web Activity.
      */
-    public boolean isTrustedWebActivity() {
-        return false;
+    public final boolean isTrustedWebActivity() {
+        return getActivityType() == ActivityType.TRUSTED_WEB_ACTIVITY;
     }
 
     /**
      * @return Whether the Activity is either a Webapp or a WebAPK activity.
      */
-    public boolean isWebappOrWebApkActivity() {
-        return false;
+    public final boolean isWebappOrWebApkActivity() {
+        return getActivityType() == ActivityType.WEBAPP
+                || getActivityType() == ActivityType.WEB_APK;
     }
 
     /**
      * @return Whether the Activity is a WebAPK activity.
      */
-    public boolean isWebApkActivity() {
-        return false;
+    public final boolean isWebApkActivity() {
+        return getActivityType() == ActivityType.WEB_APK;
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
index ec7de0a..f81fbb7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
@@ -115,6 +115,12 @@
     }
 
     @Override
+    @ActivityType
+    public int getActivityType() {
+        return getIntentDataProvider().getActivityType();
+    }
+
+    @Override
     public void initializeCompositor() {
         super.initializeCompositor();
         getTabModelSelector().onNativeLibraryReady(getTabContentManager());
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
index ed3c0511..4c5f70d2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
@@ -45,7 +45,6 @@
 import org.chromium.chrome.browser.customtabs.features.CustomTabNavigationBarController;
 import org.chromium.chrome.browser.dependency_injection.ChromeActivityCommonsModule;
 import org.chromium.chrome.browser.firstrun.FirstRunSignInProcessor;
-import org.chromium.chrome.browser.flags.ActivityType;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.infobar.InfoBarContainer;
 import org.chromium.chrome.browser.night_mode.NightModeStateProvider;
@@ -103,13 +102,6 @@
     }
 
     @Override
-    @ActivityType
-    public int getActivityType() {
-        return mIntentDataProvider.isTrustedWebActivity()
-                ? ActivityType.TRUSTED_WEB_ACTIVITY : ActivityType.CUSTOM_TAB;
-    }
-
-    @Override
     public void performPreInflationStartup() {
         // Parse the data from the Intent before calling super to allow the Intent to customize
         // the Activity parameters, including the background of the page.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
index 1b6df1d..afe00b34 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
@@ -43,6 +43,7 @@
 import org.chromium.chrome.browser.ChromeVersionInfo;
 import org.chromium.chrome.browser.IntentHandler;
 import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider;
+import org.chromium.chrome.browser.flags.ActivityType;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.components.browser_ui.styles.ChromeColors;
 import org.chromium.components.browser_ui.widget.TintedDrawable;
@@ -165,7 +166,7 @@
     private final int mInitialBackgroundColor;
     private final boolean mDisableStar;
     private final boolean mDisableDownload;
-    private final boolean mIsTrustedWebActivity;
+    private final @ActivityType int mActivityType;
     @Nullable
     private final Integer mNavigationBarColor;
     private final boolean mIsIncognito;
@@ -306,8 +307,10 @@
             }
         }
 
-        mIsTrustedWebActivity = IntentUtils.safeGetBooleanExtra(
-                intent, TrustedWebUtils.EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY, false);
+        mActivityType = IntentUtils.safeGetBooleanExtra(
+                                intent, TrustedWebUtils.EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY, false)
+                ? ActivityType.TRUSTED_WEB_ACTIVITY
+                : ActivityType.CUSTOM_TAB;
         mTrustedWebActivityAdditionalOrigins = IntentUtils.safeGetStringArrayListExtra(intent,
                 TrustedWebActivityIntentBuilder.EXTRA_ADDITIONAL_TRUSTED_ORIGINS);
         mTrustedWebActivityDisplayMode = resolveTwaDisplayMode();
@@ -529,6 +532,11 @@
     }
 
     @Override
+    public @ActivityType int getActivityType() {
+        return mActivityType;
+    }
+
+    @Override
     @Nullable
     public Intent getIntent() {
         return mIntent;
@@ -728,11 +736,6 @@
         return mIsIncognito;
     }
 
-    @Override
-    public boolean isTrustedWebActivity() {
-        return mIsTrustedWebActivity;
-    }
-
     @Nullable
     @Override
     public TrustedWebActivityDisplayMode getTwaDisplayMode() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabIntentHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabIntentHandler.java
index baa3270..94c2d14 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabIntentHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabIntentHandler.java
@@ -15,6 +15,7 @@
 
 import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider;
 import org.chromium.chrome.browser.dependency_injection.ActivityScope;
+import org.chromium.chrome.browser.flags.ActivityType;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.net.NetworkChangeNotifier;
 
@@ -73,8 +74,7 @@
         runWhenTabCreated(() -> {
             if (mTabProvider.getInitialTabCreationMode() != TabCreationMode.RESTORED) {
                 mHandlingStrategy.handleInitialIntent(mIntentDataProvider);
-            } else if (mIntentDataProvider.isWebappOrWebApkActivity()
-                    && !mIntentDataProvider.isWebApkActivity()
+            } else if (mIntentDataProvider.getActivityType() == ActivityType.WEBAPP
                     && NetworkChangeNotifier.isOnline()) {
                 mTabProvider.getTab().reloadIgnoringCache();
             }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dependency_injection/BaseCustomTabActivityModule.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dependency_injection/BaseCustomTabActivityModule.java
index 39ed858..660c28d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dependency_injection/BaseCustomTabActivityModule.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dependency_injection/BaseCustomTabActivityModule.java
@@ -11,6 +11,7 @@
 import org.chromium.chrome.browser.customtabs.content.CustomTabIntentHandler.IntentIgnoringCriterion;
 import org.chromium.chrome.browser.customtabs.content.CustomTabIntentHandlingStrategy;
 import org.chromium.chrome.browser.customtabs.content.DefaultCustomTabIntentHandlingStrategy;
+import org.chromium.chrome.browser.flags.ActivityType;
 import org.chromium.chrome.browser.webapps.AddToHomescreenVerifier;
 import org.chromium.chrome.browser.webapps.WebApkPostShareTargetNavigator;
 import org.chromium.chrome.browser.webapps.WebApkVerifier;
@@ -26,11 +27,13 @@
 @Module
 public class BaseCustomTabActivityModule {
     private final BrowserServicesIntentDataProvider mIntentDataProvider;
+    private final @ActivityType int mActivityType;
     private final IntentIgnoringCriterion mIntentIgnoringCriterion;
 
     public BaseCustomTabActivityModule(BrowserServicesIntentDataProvider intentDataProvider,
             IntentIgnoringCriterion intentIgnoringCriterion) {
         mIntentDataProvider = intentDataProvider;
+        mActivityType = intentDataProvider.getActivityType();
         mIntentIgnoringCriterion = intentIgnoringCriterion;
     }
 
@@ -43,8 +46,8 @@
     public CustomTabIntentHandlingStrategy provideIntentHandler(
             Lazy<DefaultCustomTabIntentHandlingStrategy> defaultHandler,
             Lazy<TwaIntentHandlingStrategy> twaHandler) {
-        return (mIntentDataProvider.isTrustedWebActivity()
-                       || mIntentDataProvider.isWebApkActivity())
+        return (mActivityType == ActivityType.TRUSTED_WEB_ACTIVITY
+                       || mActivityType == ActivityType.WEB_APK)
                 ? twaHandler.get()
                 : defaultHandler.get();
     }
@@ -52,10 +55,10 @@
     @Provides
     public Verifier provideVerifier(Lazy<WebApkVerifier> webApkVerifier,
             Lazy<AddToHomescreenVerifier> addToHomescreenVerifier, Lazy<TwaVerifier> twaVerifier) {
-        if (mIntentDataProvider.isWebApkActivity()) {
+        if (mActivityType == ActivityType.WEB_APK) {
             return webApkVerifier.get();
         }
-        if (mIntentDataProvider.isWebappOrWebApkActivity()) {
+        if (mActivityType == ActivityType.WEBAPP) {
             return addToHomescreenVerifier.get();
         }
         return twaVerifier.get();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentAppServiceBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentAppServiceBridge.java
new file mode 100644
index 0000000..55ff3c7
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentAppServiceBridge.java
@@ -0,0 +1,75 @@
+// Copyright 2020 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.payments;
+
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.NativeMethods;
+import org.chromium.content_public.browser.RenderFrameHost;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Native bridge for finding payment apps.
+ */
+public class PaymentAppServiceBridge implements PaymentAppFactoryInterface {
+    /* package */ PaymentAppServiceBridge() {}
+
+    // PaymentAppFactoryInterface implementation.
+    @Override
+    public void create(PaymentAppFactoryDelegate delegate) {
+        assert (delegate.getParams().getRenderFrameHost().getLastCommittedURL().equals(
+                delegate.getParams().getPaymentRequestOrigin()));
+        PaymentAppServiceCallback callback = new PaymentAppServiceCallback(delegate);
+        PaymentAppServiceBridgeJni.get().create(delegate.getParams().getRenderFrameHost(),
+                delegate.getParams().getTopLevelOrigin(),
+                delegate.getParams()
+                        .getMethodData()
+                        .values()
+                        .stream()
+                        .map(data -> data.serialize())
+                        .toArray(ByteBuffer[] ::new),
+                delegate.getParams().getMayCrawl(), callback);
+    }
+
+    /** Handles callbacks from native PaymentAppService and creates PaymentApps. */
+    public class PaymentAppServiceCallback {
+        private final PaymentAppFactoryDelegate mDelegate;
+        private boolean mCanMakePayment;
+
+        private PaymentAppServiceCallback(PaymentAppFactoryDelegate delegate) {
+            mDelegate = delegate;
+            mCanMakePayment = false;
+        }
+
+        @CalledByNative("PaymentAppServiceCallback")
+        private void onPaymentAppCreated() {
+            ThreadUtils.assertOnUiThread();
+            mCanMakePayment = true;
+            // TODO(crbug.com/1063118): call mDelegate.onPaymentAppCreated().
+        }
+
+        @CalledByNative("PaymentAppServiceCallback")
+        private void onPaymentAppCreationError(String errorMessage) {
+            ThreadUtils.assertOnUiThread();
+            mDelegate.onPaymentAppCreationError(errorMessage);
+        }
+
+        // Expect to be called exactly once
+        @CalledByNative("PaymentAppServiceCallback")
+        private void onDoneCreatingPaymentApps() {
+            ThreadUtils.assertOnUiThread();
+            mDelegate.onCanMakePaymentCalculated(mCanMakePayment);
+            mDelegate.onDoneCreatingPaymentApps(PaymentAppServiceBridge.this);
+        }
+    }
+
+    @NativeMethods
+    /* package */ interface Natives {
+        void create(RenderFrameHost initiatorRenderFrameHost, String topOrigin,
+                ByteBuffer[] methodData, boolean mayCrawlForInstallablePaymentApps,
+                PaymentAppServiceCallback callback);
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/FileEnumWorkerTask.java b/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/FileEnumWorkerTask.java
index cb6a3d50..272b139 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/FileEnumWorkerTask.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/FileEnumWorkerTask.java
@@ -101,7 +101,7 @@
      */
     @Override
     protected List<PickerBitmap> doInBackground() {
-        assert !ThreadUtils.runningOnUiThread();
+        ThreadUtils.assertOnBackgroundThread();
 
         if (isCancelled()) return null;
 
@@ -158,8 +158,7 @@
 
         Uri contentUri = MediaStore.Files.getContentUri("external");
         Cursor imageCursor =
-                mContentResolver.query(contentUri, selectColumns, whereClause, whereArgs, orderBy);
-
+                createImageCursor(contentUri, selectColumns, whereClause, whereArgs, orderBy);
         if (imageCursor == null) {
             Log.e(TAG, "Content Resolver query() returned null");
             return null;
@@ -190,18 +189,19 @@
         imageCursor.close();
 
         pickerBitmaps.add(0, new PickerBitmap(null, 0, PickerBitmap.TileTypes.GALLERY));
-        boolean hasCameraAppAvailable =
-                mWindowAndroid.canResolveActivity(new Intent(MediaStore.ACTION_IMAGE_CAPTURE));
-        boolean hasOrCanRequestCameraPermission =
-                mWindowAndroid.hasPermission(Manifest.permission.CAMERA)
-                || mWindowAndroid.canRequestPermission(Manifest.permission.CAMERA);
-        if (hasCameraAppAvailable && hasOrCanRequestCameraPermission) {
+        if (shouldShowCameraTile()) {
             pickerBitmaps.add(0, new PickerBitmap(null, 0, PickerBitmap.TileTypes.CAMERA));
         }
 
         return pickerBitmaps;
     }
 
+    @Override
+    protected void onCancelled() {
+        super.onCancelled();
+        mCallback.filesEnumeratedCallback(null);
+    }
+
     /**
      * Communicates the results back to the client. Called on the UI thread.
      * @param files The resulting list of files on disk.
@@ -214,4 +214,25 @@
 
         mCallback.filesEnumeratedCallback(files);
     }
+
+    /**
+     * Creates a cursor containing the image files to show. Can be overridden in tests to provide
+     * fake data.
+     */
+    protected Cursor createImageCursor(Uri contentUri, String[] selectColumns, String whereClause,
+            String[] whereArgs, String orderBy) {
+        return mContentResolver.query(contentUri, selectColumns, whereClause, whereArgs, orderBy);
+    }
+
+    /**
+     * Returns whether to include the Camera tile also.
+     */
+    protected boolean shouldShowCameraTile() {
+        boolean hasCameraAppAvailable =
+                mWindowAndroid.canResolveActivity(new Intent(MediaStore.ACTION_IMAGE_CAPTURE));
+        boolean hasOrCanRequestCameraPermission =
+                (mWindowAndroid.hasPermission(Manifest.permission.CAMERA)
+                        || mWindowAndroid.canRequestPermission(Manifest.permission.CAMERA));
+        return hasCameraAppAvailable && hasOrCanRequestCameraPermission;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/PickerBitmap.java b/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/PickerBitmap.java
index ecf0c52..8089bc3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/PickerBitmap.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/PickerBitmap.java
@@ -7,6 +7,7 @@
 import android.net.Uri;
 
 import androidx.annotation.IntDef;
+import androidx.annotation.VisibleForTesting;
 
 import org.chromium.base.ApiCompatibilityUtils;
 
@@ -102,4 +103,13 @@
     public int compareTo(PickerBitmap other) {
         return ApiCompatibilityUtils.compareLong(other.mLastModified, mLastModified);
     }
+
+    /**
+     * Accessor for the last modified date (for testing use only).
+     * @return The last modified date.
+     */
+    @VisibleForTesting
+    public long getLastModifiedForTesting() {
+        return mLastModified;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorMediator.java
index 5ba51928..bbc61aeb6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorMediator.java
@@ -15,6 +15,7 @@
 
 import androidx.annotation.ColorInt;
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
@@ -38,6 +39,7 @@
     private Supplier<Boolean> mCanAnimateNativeBrowserControls;
 
     private int mIndicatorHeight;
+    private int mJavaLayoutHeight;
     private boolean mIsHiding;
 
     /**
@@ -75,9 +77,11 @@
     @Override
     public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
             int oldTop, int oldRight, int oldBottom) {
-        if (mIndicatorHeight == v.getHeight()) return;
+        // Wait for first valid height while showing indicator.
+        if (mIsHiding || mJavaLayoutHeight != 0 || v.getHeight() <= 0) return;
 
-        heightChanged(v.getHeight());
+        mJavaLayoutHeight = v.getHeight();
+        updateVisibility(false);
     }
 
     void addObserver(StatusIndicatorCoordinator.StatusIndicatorObserver observer) {
@@ -293,7 +297,7 @@
         animatorSet.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                heightChanged(0);
+                updateVisibility(true);
             }
         });
         animatorSet.start();
@@ -315,14 +319,16 @@
 
     // Other internal methods
 
-    private void heightChanged(int newHeight) {
-        mIndicatorHeight = newHeight;
+    /**
+     * Call to kick off height change when status indicator is shown/hidden.
+     * @param hiding Whether the status indicator is hiding.
+     */
+    private void updateVisibility(boolean hiding) {
+        mIsHiding = hiding;
+        mIndicatorHeight = hiding ? 0 : mJavaLayoutHeight;
 
-        if (mIndicatorHeight > 0) {
+        if (!mIsHiding) {
             mFullscreenManager.addListener(this);
-            mIsHiding = false;
-        } else {
-            mIsHiding = true;
         }
 
         // If the browser controls won't be animating, we can pretend that the animation ended.
@@ -330,7 +336,7 @@
             onOffsetChanged(mIndicatorHeight);
         }
 
-        notifyHeightChange(newHeight);
+        notifyHeightChange(mIndicatorHeight);
     }
 
     private void onOffsetChanged(int topControlsMinHeightOffset) {
@@ -353,6 +359,12 @@
         if (doneHiding) {
             mFullscreenManager.removeListener(this);
             mIsHiding = false;
+            mJavaLayoutHeight = 0;
         }
     }
+
+    @VisibleForTesting
+    void updateVisibilityForTesting(boolean hiding) {
+        updateVisibility(hiding);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/CustomTabToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/CustomTabToolbar.java
index 0ff5970..e6883bf 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/CustomTabToolbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/CustomTabToolbar.java
@@ -200,7 +200,8 @@
         mCloseButton = findViewById(R.id.close_button);
         mCloseButton.setOnLongClickListener(this);
         mMenuButton = findViewById(R.id.menu_button);
-        mAnimDelegate = new CustomTabToolbarAnimationDelegate(mSecurityButton, mTitleUrlContainer);
+        mAnimDelegate = new CustomTabToolbarAnimationDelegate(
+                mSecurityButton, mTitleUrlContainer, R.dimen.location_bar_icon_width);
     }
 
     @Override
@@ -768,18 +769,12 @@
 
             int securityIconResource = getToolbarDataProvider().getSecurityIconResource(
                     DeviceFormFactor.isNonMultiDisplayContextOnTablet(getContext()));
-            if (securityIconResource == 0) {
-                // Hide the button if we don't have an actual icon to display.
-                mSecurityButton.setImageDrawable(null);
-                mAnimDelegate.hideSecurityButton();
-            } else {
-                // ImageView#setImageResource is no-op if given resource is the current one.
-                mSecurityButton.setImageResource(securityIconResource);
+            if (securityIconResource != 0) {
                 ColorStateList colorStateList = AppCompatResources.getColorStateList(
                         getContext(), getToolbarDataProvider().getSecurityIconColorStateList());
                 ApiCompatibilityUtils.setImageTintList(mSecurityButton, colorStateList);
-                mAnimDelegate.showSecurityButton();
             }
+            mAnimDelegate.updateSecurityButton(securityIconResource);
 
             int contentDescriptionId =
                     getToolbarDataProvider().getSecurityIconContentDescriptionResourceId();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/CustomTabToolbarAnimationDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/CustomTabToolbarAnimationDelegate.java
index 3543f71b..84f891a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/CustomTabToolbarAnimationDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/CustomTabToolbarAnimationDelegate.java
@@ -6,15 +6,16 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.util.TypedValue;
 import android.view.View;
+import android.widget.ImageButton;
 import android.widget.TextView;
 
+import androidx.annotation.DimenRes;
+
 import org.chromium.chrome.R;
-import org.chromium.components.browser_ui.widget.animation.CancelAwareAnimatorListener;
+import org.chromium.components.omnibox.SecurityButtonAnimationDelegate;
 import org.chromium.ui.interpolators.BakedBezierInterpolator;
 
 /**
@@ -30,63 +31,23 @@
  * its new position.
  */
 class CustomTabToolbarAnimationDelegate {
-    private static final int CUSTOM_TAB_TOOLBAR_SLIDE_DURATION_MS = 200;
-    private static final int CUSTOM_TAB_TOOLBAR_FADE_DURATION_MS = 150;
-
-    private final View mSecurityButton;
-    private final View mTitleUrlContainer;
-    private final AnimatorSet mSecurityButtonShowAnimator;
-    private final AnimatorSet mSecurityButtonHideAnimator;
+    private final SecurityButtonAnimationDelegate mSecurityButtonAnimationDelegate;
 
     private TextView mUrlBar;
     private TextView mTitleBar;
-    private int mSecurityButtonWidth;
     // A flag controlling whether the animation has run before.
     private boolean mShouldRunTitleAnimation;
 
     /**
      * Constructs an instance of {@link CustomTabToolbarAnimationDelegate}.
      */
-    CustomTabToolbarAnimationDelegate(View securityButton, final View titleUrlContainer) {
-        mSecurityButton = securityButton;
-        mTitleUrlContainer = titleUrlContainer;
-        mSecurityButtonWidth = securityButton.getResources().getDimensionPixelSize(
-                R.dimen.location_bar_icon_width);
-
-        titleUrlContainer.setTranslationX(-mSecurityButtonWidth);
-
-        mSecurityButtonShowAnimator = new AnimatorSet();
-        Animator translateRight = ObjectAnimator.ofFloat(titleUrlContainer, View.TRANSLATION_X, 0);
-        translateRight.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE);
-        translateRight.setDuration(CUSTOM_TAB_TOOLBAR_SLIDE_DURATION_MS);
-
-        Animator fadeIn = ObjectAnimator.ofFloat(mSecurityButton, View.ALPHA, 1);
-        fadeIn.setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE);
-        fadeIn.setDuration(CUSTOM_TAB_TOOLBAR_FADE_DURATION_MS);
-        fadeIn.addListener(new CancelAwareAnimatorListener() {
-            @Override
-            public void onStart(Animator animation) {
-                mSecurityButton.setVisibility(View.VISIBLE);
-            }
-        });
-        mSecurityButtonShowAnimator.playSequentially(translateRight, fadeIn);
-
-        mSecurityButtonHideAnimator = new AnimatorSet();
-        Animator fadeOut = ObjectAnimator.ofFloat(mSecurityButton, View.ALPHA, 0);
-        fadeOut.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE);
-        fadeOut.setDuration(CUSTOM_TAB_TOOLBAR_FADE_DURATION_MS);
-        fadeOut.addListener(new CancelAwareAnimatorListener() {
-            @Override
-            public void onEnd(Animator animation) {
-                mSecurityButton.setVisibility(View.INVISIBLE);
-            }
-        });
-
-        Animator translateLeft = ObjectAnimator.ofFloat(
-                titleUrlContainer, View.TRANSLATION_X, -mSecurityButtonWidth);
-        translateLeft.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE);
-        translateLeft.setDuration(CUSTOM_TAB_TOOLBAR_SLIDE_DURATION_MS);
-        mSecurityButtonHideAnimator.playSequentially(fadeOut, translateLeft);
+    CustomTabToolbarAnimationDelegate(ImageButton securityButton, final View titleUrlContainer,
+            @DimenRes int securityStatusIconSize) {
+        int securityButtonWidth =
+                securityButton.getResources().getDimensionPixelSize(securityStatusIconSize);
+        titleUrlContainer.setTranslationX(-securityButtonWidth);
+        mSecurityButtonAnimationDelegate = new SecurityButtonAnimationDelegate(
+                securityButton, titleUrlContainer, securityStatusIconSize);
     }
 
     /**
@@ -147,7 +108,7 @@
                         .scaleY(1f)
                         .translationX(0)
                         .translationY(0)
-                        .setDuration(CUSTOM_TAB_TOOLBAR_SLIDE_DURATION_MS)
+                        .setDuration(SecurityButtonAnimationDelegate.SLIDE_DURATION_MS)
                         .setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE)
                         .setListener(new AnimatorListenerAdapter() {
                             @Override
@@ -155,7 +116,8 @@
                                 mTitleBar.animate()
                                         .alpha(1f)
                                         .setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE)
-                                        .setDuration(CUSTOM_TAB_TOOLBAR_FADE_DURATION_MS)
+                                        .setDuration(
+                                                SecurityButtonAnimationDelegate.FADE_DURATION_MS)
                                         .start();
                             }
                         })
@@ -165,26 +127,11 @@
     }
 
     /**
-     * Starts the animation to show the security button.
+     * Starts the animation to show/hide the security button,
+     * @param securityIconResource  The updated resource to be assigned to the security status icon.
+     * When this is null, the icon is animated to the left and faded out.
      */
-    void showSecurityButton() {
-        if (mSecurityButtonHideAnimator.isStarted()) mSecurityButtonHideAnimator.cancel();
-        if (mSecurityButtonShowAnimator.isStarted()
-                || mSecurityButton.getVisibility() == View.VISIBLE) {
-            return;
-        }
-        mSecurityButtonShowAnimator.start();
-    }
-
-    /**
-     * Starts the animation to hide the security button.
-     */
-    void hideSecurityButton() {
-        if (mSecurityButtonShowAnimator.isStarted()) mSecurityButtonShowAnimator.cancel();
-        if (mSecurityButtonHideAnimator.isStarted()
-                || mTitleUrlContainer.getTranslationX() == -mSecurityButtonWidth) {
-            return;
-        }
-        mSecurityButtonHideAnimator.start();
+    void updateSecurityButton(int securityIconResource) {
+        mSecurityButtonAnimationDelegate.updateSecurityButton(securityIconResource);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivity.java
index 7157df9..a82a375 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivity.java
@@ -4,48 +4,9 @@
 
 package org.chromium.chrome.browser.webapps;
 
-import android.content.Intent;
-
-import org.chromium.base.IntentUtils;
-import org.chromium.chrome.browser.flags.ActivityType;
-import org.chromium.webapk.lib.common.WebApkConstants;
-
 /**
  * An Activity is designed for WebAPKs (native Android apps) and displays a webapp in a nearly
  * UI-less Chrome.
  */
 public class WebApkActivity extends WebappActivity {
-    private static final String TAG = "WebApkActivity";
-
-    @Override
-    protected WebappInfo createWebappInfo(Intent intent) {
-        return (intent == null) ? WebApkInfo.createEmpty() : WebApkInfo.create(intent);
-    }
-
-    @Override
-    public boolean shouldPreferLightweightFre(Intent intent) {
-        // We cannot use getWebApkPackageName() because
-        // {@link WebappActivity#performPreInflationStartup()} may not have been called yet.
-        String webApkPackageName =
-                IntentUtils.safeGetStringExtra(intent, WebApkConstants.EXTRA_WEBAPK_PACKAGE_NAME);
-
-        // Use the lightweight FRE for unbound WebAPKs.
-        return webApkPackageName != null
-                && !webApkPackageName.startsWith(WebApkConstants.WEBAPK_PACKAGE_PREFIX);
-    }
-
-    @Override
-    protected void onDestroyInternal() {
-        // The common case is to be connected to just one WebAPK's services. For the sake of
-        // simplicity disconnect from the services of all WebAPKs.
-        ChromeWebApkHost.disconnectFromAllServices(true /* waitForPendingWork */);
-
-        super.onDestroyInternal();
-    }
-
-    @Override
-    @ActivityType
-    public int getActivityType() {
-        return ActivityType.WEB_APK;
-    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivityCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivityCoordinator.java
index 5f73668..61463c4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivityCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkActivityCoordinator.java
@@ -8,6 +8,8 @@
 
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.dependency_injection.ActivityScope;
+import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
+import org.chromium.chrome.browser.lifecycle.Destroyable;
 
 import javax.inject.Inject;
 
@@ -18,8 +20,8 @@
  * Add methods here if other components need to communicate with the WebAPK activity component.
  */
 @ActivityScope
-public class WebApkActivityCoordinator {
-    private final WebApkActivity mActivity;
+public class WebApkActivityCoordinator implements Destroyable {
+    private final WebappActivity mActivity;
     private final Lazy<WebApkUpdateManager> mWebApkUpdateManager;
 
     @Inject
@@ -27,12 +29,13 @@
             WebappDeferredStartupWithStorageHandler deferredStartupWithStorageHandler,
             WebappDisclosureSnackbarController disclosureSnackbarController,
             WebApkActivityLifecycleUmaTracker webApkActivityLifecycleUmaTracker,
+            ActivityLifecycleDispatcher lifecycleDispatcher,
             Lazy<WebApkUpdateManager> webApkUpdateManager) {
         // We don't need to do anything with |disclosureSnackbarController| and
         // |webApkActivityLifecycleUmaTracker|. We just need to resolve
         // them so that they start working.
 
-        mActivity = (WebApkActivity) activity;
+        mActivity = (WebappActivity) activity;
         mWebApkUpdateManager = webApkUpdateManager;
 
         deferredStartupWithStorageHandler.addTask((storage, didCreateStorage) -> {
@@ -40,6 +43,7 @@
 
             onDeferredStartupWithStorage(storage, didCreateStorage);
         });
+        lifecycleDispatcher.register(this);
     }
 
     public void onDeferredStartupWithStorage(
@@ -50,4 +54,11 @@
         WebApkInfo info = (WebApkInfo) mActivity.getWebappInfo();
         mWebApkUpdateManager.get().updateIfNeeded(storage, info);
     }
+
+    @Override
+    public void destroy() {
+        // The common case is to be connected to just one WebAPK's services. For the sake of
+        // simplicity disconnect from the services of all WebAPKs.
+        ChromeWebApkHost.disconnectFromAllServices(true /* waitForPendingWork */);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkInfo.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkInfo.java
index 6408c0a..9909b8db 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkInfo.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkInfo.java
@@ -94,10 +94,6 @@
         }
     }
 
-    public static WebApkInfo createEmpty() {
-        return create(createEmptyIntentDataProvider());
-    }
-
     /**
      * Constructs a WebApkInfo from the passed in Intent and <meta-data> in the WebAPK's Android
      * manifest.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
index 26e12de..180911ee 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
@@ -21,6 +21,7 @@
 import org.chromium.base.ActivityState;
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.ApplicationStatus;
+import org.chromium.base.IntentUtils;
 import org.chromium.base.Log;
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.base.task.PostTask;
@@ -40,7 +41,6 @@
 import org.chromium.chrome.browser.customtabs.features.ImmersiveModeController;
 import org.chromium.chrome.browser.dependency_injection.ChromeActivityCommonsModule;
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
-import org.chromium.chrome.browser.flags.ActivityType;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabBrowserControlsConstraintsHelper;
 import org.chromium.chrome.browser.tab.TabDelegateFactory;
@@ -53,6 +53,7 @@
 import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.ScreenOrientationProvider;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
+import org.chromium.webapk.lib.common.WebApkConstants;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -152,12 +153,6 @@
         }
     }
 
-    @Override
-    @ActivityType
-    public int getActivityType() {
-        return ActivityType.WEBAPP;
-    }
-
     protected boolean loadUrlIfPostShareTarget(WebappInfo webappInfo) {
         return false;
     }
@@ -167,7 +162,24 @@
     }
 
     protected WebappInfo createWebappInfo(Intent intent) {
-        return (intent == null) ? WebappInfo.createEmpty() : WebappInfo.create(intent);
+        if (intent == null) return WebappInfo.createEmpty();
+
+        WebappInfo info = WebApkInfo.create(intent);
+        if (info != null) return info;
+
+        return WebappInfo.create(intent);
+    }
+
+    @Override
+    public boolean shouldPreferLightweightFre(Intent intent) {
+        // We cannot use WebappInfo#webApkPackageName() because
+        // {@link WebappActivity#performPreInflationStartup()} may not have been called yet.
+        String webApkPackageName =
+                IntentUtils.safeGetStringExtra(intent, WebApkConstants.EXTRA_WEBAPK_PACKAGE_NAME);
+
+        // Use the lightweight FRE for unbound WebAPKs.
+        return webApkPackageName != null
+                && !webApkPackageName.startsWith(WebApkConstants.WEBAPK_PACKAGE_PREFIX);
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappInfo.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappInfo.java
index ffdd83d..5b470ce 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappInfo.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappInfo.java
@@ -18,14 +18,11 @@
 public class WebappInfo {
     protected final BrowserServicesIntentDataProvider mProvider;
 
-    protected static BrowserServicesIntentDataProvider createEmptyIntentDataProvider() {
-        return new WebappIntentDataProvider(WebappIntentDataProvider.getDefaultToolbarColor(),
-                false /* hasCustomToolbarColor */, null /* shareData */, WebappExtras.createEmpty(),
-                WebApkExtras.createEmpty());
-    }
-
     public static WebappInfo createEmpty() {
-        return new WebappInfo(createEmptyIntentDataProvider());
+        return new WebappInfo(
+                new WebappIntentDataProvider(WebappIntentDataProvider.getDefaultToolbarColor(),
+                        false /* hasCustomToolbarColor */, null /* shareData */,
+                        WebappExtras.createEmpty(), WebApkExtras.createEmpty()));
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappIntentDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappIntentDataProvider.java
index 7ccfa67..c3a67af4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappIntentDataProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappIntentDataProvider.java
@@ -16,6 +16,7 @@
 import org.chromium.base.ContextUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider;
+import org.chromium.chrome.browser.flags.ActivityType;
 import org.chromium.components.browser_ui.widget.TintedDrawable;
 
 /**
@@ -28,6 +29,7 @@
     private final ShareData mShareData;
     private final @NonNull WebappExtras mWebappExtras;
     private final @Nullable WebApkExtras mWebApkExtras;
+    private final @ActivityType int mActivityType;
     private final Intent mIntent;
 
     /**
@@ -46,10 +48,16 @@
         mShareData = shareData;
         mWebappExtras = webappExtras;
         mWebApkExtras = webApkExtras;
+        mActivityType = (webApkExtras != null) ? ActivityType.WEB_APK : ActivityType.WEBAPP;
         mIntent = new Intent();
     }
 
     @Override
+    public @ActivityType int getActivityType() {
+        return mActivityType;
+    }
+
+    @Override
     @Nullable
     public Intent getIntent() {
         return mIntent;
@@ -82,16 +90,6 @@
     }
 
     @Override
-    public boolean isWebappOrWebApkActivity() {
-        return true;
-    }
-
-    @Override
-    public boolean isWebApkActivity() {
-        return mWebApkExtras != null;
-    }
-
-    @Override
     @Nullable
     public ShareData getShareData() {
         return mShareData;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragmentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragmentTest.java
index 33d4120a..79424065 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragmentTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragmentTest.java
@@ -31,6 +31,7 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.browser.ChromeActivity;
@@ -450,6 +451,7 @@
     @Test
     @MediumTest
     @Feature({"SiteEngagement"})
+    @DisabledTest(message = "crbug.com/1067686")
     public void testImportantSitesDialogNoopOnCancel() throws Exception {
         // Sign in.
         SigninTestUtil.addAndSignInTestAccount();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunUtilsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunUtilsTest.java
index a7bb111..a9f9ba6f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunUtilsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunUtilsTest.java
@@ -18,10 +18,12 @@
 import org.chromium.base.test.util.AdvancedMockContext;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.AccountManagerFacadeProvider;
 import org.chromium.components.signin.AccountUtils;
 import org.chromium.components.signin.test.util.AccountHolder;
 import org.chromium.components.signin.test.util.FakeAccountManagerDelegate;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 /**
  * Unit Test for {@link FirstRunUtils}.
@@ -63,7 +65,10 @@
 
     private void setUpAccountManager(String accountType) {
         mAccountManager = new FakeAuthenticationAccountManager(accountType);
-        AccountManagerFacadeProvider.overrideAccountManagerFacadeForTests(mAccountManager);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            AccountManagerFacadeProvider.setInstanceForTests(
+                    new AccountManagerFacade(mAccountManager));
+        });
     }
 
     private void addTestAccount() {
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 aec2fabf..ce5b0e2 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
@@ -238,9 +238,8 @@
         mRenderTestRule.render(mNtp.getSignInPromoViewForTesting(), "sign_in_promo");
     }
 
-    @DisabledTest(message = "https://crbug.com/945293")
     @Test
-    @SmallTest
+    @MediumTest
     @Feature({"NewTabPage", "FeedNewTabPage", "RenderTest"})
     @ParameterAnnotations.UseMethodParameter(InterestFeedParams.class)
     public void testRender_ArticleSectionHeader(boolean interestFeedEnabled) throws Exception {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/RequestGeneratorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/RequestGeneratorTest.java
index 73fee7e..ce03f7d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/RequestGeneratorTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/RequestGeneratorTest.java
@@ -23,9 +23,11 @@
 import org.chromium.chrome.test.omaha.MockRequestGenerator;
 import org.chromium.chrome.test.omaha.MockRequestGenerator.DeviceType;
 import org.chromium.chrome.test.omaha.MockRequestGenerator.SignedInStatus;
+import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.AccountManagerFacadeProvider;
 import org.chromium.components.signin.test.util.AccountHolder;
 import org.chromium.components.signin.test.util.FakeAccountManagerDelegate;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 /**
  * Unit tests for the RequestGenerator class.
@@ -179,7 +181,10 @@
         for (Account account : accounts) {
             accountManager.addAccountHolderExplicitly(AccountHolder.builder(account).build());
         }
-        AccountManagerFacadeProvider.overrideAccountManagerFacadeForTests(accountManager);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            AccountManagerFacadeProvider.setInstanceForTests(
+                    new AccountManagerFacade(accountManager));
+        });
 
         String sessionId = "random_session_id";
         String requestId = "random_request_id";
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/settings/MainSettingsFragmentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/settings/MainSettingsFragmentTest.java
index a2c68bb..d51dc37 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/settings/MainSettingsFragmentTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/settings/MainSettingsFragmentTest.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.settings;
 
 import android.accounts.Account;
+import android.os.Build.VERSION_CODES;
 import android.support.test.filters.SmallTest;
 import android.text.TextUtils;
 
@@ -22,6 +23,7 @@
 import org.mockito.MockitoAnnotations;
 
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.DisableIf;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.about_settings.AboutChromeSettings;
 import org.chromium.chrome.browser.accessibility.settings.AccessibilitySettings;
@@ -115,6 +117,7 @@
      */
     @Test
     @SmallTest
+    @DisableIf.Build(sdk_is_less_than = VERSION_CODES.LOLLIPOP)
     public void testStartup() {
         launchSettingsActivity();
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninHelperTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninHelperTest.java
index f77a67a..1da1ad5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninHelperTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninHelperTest.java
@@ -40,7 +40,7 @@
 
     @After
     public void tearDown() {
-        AccountManagerFacadeProvider.resetAccountManagerFacadeForTests();
+        AccountManagerFacadeProvider.resetInstanceForTests();
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorTest.java
index 7b141f6d..5e50011f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorTest.java
@@ -14,7 +14,6 @@
 
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
-import android.os.Build;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
 import android.view.View;
@@ -32,7 +31,6 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
@@ -99,8 +97,6 @@
 
     @Test
     @MediumTest
-    @DisableIf.Build(sdk_is_greater_than = Build.VERSION_CODES.KITKAT,
-            sdk_is_less_than = Build.VERSION_CODES.M, message = "crbug.com/1059421")
     public void testShowAndHide() {
         final ChromeFullscreenManager fullscreenManager =
                 mActivityTestRule.getActivity().getFullscreenManager();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ManageSyncSettingsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ManageSyncSettingsTest.java
index c143beb..393897d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ManageSyncSettingsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ManageSyncSettingsTest.java
@@ -6,7 +6,6 @@
 
 import android.app.Dialog;
 import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
 import android.support.test.filters.SmallTest;
 import android.widget.Button;
 import android.widget.EditText;
@@ -24,6 +23,7 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.FlakyTest;
 import org.chromium.chrome.R;
@@ -111,8 +111,12 @@
         ChromeSwitchPreference syncEverything = getSyncEverything(fragment);
         Map<Integer, CheckBoxPreference> dataTypes = getDataTypes(fragment);
 
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
         assertSyncOnState(fragment);
         mSyncTestRule.togglePreference(syncEverything);
+
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        Assert.assertFalse(syncEverything.isChecked());
         for (CheckBoxPreference dataType : dataTypes.values()) {
             Assert.assertTrue(dataType.isChecked());
             Assert.assertTrue(dataType.isEnabled());
@@ -203,8 +207,9 @@
         assertPaymentsIntegrationEnabled(true);
     }
 
+    @DisabledTest(message = "crbug.com/994726")
     @Test
-    @MediumTest
+    @SmallTest
     @Feature({"Sync"})
     public void testPaymentsIntegrationCheckboxClearsServerAutofillCreditCards() {
         mSyncTestRule.setUpTestAccountAndSignIn();
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CloseButtonNavigatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CloseButtonNavigatorTest.java
index ea02b3d1..684a507 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CloseButtonNavigatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CloseButtonNavigatorTest.java
@@ -32,6 +32,7 @@
 import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider;
 import org.chromium.chrome.browser.customtabs.content.CustomTabActivityTabController;
 import org.chromium.chrome.browser.customtabs.content.CustomTabActivityTabProvider;
+import org.chromium.chrome.browser.flags.ActivityType;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.webapps.WebappExtras;
 import org.chromium.content_public.browser.NavigationController;
@@ -78,7 +79,9 @@
             mWebappExtras = null;
         }
         doReturn(mWebappExtras).when(mIntentDataProvider).getWebappExtras();
-        doReturn(mIsWebapp).when(mIntentDataProvider).isWebappOrWebApkActivity();
+        doReturn(mIsWebapp ? ActivityType.WEBAPP : ActivityType.CUSTOM_TAB)
+                .when(mIntentDataProvider)
+                .getActivityType();
 
         mCloseButtonNavigator =
                 new CloseButtonNavigator(mTabController, mTabProvider, mIntentDataProvider);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/firstrun/ToSAckedReceiverTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/firstrun/ToSAckedReceiverTest.java
index 1377a437..d8138d4 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/firstrun/ToSAckedReceiverTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/firstrun/ToSAckedReceiverTest.java
@@ -23,6 +23,7 @@
 import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
 import org.chromium.components.signin.AccountManagerDelegate;
 import org.chromium.components.signin.AccountManagerDelegateException;
+import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.AccountManagerFacadeProvider;
 
 import java.util.HashSet;
@@ -62,7 +63,8 @@
         Account[] accounts = new Account[1];
         accounts[0] = new Account(GOOGLE_ACCOUNT, "LegitAccount");
         Mockito.doReturn(accounts).when(accountManagerDelegate).getAccountsSync();
-        AccountManagerFacadeProvider.overrideAccountManagerFacadeForTests(accountManagerDelegate);
+        AccountManagerFacadeProvider.setInstanceForTests(
+                new AccountManagerFacade(accountManagerDelegate));
         Assert.assertTrue(ToSAckedReceiver.checkAnyUserHasSeenToS());
     }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/photo_picker/FileEnumWorkerTaskTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/photo_picker/FileEnumWorkerTaskTest.java
new file mode 100644
index 0000000..2574540
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/photo_picker/FileEnumWorkerTaskTest.java
@@ -0,0 +1,392 @@
+// Copyright 2020 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.photo_picker;
+
+import static org.mockito.ArgumentMatchers.eq;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.support.test.filters.SmallTest;
+
+import androidx.annotation.IntDef;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.robolectric.android.util.concurrent.RoboExecutorService;
+import org.robolectric.annotation.Config;
+import org.robolectric.fakes.BaseCursor;
+
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.net.MimeTypeFilter;
+import org.chromium.ui.base.WindowAndroid;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Tests for {@link FileEnumWorkerTaskTest}.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class FileEnumWorkerTaskTest implements FileEnumWorkerTask.FilesEnumeratedCallback {
+    // The Fields the test Cursor represents.
+    @IntDef({Fields.ID, Fields.MIME_TYPE, Fields.DATE_ADDED})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface Fields {
+        int ID = 0;
+        int MIME_TYPE = 1;
+        int DATE_ADDED = 2;
+    }
+
+    private static class TestFileEnumWorkerTask extends FileEnumWorkerTask {
+        private boolean mShouldShowCameraTile = true;
+
+        public TestFileEnumWorkerTask(WindowAndroid windowAndroid, FilesEnumeratedCallback callback,
+                MimeTypeFilter filter, List<String> mimeTypes, ContentResolver contentResolver) {
+            super(windowAndroid, callback, filter, mimeTypes, contentResolver);
+        }
+
+        public void setShouldShowCameraTile(boolean shouldShow) {
+            mShouldShowCameraTile = shouldShow;
+        }
+
+        @Override
+        protected Cursor createImageCursor(Uri contentUri, String[] selectColumns,
+                String whereClause, String[] whereArgs, String orderBy) {
+            ArrayList<TestData> list = new ArrayList<TestData>();
+            list.add(new TestData("file0", "text/html", 0));
+            list.add(new TestData("file1", "image/jpeg", 1));
+            list.add(new TestData("file2", "image/jpeg", 2));
+            list.add(new TestData("file3", "video/mp4", 3));
+            list.add(new TestData("file4", "video/mp4", 4));
+
+            return new FileCursor(list);
+        }
+
+        @Override
+        protected boolean shouldShowCameraTile() {
+            return mShouldShowCameraTile;
+        }
+    }
+
+    private static class TestData {
+        public Uri mUri;
+        public String mMimeType;
+        public long mDateAdded;
+
+        public TestData(String uri, String mimeType, long dateAdded) {
+            mUri = Uri.parse(uri);
+            mMimeType = mimeType;
+            mDateAdded = dateAdded;
+        }
+    }
+
+    private static class FileCursor extends BaseCursor {
+        private List<TestData> mData;
+
+        private int mIndex;
+
+        public FileCursor(List<TestData> data) {
+            mData = data;
+        }
+
+        @Override
+        public int getCount() {
+            return mData.size();
+        }
+
+        @Override
+        public boolean moveToNext() {
+            return mIndex++ < mData.size();
+        }
+
+        @Override
+        public int getColumnIndex(String columnName) {
+            switch (columnName) {
+                case MediaStore.Files.FileColumns._ID:
+                    return Fields.ID;
+                case MediaStore.Files.FileColumns.MIME_TYPE:
+                    return Fields.MIME_TYPE;
+                case MediaStore.Files.FileColumns.DATE_ADDED:
+                    return Fields.DATE_ADDED;
+                default:
+                    return -1;
+            }
+        }
+
+        @Override
+        public int getInt(int columnIndex) {
+            if (columnIndex == Fields.ID) {
+                return mIndex;
+            }
+            return -1;
+        }
+
+        @Override
+        public long getLong(int columnIndex) {
+            if (columnIndex == Fields.DATE_ADDED) {
+                return mData.get(mIndex - 1).mDateAdded;
+            }
+            return -1;
+        }
+
+        @Override
+        public String getString(int columnIndex) {
+            if (columnIndex == Fields.MIME_TYPE) {
+                return mData.get(mIndex - 1).mMimeType;
+            }
+            return "";
+        }
+
+        @Override
+        public void close() {}
+    }
+
+    private final RoboExecutorService mRoboExecutorService = new RoboExecutorService();
+
+    // A callback that fires the task completes.
+    private final CallbackHelper mOnWorkerCompleteCallback = new CallbackHelper();
+
+    // The last set of data returned by the FileEnumWorkerTask.
+    private List<PickerBitmap> mFilesReturned;
+
+    @Before
+    public void setUp() {
+        ThreadUtils.setThreadAssertsDisabledForTesting(true);
+    }
+
+    @After
+    public void tearDown() {
+        ThreadUtils.setThreadAssertsDisabledForTesting(false);
+        Assert.assertTrue(mRoboExecutorService.shutdownNow().isEmpty());
+    }
+
+    // FileEnumWorkerTask.FilesEnumeratedCallback:
+
+    @Override
+    public void filesEnumeratedCallback(List<PickerBitmap> files) {
+        mFilesReturned = files;
+        mOnWorkerCompleteCallback.notifyCalled();
+    }
+
+    /**
+     * Test cursor creation (with the help of a mocked ContentResolver). This calls direct into
+     * {@link FileEnumWorkerTask} (as opposed to {@link TestFileEnumWorkerTask}), to make sure it
+     * calls the {@link ContentResolver} back with the right parameters.
+     */
+    @Test
+    @SmallTest
+    public void testCursorCreation() throws Exception {
+        ContentResolver contentResolver = Mockito.mock(ContentResolver.class);
+        List<String> mimeTypes = Collections.singletonList("");
+        FileEnumWorkerTask task = new FileEnumWorkerTask(/* windowAndroid= */ null, this,
+                new MimeTypeFilter(mimeTypes, true), mimeTypes, contentResolver);
+        task.executeOnExecutor(mRoboExecutorService);
+        mOnWorkerCompleteCallback.waitForFirst();
+
+        Uri contentUri = MediaStore.Files.getContentUri("external");
+        String[] selectColumns = {MediaStore.Files.FileColumns._ID,
+                MediaStore.Files.FileColumns.DATE_ADDED, MediaStore.Files.FileColumns.MEDIA_TYPE,
+                MediaStore.Files.FileColumns.MIME_TYPE, MediaStore.Files.FileColumns.DATA};
+        String whereClause = "(_data LIKE ? OR _data LIKE ? OR _data LIKE ?) AND _data NOT LIKE ?";
+        String orderBy = MediaStore.MediaColumns.DATE_ADDED + " DESC";
+
+        ArgumentCaptor<String[]> argument = ArgumentCaptor.forClass(String[].class);
+        Mockito.verify(contentResolver)
+                .query(eq(contentUri), eq(selectColumns), eq(whereClause), argument.capture(),
+                        eq(orderBy));
+        String[] actualWhereArgs = argument.getValue();
+        Assert.assertEquals(4, actualWhereArgs.length);
+        Assert.assertTrue(actualWhereArgs[0],
+                actualWhereArgs[0].contains(Environment.DIRECTORY_DCIM + "/Camera"));
+        Assert.assertTrue(
+                actualWhereArgs[1], actualWhereArgs[1].contains(Environment.DIRECTORY_PICTURES));
+        Assert.assertTrue(
+                actualWhereArgs[2], actualWhereArgs[2].contains(Environment.DIRECTORY_DOWNLOADS));
+        Assert.assertTrue(actualWhereArgs[3],
+                actualWhereArgs[3].contains(Environment.DIRECTORY_PICTURES + "/Screenshots"));
+    }
+
+    @Test
+    @SmallTest
+    public void testNoMimeTypes() throws Exception {
+        List<String> mimeTypes = Collections.singletonList("");
+        TestFileEnumWorkerTask task = new TestFileEnumWorkerTask(/* windowAndroid= */ null, this,
+                new MimeTypeFilter(mimeTypes, true), mimeTypes, /* contentResolver= */ null);
+        task.executeOnExecutor(mRoboExecutorService);
+        mOnWorkerCompleteCallback.waitForFirst();
+
+        // If this assert hits, then onCancelled has been called in FileEnumWorkerTask, most likely
+        // due to an exception thrown inside doInBackground. To surface the exception message, call
+        // task.doInBackground() directly, instead of task.executeOnExecutor(...).
+        Assert.assertTrue(mFilesReturned != null);
+        Assert.assertEquals(2, mFilesReturned.size());
+
+        PickerBitmap tile = mFilesReturned.get(0);
+        Assert.assertEquals(PickerBitmap.TileTypes.CAMERA, tile.type());
+        Assert.assertEquals(0, tile.getLastModifiedForTesting());
+        Assert.assertEquals(null, tile.getUri());
+
+        tile = mFilesReturned.get(1);
+        Assert.assertEquals(PickerBitmap.TileTypes.GALLERY, tile.type());
+        Assert.assertEquals(0, tile.getLastModifiedForTesting());
+        Assert.assertEquals(null, tile.getUri());
+    }
+
+    @Test
+    @SmallTest
+    public void testNoCameraTile() throws Exception {
+        List<String> mimeTypes = Collections.singletonList("");
+        TestFileEnumWorkerTask task = new TestFileEnumWorkerTask(/* windowAndroid= */ null, this,
+                new MimeTypeFilter(mimeTypes, true), mimeTypes, /* contentResolver= */ null);
+        task.setShouldShowCameraTile(false);
+        task.executeOnExecutor(mRoboExecutorService);
+        mOnWorkerCompleteCallback.waitForFirst();
+
+        // If this assert hits, then onCancelled has been called in FileEnumWorkerTask, most likely
+        // due to an exception thrown inside doInBackground. To surface the exception message, call
+        // task.doInBackground() directly, instead of task.executeOnExecutor(...).
+        Assert.assertTrue(mFilesReturned != null);
+        Assert.assertEquals(1, mFilesReturned.size());
+
+        PickerBitmap tile = mFilesReturned.get(0);
+        Assert.assertEquals(PickerBitmap.TileTypes.GALLERY, tile.type());
+        Assert.assertEquals(0, tile.getLastModifiedForTesting());
+        Assert.assertEquals(null, tile.getUri());
+    }
+
+    @Test
+    @SmallTest
+    public void testImagesOnly() throws Exception {
+        List<String> mimeTypes = Collections.singletonList("image/*");
+        TestFileEnumWorkerTask task = new TestFileEnumWorkerTask(/* windowAndroid= */ null, this,
+                new MimeTypeFilter(mimeTypes, true), mimeTypes, /* contentResolver= */ null);
+        task.executeOnExecutor(mRoboExecutorService);
+        mOnWorkerCompleteCallback.waitForFirst();
+
+        // If this assert hits, then onCancelled has been called in FileEnumWorkerTask, most likely
+        // due to an exception thrown inside doInBackground. To surface the exception message, call
+        // task.doInBackground() directly, instead of task.executeOnExecutor(...).
+        Assert.assertTrue(mFilesReturned != null);
+        Assert.assertEquals(4, mFilesReturned.size());
+
+        PickerBitmap tile = mFilesReturned.get(0);
+        Assert.assertEquals(PickerBitmap.TileTypes.CAMERA, tile.type());
+        Assert.assertEquals(0, tile.getLastModifiedForTesting());
+        Assert.assertEquals(null, tile.getUri());
+
+        tile = mFilesReturned.get(1);
+        Assert.assertEquals(PickerBitmap.TileTypes.GALLERY, tile.type());
+        Assert.assertEquals(0, tile.getLastModifiedForTesting());
+        Assert.assertEquals(null, tile.getUri());
+
+        tile = mFilesReturned.get(2);
+        Assert.assertEquals(PickerBitmap.TileTypes.PICTURE, tile.type());
+        Assert.assertEquals(1, tile.getLastModifiedForTesting());
+        Assert.assertEquals("/external/file/2", tile.getUri().getPath());
+
+        tile = mFilesReturned.get(3);
+        Assert.assertEquals(PickerBitmap.TileTypes.PICTURE, tile.type());
+        Assert.assertEquals(2, tile.getLastModifiedForTesting());
+        Assert.assertEquals("/external/file/3", tile.getUri().getPath());
+    }
+
+    @Test
+    @SmallTest
+    public void testVideoOnly() throws Exception {
+        // Try with just video files (plus camera and gallery tiles).
+        List<String> mimeTypes = Collections.singletonList("video/*");
+        TestFileEnumWorkerTask task = new TestFileEnumWorkerTask(/* windowAndroid= */ null, this,
+                new MimeTypeFilter(mimeTypes, true), mimeTypes, /* contentResolver= */ null);
+        task.executeOnExecutor(mRoboExecutorService);
+        mOnWorkerCompleteCallback.waitForFirst();
+
+        // If this assert hits, then onCancelled has been called in FileEnumWorkerTask, most likely
+        // due to an exception thrown inside doInBackground. To surface the exception message, call
+        // task.doInBackground() directly, instead of task.executeOnExecutor(...).
+        Assert.assertTrue(mFilesReturned != null);
+        Assert.assertEquals(4, mFilesReturned.size());
+
+        PickerBitmap tile = mFilesReturned.get(0);
+        Assert.assertEquals(PickerBitmap.TileTypes.CAMERA, tile.type());
+        Assert.assertEquals(0, tile.getLastModifiedForTesting());
+        Assert.assertEquals(null, tile.getUri());
+
+        tile = mFilesReturned.get(1);
+        Assert.assertEquals(PickerBitmap.TileTypes.GALLERY, tile.type());
+        Assert.assertEquals(0, tile.getLastModifiedForTesting());
+        Assert.assertEquals(null, tile.getUri());
+
+        tile = mFilesReturned.get(2);
+        Assert.assertEquals(PickerBitmap.TileTypes.VIDEO, tile.type());
+        Assert.assertEquals(3, tile.getLastModifiedForTesting());
+        Assert.assertEquals("/external/file/4", tile.getUri().getPath());
+
+        tile = mFilesReturned.get(3);
+        Assert.assertEquals(PickerBitmap.TileTypes.VIDEO, tile.type());
+        Assert.assertEquals(4, tile.getLastModifiedForTesting());
+        Assert.assertEquals("/external/file/5", tile.getUri().getPath());
+    }
+
+    @Test
+    @SmallTest
+    public void testImagesAndVideos() throws Exception {
+        List<String> mimeTypes = Arrays.asList("image/*", "video/*");
+        TestFileEnumWorkerTask task = new TestFileEnumWorkerTask(/* windowAndroid= */ null, this,
+                new MimeTypeFilter(mimeTypes, true), mimeTypes, /* contentResolver= */ null);
+        task.executeOnExecutor(mRoboExecutorService);
+        mOnWorkerCompleteCallback.waitForFirst();
+
+        // If this assert hits, then onCancelled has been called in FileEnumWorkerTask, most likely
+        // due to an exception thrown inside doInBackground. To surface the exception message, call
+        // task.doInBackground() directly, instead of task.executeOnExecutor(...).
+        Assert.assertTrue(mFilesReturned != null);
+        Assert.assertEquals(6, mFilesReturned.size());
+
+        PickerBitmap tile = mFilesReturned.get(0);
+        Assert.assertEquals(PickerBitmap.TileTypes.CAMERA, tile.type());
+        Assert.assertEquals(0, tile.getLastModifiedForTesting());
+        Assert.assertEquals(null, tile.getUri());
+
+        tile = mFilesReturned.get(1);
+        Assert.assertEquals(PickerBitmap.TileTypes.GALLERY, tile.type());
+        Assert.assertEquals(0, tile.getLastModifiedForTesting());
+        Assert.assertEquals(null, tile.getUri());
+
+        tile = mFilesReturned.get(2);
+        Assert.assertEquals(PickerBitmap.TileTypes.PICTURE, tile.type());
+        Assert.assertEquals(1, tile.getLastModifiedForTesting());
+        Assert.assertEquals("/external/file/2", tile.getUri().getPath());
+
+        tile = mFilesReturned.get(3);
+        Assert.assertEquals(PickerBitmap.TileTypes.PICTURE, tile.type());
+        Assert.assertEquals(2, tile.getLastModifiedForTesting());
+        Assert.assertEquals("/external/file/3", tile.getUri().getPath());
+
+        tile = mFilesReturned.get(4);
+        Assert.assertEquals(PickerBitmap.TileTypes.VIDEO, tile.type());
+        Assert.assertEquals(3, tile.getLastModifiedForTesting());
+        Assert.assertEquals("/external/file/4", tile.getUri().getPath());
+
+        tile = mFilesReturned.get(5);
+        Assert.assertEquals(PickerBitmap.TileTypes.VIDEO, tile.type());
+        Assert.assertEquals(4, tile.getLastModifiedForTesting());
+        Assert.assertEquals("/external/file/5", tile.getUri().getPath());
+    }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorMediatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorMediatorTest.java
index 7e2f17b..6f76da3 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorMediatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorMediatorTest.java
@@ -90,8 +90,7 @@
         mMediator.onControlsOffsetChanged(0, 70, 0, 0, false);
 
         // Now, hide it. Listener shouldn't be removed.
-        setViewHeight(0);
-        mMediator.onLayoutChange(mStatusIndicatorView, 0, 0, 0, 0, 0, 0, 0, 0);
+        mMediator.updateVisibilityForTesting(true);
         verify(mFullscreenManager, never()).removeListener(mMediator);
 
         // Once the hiding animation is done...
@@ -109,8 +108,7 @@
         // The Android view should be visible at this point.
         assertEquals(View.VISIBLE, mModel.get(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY));
         // Now hide it.
-        setViewHeight(0);
-        mMediator.onLayoutChange(mStatusIndicatorView, 0, 0, 0, 0, 0, 0, 0, 0);
+        mMediator.updateVisibilityForTesting(true);
         // The hiding animation...
         mMediator.onControlsOffsetChanged(0, 30, 0, 0, false);
         // Android view will be gone once the animation starts.
@@ -160,8 +158,7 @@
         assertTrue(mModel.get(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE));
 
         // Hide the indicator.
-        setViewHeight(0);
-        mMediator.onLayoutChange(mStatusIndicatorView, 0, 0, 0, 0, 0, 0, 0, 0);
+        mMediator.updateVisibilityForTesting(true);
         // The indicator should hide immediately.
         assertEquals(View.GONE, mModel.get(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY));
         assertFalse(mModel.get(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE));
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 7cbbf4a0..84bd45d 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -6251,12 +6251,6 @@
       <message name="IDS_PICTURE_IN_PICTURE_BACK_TO_TAB_CONTROL_TEXT" desc="Text label of the back to tab control button. The button appears when the user hovers over the Picture-in-Picture window.">
         Back to tab
       </message>
-      <message name="IDS_PICTURE_IN_PICTURE_MUTE_CONTROL_TEXT" desc="Text label of the mute control button. The button appears when the user hovers over the Picture-in-Picture window and the video is currently unmuted.">
-        Mute
-      </message>
-      <message name="IDS_PICTURE_IN_PICTURE_UNMUTE_CONTROL_TEXT" desc="Text label of the mute control button. The button appears when the user hovers over the Picture-in-Picture window and the video is currently muted.">
-        Unmute
-      </message>
       <message name="IDS_PICTURE_IN_PICTURE_SKIP_AD_CONTROL_TEXT" desc="Text label of the skip ad control button. The button appears when the user hovers over the Picture-in-Picture window.">
         Skip Ad
       </message>
@@ -6275,24 +6269,6 @@
       <message name="IDS_PICTURE_IN_PICTURE_PREVIOUS_TRACK_CONTROL_ACCESSIBLE_TEXT" desc="Accessible text label used for the controls button in the Picture-in-Picture window. The button invokes previous track action.">
         Previous track
       </message>
-      <message name="IDS_PICTURE_IN_PICTURE_CONFIRM_CLOSE_TITLE" desc="Text label of the title for the confirmation dialog. This dialog appears when the user tries to close a tab / window while in Picture-in-Picture mode.">
-        Are you sure you want to close this tab?
-      </message>
-      <message name="IDS_PICTURE_IN_PICTURE_CONFIRM_LEAVE_TITLE" desc="Text label of the description for the confirmation dialog. This dialog appears when the user tries to leave a page while in Picture-in-Picture mode.">
-        Are you sure you want to leave this page?
-      </message>
-      <message name="IDS_PICTURE_IN_PICTURE_CONFIRM_DESCRIPTION" desc="Text label of the description for the confirmation dialog. This dialog appears when the user tries to close a tab / window or leave a page while in Picture-in-Picture mode.">
-        The video in picture-in-picture mode will stop playing.
-      </message>
-      <message name="IDS_PICTURE_IN_PICTURE_CONFIRM_CLOSE_TEXT" desc="Text label of the close button. The button appears in the confirmation dialog when the user tries to close a tab or window while in Picture-in-Picture mode.">
-        Close
-      </message>
-      <message name="IDS_PICTURE_IN_PICTURE_CONFIRM_LEAVE_TEXT" desc="Text label of the leave button. The button appears in the confirmation dialog when the user tries to leave a page while in Picture-in-Picture mode.">
-        Leave
-      </message>
-      <message name="IDS_PICTURE_IN_PICTURE_CANCEL_TEXT" desc="Text label of the cancel button. The button appears in the confirmation dialog when the user tries to close a tab / window or leave a page while in Picture-in-Picture mode.">
-        Cancel
-      </message>
 
       <!-- Load State -->
       <message name="IDS_LOAD_STATE_WAITING_FOR_SOCKET_SLOT" desc="Status text when Chrome is at its connection limit, and is waiting for a URL request to complete to free up a connection.">
@@ -8047,9 +8023,6 @@
         <message name="IDS_TRANSLATE_BUBBLE_BEFORE_TRANSLATE_TITLE" desc="Title text for the translate bubble when asking to translate a page.">
           Translate this page?
         </message>
-        <message name="IDS_TRANSLATE_BUBBLE_BEFORE_TRANSLATE_NEW" desc="Text to show for the translate bubble label when that page is in specified language and ask if should translate.">
-          Do you want Google to translate this page from <ph name="source_language">$1<ex>Spanish</ex></ph> to <ph name="target_language">$2<ex>English</ex></ph>?
-        </message>
         <message name="IDS_TRANSLATE_BUBBLE_ADVANCED_TITLE" desc="Title text for the options panel.">
           Options
         </message>
@@ -8060,9 +8033,6 @@
           This page could not be translated
         </message>
         <if expr="not use_titlecase">
-          <message name="IDS_TRANSLATE_BUBBLE_ADVANCED_LINK" desc="Text to show for the translate bubble link label to jump to the advanced panel.">
-            Options
-          </message>
           <message name="IDS_TRANSLATE_BUBBLE_ADVANCED_BUTTON" desc="Text to show for the translate bubble button to jump to the advanced panel.">
             Options
           </message>
@@ -8093,9 +8063,6 @@
           <message name="IDS_TRANSLATE_BUBBLE_TRANSLATING" desc="Text to show for the translate bubble label when page is currently being translated by servers">
             Translating...
           </message>
-          <message name="IDS_TRANSLATE_BUBBLE_TRANSLATED" desc="Text to show for the translate bubble label when the page has been translated from one language to another">
-            This page has been translated
-          </message>
           <message name="IDS_TRANSLATE_BUBBLE_REVERT" desc="Text to show for the translate bubble button to revert translation of translated page">
             Show original
           </message>
@@ -8105,24 +8072,15 @@
           <message name="IDS_TRANSLATE_BUBBLE_ALWAYS" desc="Text to show for the translate bubble checkbox to always translate from one language to another">
             Always translate
           </message>
-          <message name="IDS_TRANSLATE_BUBBLE_ALWAYS_DO_THIS" desc="Text to show for the translate bubble checkbox to always translate from one language to another in 2016Q2 UI">
-            Always do this
-          </message>
           <message name="IDS_TRANSLATE_BUBBLE_OPTIONS_MENU_BUTTON" desc="Text to show for the translate bubble 'Options' menu button">
             Options
           </message>
-          <message name="IDS_TRANSLATE_BUBBLE_COULD_NOT_TRANSLATE" desc="Text to show for the translate bubble label when the page could not be translated.">
-            This page could not be translated
-          </message>
           <message name="IDS_TRANSLATE_BUBBLE_PAGE_LANGUAGE" desc="Text to show for the translate bubble label next to the combobox of the page language">
             Page language:
           </message>
           <message name="IDS_TRANSLATE_BUBBLE_TRANSLATION_LANGUAGE" desc="Text to show for the translate bubble label next to the combobox of the target language for Translate">
             Translation language:
           </message>
-          <message name="IDS_TRANSLATE_BUBBLE_LANGUAGE_SETTINGS" desc="Text to show for the translate bubble link label next to jump to the language setting page.">
-            Language settings
-          </message>
           <message name="IDS_TRANSLATE_BUBBLE_ADVANCED_TARGET" desc="Text to show as the title in the advanced source language view for TAB UI">
             Language to translate into
           </message>
@@ -8131,9 +8089,6 @@
           </message>
         </if>
         <if expr="use_titlecase">
-          <message name="IDS_TRANSLATE_BUBBLE_ADVANCED_LINK" desc="In Title Case: Text to show for the translate bubble link label to jump to the advanced panel.">
-            Options
-          </message>
           <message name="IDS_TRANSLATE_BUBBLE_ADVANCED_BUTTON" desc="In Title Case: Text to show for the translate bubble button to jump to the advanced panel.">
             Options
           </message>
@@ -8164,9 +8119,6 @@
           <message name="IDS_TRANSLATE_BUBBLE_TRANSLATING" desc="In Title Case: Text to show for the translate bubble label when page is currently being translated by servers">
             Translating...
           </message>
-          <message name="IDS_TRANSLATE_BUBBLE_TRANSLATED" desc="In Title Case: Text to show for the translate bubble label when the page has been translated from one language to another">
-            This Page Has Been Translated
-          </message>
           <message name="IDS_TRANSLATE_BUBBLE_REVERT" desc="In Title Case: Text to show for the translate bubble button to revert translation of translated page">
             Show Original
           </message>
@@ -8176,24 +8128,15 @@
           <message name="IDS_TRANSLATE_BUBBLE_ALWAYS" desc="In Title Case: Text to show for the translate bubble checkbox to always translate from one language to another">
             Always Translate
           </message>
-          <message name="IDS_TRANSLATE_BUBBLE_ALWAYS_DO_THIS" desc="In Title Case: Text to show for the translate bubble checkbox to always translate from one language to another in 2016Q2 UI">
-            Always Do This
-          </message>
           <message name="IDS_TRANSLATE_BUBBLE_OPTIONS_MENU_BUTTON" desc="In Title Case: Text to show for the translate bubble 'Options' button">
             Options
           </message>
-          <message name="IDS_TRANSLATE_BUBBLE_COULD_NOT_TRANSLATE" desc="In Title Case: Text to show for the translate bubble label when the page could not be translated.">
-            This Page Could Not Be Translated
-          </message>
           <message name="IDS_TRANSLATE_BUBBLE_PAGE_LANGUAGE" desc="In Title Case: Text to show for the translate bubble label next to the combobox of the page language">
             Page Language:
           </message>
           <message name="IDS_TRANSLATE_BUBBLE_TRANSLATION_LANGUAGE" desc="In Title Case: Text to show for the translate bubble label next to the combobox of the target language for Translate">
             Translation Language:
           </message>
-          <message name="IDS_TRANSLATE_BUBBLE_LANGUAGE_SETTINGS" desc="In Title Case: Text to show for the translate bubble link label next to jump to the language setting page.">
-            Language Settings
-          </message>
           <message name="IDS_TRANSLATE_BUBBLE_ADVANCED_TARGET" desc="In Title Case: Text to show as the title in the advanced source language view for TAB UI">
             Language to Translate into
           </message>
diff --git a/chrome/app/settings_chromium_strings.grdp b/chrome/app/settings_chromium_strings.grdp
index fdf2f2b6..0c71911 100644
--- a/chrome/app/settings_chromium_strings.grdp
+++ b/chrome/app/settings_chromium_strings.grdp
@@ -86,6 +86,8 @@
   <message name="IDS_SETTINGS_SIGNIN_ALLOWED_DESC" desc="The description of the preference to allow to sign-in to Chrome">
     By turning this off, you can sign in to Google sites like Gmail without signing in to Chromium
   </message>
+
+  <!-- Safety check -->
   <message name="IDS_SETTINGS_SAFETY_CHECK_PARENT_PRIMARY_LABEL_BEFORE" desc="This text describes what the safety check is. (It's an area of the Settings page where users can quickly check whether their safety-related settings are fully protecting them.)">
     Chromium can help keep you safe from data breaches, bad extensions, and more
   </message>
@@ -95,21 +97,9 @@
   <message name="IDS_SETTINGS_SAFETY_CHECK_UPDATES_FAILED" desc="This text describes that Chromium cannot update due to an unknown error.">
     Chromium didn't update, something went wrong. <ph name="BEGIN_LINK">&lt;a target="_blank" href="$1"&gt;</ph>Fix Chromium update problems and failed updates.<ph name="END_LINK">&lt;/a&gt;</ph>
   </message>
-  <message name="IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_OFFLINE" desc="This text points out that the password check cannot run due to not having internet connection.">
-    Chromium can't check your passwords. Try checking your internet connection.
-  </message>
   <message name="IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_SIGNED_OUT" desc="This text points out that the password check can only run when the user is signed in.">
     Chromium can't check your passwords because you're not signed in
   </message>
-  <message name="IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_QUOTA_LIMIT" desc="This text points out that the password check cannot run and that the user should try again tomorrow.">
-    Chromium can't check all your passwords. Try again after 24 hours.
-  </message>
-  <message name="IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_TOO_MANY_PASSWORDS" desc="This text points out that the password check cannot run due to the user having too many saved passwords.">
-    Chromium can't check your passwords because there are too many
-  </message>
-  <message name="IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_ERROR" desc="This text points out that the password check encountered an unknown error.">
-    Chromium can't check your passwords. Try again later.
-  </message>
   <message name="IDS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_DISABLED" desc="This text points out that Safe Browsing is disabled and that the user is not protected.">
     Safe Browsing is off. Chromium recommends turning it on.
   </message>
diff --git a/chrome/app/settings_google_chrome_strings.grdp b/chrome/app/settings_google_chrome_strings.grdp
index 5b25f5c..b413e3e 100644
--- a/chrome/app/settings_google_chrome_strings.grdp
+++ b/chrome/app/settings_google_chrome_strings.grdp
@@ -86,6 +86,8 @@
   <message name="IDS_SETTINGS_SIGNIN_ALLOWED_DESC" desc="The description of the preference to allow to sign-in to Chrome">
     By turning this off, you can sign in to Google sites like Gmail without signing in to Chrome
   </message>
+
+  <!-- Safety check -->
   <message name="IDS_SETTINGS_SAFETY_CHECK_PARENT_PRIMARY_LABEL_BEFORE" desc="This text describes what the safety check is. (It's an area of the Settings page where users can quickly check whether their safety-related settings are fully protecting them.)">
     Chrome can help keep you safe from data breaches, bad extensions, and more
   </message>
@@ -95,21 +97,9 @@
   <message name="IDS_SETTINGS_SAFETY_CHECK_UPDATES_FAILED" desc="This text describes that Chrome cannot update due to an unknown error.">
     Chrome didn't update, something went wrong. <ph name="BEGIN_LINK">&lt;a target="_blank" href="$1"&gt;</ph>Fix Chrome update problems and failed updates.<ph name="END_LINK">&lt;/a&gt;</ph>
   </message>
-  <message name="IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_OFFLINE" desc="This text points out that the password check cannot run due to not having internet connection.">
-    Chrome can't check your passwords. Try checking your internet connection.
-  </message>
   <message name="IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_SIGNED_OUT" desc="This text points out that the password check can only run when the user is signed in.">
     Chrome can't check your passwords because you're not signed in
   </message>
-  <message name="IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_QUOTA_LIMIT" desc="This text points out that the password check cannot run and that the user should try again tomorrow.">
-    Chrome can't check all your passwords. Try again after 24 hours.
-  </message>
-  <message name="IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_TOO_MANY_PASSWORDS" desc="This text points out that the password check cannot run due to the user having too many saved passwords.">
-    Chrome can't check your passwords because there are too many
-  </message>
-  <message name="IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_ERROR" desc="This text points out that the password check encountered an unknown error.">
-    Chrome can't check your passwords. Try again later.
-  </message>
   <message name="IDS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_DISABLED" desc="This text points out that Safe Browsing is disabled and that the user is not protected.">
     Safe Browsing is off. Chrome recommends turning it on.
   </message>
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index 59b7a17..a7d2b53 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -1608,6 +1608,8 @@
   <message name="IDS_SETTINGS_PRIVACY_MORE" desc="Label on the expansion button to show more privacy settings.">
     More
   </message>
+
+  <!-- Safety check -->
   <message name="IDS_SETTINGS_SAFETY_CHECK_SECTION_TITLE" desc="'Safety check' is a noun phrase (sentence case). 'Safety check' refers to an area of the Settings page where users can quickly check whether their safety-related settings are fully protecting them.">
     Safety check
   </message>
@@ -1677,20 +1679,6 @@
   <message name="IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_PRIMARY_LABEL" desc="'Passwords' is an element in safety check that allows users to check for their passwords being compromised.">
     Passwords
   </message>
-  <message name="IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_PROGRESS" desc="This text describes the progress of the safety check password check.">
-    <ph name="DONE">$1<ex>3</ex></ph>/<ph name="TOTAL">$2<ex>20</ex></ph> passwords checked...
-  </message>
-  <message name="IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_SAFE" desc="This text points out that the safety check password check has not found any compromised passwords.">
-    No compromised passwords found
-  </message>
-  <message name="IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_COMPROMISED" desc="This text points out that the safety check password check has found compromised passwords. The placeholder will be a numeral.">
-    {NUM_PASSWORDS, plural,
-     =1 {1 compromised password}
-     other {{NUM_PASSWORDS} compromised passwords}}
-  </message>
-  <message name="IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_NO_PASSWORDS" desc="This text points out that password check cannot run due to the user not having saved passwords.">
-    No saved passwords
-  </message>
   <message name="IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_BUTTON" desc="This button allows users to change their compromised passwords.">
     {NUM_PASSWORDS, plural,
      =1 {Change password}
@@ -2905,8 +2893,8 @@
     <message name="IDS_SETTINGS_ACCOUNT_MANAGER_ACCOUNT_REMOVED_MESSAGE" desc="Notification message after account removal.">
       <ph name="EMAIL">$1<ex>abcd@google.com</ex></ph> was removed from this device
     </message>
-    <message name="IDS_SETTINGS_ACCOUNT_MANAGER_EDUCATION_ACCOUNT" desc="Status label which indicates that specified account is EDU account.">
-      Education account
+    <message name="IDS_SETTINGS_ACCOUNT_MANAGER_EDUCATION_ACCOUNT" desc="Status label which indicates that specified account is a school/EDU account.">
+      School account
     </message>
     <message name="IDS_SETTINGS_ADD_FINGERPRINT_DIALOG_INSTRUCTION_LOCATE_SCANNER_POWER_BUTTON" desc="Text in the add fingerprint dialog telling users to place finger on the power button which is the sensor.">
       Touch the power button with your finger
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_ACCOUNT_MANAGER_EDUCATION_ACCOUNT.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_ACCOUNT_MANAGER_EDUCATION_ACCOUNT.png.sha1
index 3c03d29f..91a0e5b 100644
--- a/chrome/app/settings_strings_grdp/IDS_SETTINGS_ACCOUNT_MANAGER_EDUCATION_ACCOUNT.png.sha1
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_ACCOUNT_MANAGER_EDUCATION_ACCOUNT.png.sha1
@@ -1 +1 @@
-c683ad17914c9b29ef306bb68aa5b91bcbc76769
\ No newline at end of file
+2ff288850eaf86b68ea8c9f8d982074d1f073741
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 023024e..49c915b1e0 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -682,6 +682,8 @@
     "media/cast_remoting_connector.h",
     "media/feeds/media_feeds_contents_observer.cc",
     "media/feeds/media_feeds_contents_observer.h",
+    "media/feeds/media_feeds_converter.cc",
+    "media/feeds/media_feeds_converter.h",
     "media/feeds/media_feeds_fetcher.cc",
     "media/feeds/media_feeds_fetcher.h",
     "media/feeds/media_feeds_service.cc",
@@ -761,8 +763,6 @@
     "media/webrtc/media_stream_device_permission_context.h",
     "media/webrtc/media_stream_device_permissions.cc",
     "media/webrtc/media_stream_device_permissions.h",
-    "media/webrtc/media_stream_devices_controller.cc",
-    "media/webrtc/media_stream_devices_controller.h",
     "media/webrtc/native_desktop_media_list.cc",
     "media/webrtc/native_desktop_media_list.h",
     "media/webrtc/permission_bubble_media_access_handler.cc",
@@ -2199,6 +2199,7 @@
     "//components/web_resource",
     "//components/webdata/common",
     "//components/webdata_services",
+    "//components/webrtc",
     "//components/webrtc_logging/browser",
     "//components/webrtc_logging/common",
     "//content/app/resources",
@@ -2924,6 +2925,8 @@
       "payments/android/can_make_payment_query_android.cc",
       "payments/android/journey_logger_android.cc",
       "payments/android/journey_logger_android.h",
+      "payments/android/payment_app_service_bridge.cc",
+      "payments/android/payment_app_service_bridge.h",
       "payments/android/payment_manifest_web_data_service_android.cc",
       "payments/android/payment_manifest_web_data_service_android.h",
       "payments/android/service_worker_payment_app_bridge.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 413998bc..2d4dbdb8 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -2272,8 +2272,8 @@
      ENABLE_DISABLE_VALUE_TYPE(switches::kEnableZeroCopy,
                                switches::kDisableZeroCopy)},
     {"enable-vulkan", flag_descriptions::kEnableVulkanName,
-     flag_descriptions::kEnableVulkanDescription, kOsLinux | kOsAndroid,
-     FEATURE_VALUE_TYPE(features::kVulkan)},
+     flag_descriptions::kEnableVulkanDescription,
+     kOsWin | kOsLinux | kOsAndroid, FEATURE_VALUE_TYPE(features::kVulkan)},
 #if defined(OS_MACOSX)
     {"disable-hosted-app-shim-creation",
      flag_descriptions::kHostedAppShimCreationName,
@@ -2741,6 +2741,9 @@
      flag_descriptions::kInterestFeedNotificationsName,
      flag_descriptions::kInterestFeedNotificationsDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(feed::kInterestFeedNotifications)},
+    {"interest-feed-v2", flag_descriptions::kInterestFeedV2Name,
+     flag_descriptions::kInterestFeedV2Description, kOsAndroid,
+     FEATURE_VALUE_TYPE(feed::kInterestFeedV2)},
     {"offlining-recent-pages", flag_descriptions::kOffliningRecentPagesName,
      flag_descriptions::kOffliningRecentPagesDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(offline_pages::kOffliningRecentPagesFeature)},
@@ -4781,7 +4784,7 @@
     {"dns-httpssvc", flag_descriptions::kDnsHttpssvcName,
      flag_descriptions::kDnsHttpssvcDescription,
      kOsMac | kOsWin | kOsCrOS | kOsAndroid,
-     FEATURE_VALUE_TYPE(features::kDnsHttpssvc)},
+     FEATURE_VALUE_TYPE(net::features::kDnsHttpssvc)},
 
     {"dns-over-https", flag_descriptions::kDnsOverHttpsName,
      flag_descriptions::kDnsOverHttpsDescription,
diff --git a/chrome/browser/android/feed/v2/feed_stream_surface.cc b/chrome/browser/android/feed/v2/feed_stream_surface.cc
index c3d51c9b..fe72198e 100644
--- a/chrome/browser/android/feed/v2/feed_stream_surface.cc
+++ b/chrome/browser/android/feed/v2/feed_stream_surface.cc
@@ -7,7 +7,12 @@
 #include "base/android/jni_android.h"
 #include "base/android/jni_array.h"
 #include "chrome/android/chrome_jni_headers/FeedStreamSurface_jni.h"
+#include "chrome/browser/android/feed/v2/feed_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
 #include "components/feed/core/proto/v2/ui.pb.h"
+#include "components/feed/core/v2/public/feed_service.h"
+#include "components/feed/core/v2/public/feed_stream_api.h"
 
 using base::android::JavaParamRef;
 using base::android::JavaRef;
@@ -21,16 +26,31 @@
   return reinterpret_cast<intptr_t>(new FeedStreamSurface(j_this));
 }
 
-FeedStreamSurface::FeedStreamSurface(const JavaRef<jobject>& j_this) {
+FeedStreamSurface::FeedStreamSurface(const JavaRef<jobject>& j_this)
+    : feed_stream_api_(nullptr) {
   java_ref_.Reset(j_this);
+
+  // TODO(iwells): check that this profile is okay to use. what about first run?
+  Profile* profile = ProfileManager::GetLastUsedProfile();
+  if (!profile)
+    return;
+
+  feed_stream_api_ =
+      FeedServiceFactory::GetForBrowserContext(profile)->GetStream();
+  if (feed_stream_api_)
+    feed_stream_api_->AttachSurface(this);
 }
 
-FeedStreamSurface::~FeedStreamSurface() {}
+FeedStreamSurface::~FeedStreamSurface() {
+  if (feed_stream_api_)
+    feed_stream_api_->DetachSurface(this);
+}
 
-void FeedStreamSurface::OnStreamUpdated(
+void FeedStreamSurface::StreamUpdate(
     const feedui::StreamUpdate& stream_update) {
   JNIEnv* env = base::android::AttachCurrentThread();
   int32_t data_size = stream_update.ByteSize();
+
   std::vector<uint8_t> data;
   data.resize(data_size);
   stream_update.SerializeToArray(data.data(), data_size);
diff --git a/chrome/browser/android/feed/v2/feed_stream_surface.h b/chrome/browser/android/feed/v2/feed_stream_surface.h
index 9b80aa2a..b23738b 100644
--- a/chrome/browser/android/feed/v2/feed_stream_surface.h
+++ b/chrome/browser/android/feed/v2/feed_stream_surface.h
@@ -8,6 +8,7 @@
 #include "base/android/jni_android.h"
 #include "base/android/scoped_java_ref.h"
 #include "base/macros.h"
+#include "components/feed/core/v2/public/feed_stream_api.h"
 
 namespace feedui {
 class StreamUpdate;
@@ -17,10 +18,13 @@
 
 // Native access to |FeedStreamSurface| in Java.
 // Created once for each NTP/start surface.
-class FeedStreamSurface {
+class FeedStreamSurface : public FeedStreamApi::SurfaceInterface {
  public:
   explicit FeedStreamSurface(const base::android::JavaRef<jobject>& j_this);
-  ~FeedStreamSurface();
+  ~FeedStreamSurface() override;
+
+  // SurfaceInterface implementation.
+  void StreamUpdate(const feedui::StreamUpdate& update) override;
 
   void OnStreamUpdated(const feedui::StreamUpdate& stream_update);
 
@@ -63,7 +67,7 @@
 
  private:
   base::android::ScopedJavaGlobalRef<jobject> java_ref_;
-
+  FeedStreamApi* feed_stream_api_;
   DISALLOW_COPY_AND_ASSIGN(FeedStreamSurface);
 };
 
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 9c3e16ca..82980a63 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -278,6 +278,7 @@
 
     # TODO: care about enable_basic_printing and enable_print_preview.
     "//ash/keyboard/ui",
+    "//chromeos/services/assistant/public/cpp:interaction_subscriber",
     "//printing",
     "//remoting/host/it2me:chrome_os_host",
     "//services/audio/public/cpp",
diff --git a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
index 5d781e4..fc94885 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
@@ -109,7 +109,7 @@
 #include "chromeos/dbus/session_manager/session_manager_client.h"
 #include "chromeos/printing/printer_configuration.h"
 #include "chromeos/services/assistant/public/cpp/assistant_prefs.h"
-#include "chromeos/services/assistant/public/mojom/assistant.mojom.h"
+#include "chromeos/services/assistant/public/cpp/default_assistant_interaction_subscriber.h"
 #include "chromeos/services/machine_learning/public/cpp/service_connection.h"
 #include "chromeos/settings/cros_settings_names.h"
 #include "components/arc/arc_prefs.h"
@@ -2290,7 +2290,7 @@
 // |AutotestPrivateSendAssistantTextQueryFunction| and
 // |AutotestPrivateWaitForAssistantQueryStatusFunction|.
 class AssistantInteractionHelper
-    : public chromeos::assistant::mojom::AssistantInteractionSubscriber {
+    : public chromeos::assistant::DefaultAssistantInteractionSubscriber {
  public:
   using OnInteractionFinishedCallback = base::OnceCallback<void(bool)>;
 
@@ -2305,8 +2305,7 @@
         assistant_.BindNewPipeAndPassReceiver());
 
     // Subscribe to Assistant interaction events.
-    assistant_->AddAssistantInteractionSubscriber(
-        assistant_interaction_subscriber_receiver_.BindNewPipeAndPassRemote());
+    assistant_->AddAssistantInteractionSubscriber(BindNewPipeAndPassRemote());
 
     on_interaction_finished_callback_ =
         std::move(on_interaction_finished_callback);
@@ -2383,21 +2382,7 @@
     std::move(callback).Run(true);
   }
 
-  void OnSuggestionsResponse(std::vector<AssistantSuggestionPtr>) override {}
-  void OnTimersResponse(const std::vector<std::string>& timer_ids) override {}
-  void OnOpenUrlResponse(const GURL& url, bool in_background) override {}
-  void OnSpeechRecognitionStarted() override {}
-  void OnSpeechRecognitionIntermediateResult(
-      const std::string& high_confidence_text,
-      const std::string& low_confidence_text) override {}
-  void OnSpeechRecognitionEndOfUtterance() override {}
-  void OnSpeechLevelUpdated(float speech_level) override {}
-  void OnTtsStarted(bool due_to_error) override {}
-  void OnWaitStarted() override {}
-
   mojo::Remote<chromeos::assistant::mojom::Assistant> assistant_;
-  mojo::Receiver<chromeos::assistant::mojom::AssistantInteractionSubscriber>
-      assistant_interaction_subscriber_receiver_{this};
   std::unique_ptr<base::DictionaryValue> query_status_;
   base::DictionaryValue result_;
 
diff --git a/chrome/browser/chromeos/policy/remote_commands/device_command_run_routine_job_unittest.cc b/chrome/browser/chromeos/policy/remote_commands/device_command_run_routine_job_unittest.cc
index f13054d..2abfac1 100644
--- a/chrome/browser/chromeos/policy/remote_commands/device_command_run_routine_job_unittest.cc
+++ b/chrome/browser/chromeos/policy/remote_commands/device_command_run_routine_job_unittest.cc
@@ -78,11 +78,19 @@
 
 // Dummy values to populate cros_healthd's RunRoutineResponse.
 constexpr uint32_t kId = 11;
-constexpr chromeos::cros_healthd::mojom::DiagnosticRoutineStatusEnum kStatus =
+constexpr auto kStatus =
     chromeos::cros_healthd::mojom::DiagnosticRoutineStatusEnum::kRunning;
 
 constexpr RemoteCommandJob::UniqueIDType kUniqueID = 987123;
 
+constexpr int kPositiveInt = 8789;
+constexpr int kNegativeInt = -231;
+constexpr auto kValidAcPowerStatusEnum =
+    chromeos::cros_healthd::mojom::AcPowerStatusEnum::kConnected;
+constexpr char kValidExpectedAcPowerType[] = "power_type";
+constexpr auto kValidDiskReadRoutineTypeEnum =
+    chromeos::cros_healthd::mojom::DiskReadRoutineTypeEnum::kLinearRead;
+
 em::RemoteCommand GenerateCommandProto(
     RemoteCommandJob::UniqueIDType unique_id,
     base::TimeDelta age_of_command,
@@ -214,19 +222,18 @@
 }
 
 TEST_F(DeviceCommandRunRoutineJobTest, InvalidRoutineEnumInCommandPayload) {
+  constexpr auto kInvalidRoutineEnum = static_cast<
+      chromeos::cros_healthd::mojom::DiagnosticRoutineEnum>(
+      std::numeric_limits<std::underlying_type<
+          chromeos::cros_healthd::mojom::DiagnosticRoutineEnum>::type>::max());
   auto job = std::make_unique<DeviceCommandRunRoutineJob>();
   base::Value params_dict(base::Value::Type::DICTIONARY);
   EXPECT_FALSE(job->Init(
       base::TimeTicks::Now(),
-      GenerateCommandProto(
-          kUniqueID, base::TimeTicks::Now() - test_start_time_,
-          base::TimeDelta::FromSeconds(30),
-          /*terminate_upon_input=*/false,
-          static_cast<chromeos::cros_healthd::mojom::DiagnosticRoutineEnum>(
-              std::numeric_limits<std::underlying_type<
-                  chromeos::cros_healthd::mojom::DiagnosticRoutineEnum>::type>::
-                  max()),
-          std::move(params_dict)),
+      GenerateCommandProto(kUniqueID, base::TimeTicks::Now() - test_start_time_,
+                           base::TimeDelta::FromSeconds(30),
+                           /*terminate_upon_input=*/false, kInvalidRoutineEnum,
+                           std::move(params_dict)),
       nullptr));
 
   EXPECT_EQ(kUniqueID, job->unique_id());
@@ -252,15 +259,15 @@
 // Test that not including a parameters dictionary causes the routine
 // initialization to fail.
 TEST_F(DeviceCommandRunRoutineJobTest, CommandPayloadMissingParamDict) {
+  constexpr auto kValidRoutineEnum =
+      chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kSmartctlCheck;
   auto job = std::make_unique<DeviceCommandRunRoutineJob>();
   EXPECT_FALSE(job->Init(
       base::TimeTicks::Now(),
-      GenerateCommandProto(
-          kUniqueID, base::TimeTicks::Now() - test_start_time_,
-          base::TimeDelta::FromSeconds(30),
-          /*terminate_upon_input=*/false,
-          chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kSmartctlCheck,
-          /*params=*/base::nullopt),
+      GenerateCommandProto(kUniqueID, base::TimeTicks::Now() - test_start_time_,
+                           base::TimeDelta::FromSeconds(30),
+                           /*terminate_upon_input=*/false, kValidRoutineEnum,
+                           /*params=*/base::nullopt),
       nullptr));
 
   EXPECT_EQ(kUniqueID, job->unique_id());
@@ -273,8 +280,8 @@
   chromeos::cros_healthd::FakeCrosHealthdClient::Get()
       ->SetRunRoutineResponseForTesting(run_routine_response);
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kLowMahFieldName, /*low_mah=*/90812);
-  params_dict.SetIntKey(kHighMahFieldName, /*high_mah=*/986909);
+  params_dict.SetIntKey(kLowMahFieldName, kPositiveInt);
+  params_dict.SetIntKey(kHighMahFieldName, kPositiveInt);
   EXPECT_TRUE(RunJob(
       chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kBatteryCapacity,
       std::move(params_dict),
@@ -290,7 +297,7 @@
 // routine to fail.
 TEST_F(DeviceCommandRunRoutineJobTest, RunBatteryCapacityRoutineMissingLowMah) {
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kHighMahFieldName, /*high_mah=*/986909);
+  params_dict.SetIntKey(kHighMahFieldName, kPositiveInt);
   EXPECT_TRUE(RunJob(
       chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kBatteryCapacity,
       std::move(params_dict),
@@ -307,7 +314,7 @@
 TEST_F(DeviceCommandRunRoutineJobTest,
        RunBatteryCapacityRoutineMissingHighMah) {
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kLowMahFieldName, /*low_mah=*/90812);
+  params_dict.SetIntKey(kLowMahFieldName, kPositiveInt);
   EXPECT_TRUE(RunJob(
       chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kBatteryCapacity,
       std::move(params_dict),
@@ -323,8 +330,8 @@
 // fail.
 TEST_F(DeviceCommandRunRoutineJobTest, RunBatteryCapacityRoutineInvalidLowMah) {
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kLowMahFieldName, /*low_mah=*/-1);
-  params_dict.SetIntKey(kHighMahFieldName, /*high_mah=*/986909);
+  params_dict.SetIntKey(kLowMahFieldName, kNegativeInt);
+  params_dict.SetIntKey(kHighMahFieldName, kPositiveInt);
   EXPECT_TRUE(RunJob(
       chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kBatteryCapacity,
       std::move(params_dict),
@@ -341,8 +348,8 @@
 TEST_F(DeviceCommandRunRoutineJobTest,
        RunBatteryCapacityRoutineInvalidHighMah) {
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kLowMahFieldName, /*low_mah=*/90812);
-  params_dict.SetIntKey(kHighMahFieldName, /*high_mah=*/-1);
+  params_dict.SetIntKey(kLowMahFieldName, kPositiveInt);
+  params_dict.SetIntKey(kHighMahFieldName, kNegativeInt);
   EXPECT_TRUE(RunJob(
       chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kBatteryCapacity,
       std::move(params_dict),
@@ -360,10 +367,8 @@
   chromeos::cros_healthd::FakeCrosHealthdClient::Get()
       ->SetRunRoutineResponseForTesting(run_routine_response);
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kMaximumCycleCountFieldName,
-                        /*maximum_cycle_count=*/12);
-  params_dict.SetIntKey(kPercentBatteryWearAllowedFieldName,
-                        /*percent_battery_wear_allowed=*/78);
+  params_dict.SetIntKey(kMaximumCycleCountFieldName, kPositiveInt);
+  params_dict.SetIntKey(kPercentBatteryWearAllowedFieldName, kPositiveInt);
   EXPECT_TRUE(RunJob(
       chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kBatteryHealth,
       std::move(params_dict),
@@ -380,8 +385,7 @@
 TEST_F(DeviceCommandRunRoutineJobTest,
        RunBatteryHealthRoutineMissingMaximumCycleCount) {
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kPercentBatteryWearAllowedFieldName,
-                        /*percent_battery_wear_allowed=*/78);
+  params_dict.SetIntKey(kPercentBatteryWearAllowedFieldName, kPositiveInt);
   EXPECT_TRUE(RunJob(
       chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kBatteryHealth,
       std::move(params_dict),
@@ -398,8 +402,7 @@
 TEST_F(DeviceCommandRunRoutineJobTest,
        RunBatteryHealthRoutineMissingPercentBatteryWearAllowed) {
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kMaximumCycleCountFieldName,
-                        /*maximum_cycle_count=*/12);
+  params_dict.SetIntKey(kMaximumCycleCountFieldName, kPositiveInt);
   EXPECT_TRUE(RunJob(
       chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kBatteryHealth,
       std::move(params_dict),
@@ -416,10 +419,8 @@
 TEST_F(DeviceCommandRunRoutineJobTest,
        RunBatteryHealthRoutineInvalidMaximumCycleCount) {
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kMaximumCycleCountFieldName,
-                        /*maximum_cycle_count=*/-1);
-  params_dict.SetIntKey(kPercentBatteryWearAllowedFieldName,
-                        /*percent_battery_wear_allowed=*/78);
+  params_dict.SetIntKey(kMaximumCycleCountFieldName, kNegativeInt);
+  params_dict.SetIntKey(kPercentBatteryWearAllowedFieldName, kPositiveInt);
   EXPECT_TRUE(RunJob(
       chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kBatteryHealth,
       std::move(params_dict),
@@ -436,10 +437,8 @@
 TEST_F(DeviceCommandRunRoutineJobTest,
        RunBatteryHealthRoutineInvalidPercentBatteryWearAllowed) {
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kMaximumCycleCountFieldName,
-                        /*maximum_cycle_count=*/12);
-  params_dict.SetIntKey(kPercentBatteryWearAllowedFieldName,
-                        /*percent_battery_wear_allowed=*/-1);
+  params_dict.SetIntKey(kMaximumCycleCountFieldName, kPositiveInt);
+  params_dict.SetIntKey(kPercentBatteryWearAllowedFieldName, kNegativeInt);
   EXPECT_TRUE(RunJob(
       chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kBatteryHealth,
       std::move(params_dict),
@@ -457,8 +456,7 @@
   chromeos::cros_healthd::FakeCrosHealthdClient::Get()
       ->SetRunRoutineResponseForTesting(run_routine_response);
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kLengthSecondsFieldName,
-                        /*length_seconds=*/2342);
+  params_dict.SetIntKey(kLengthSecondsFieldName, kPositiveInt);
   EXPECT_TRUE(
       RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kUrandom,
              std::move(params_dict),
@@ -489,8 +487,7 @@
 // fail.
 TEST_F(DeviceCommandRunRoutineJobTest, RunUrandomRoutineInvalidLengthSeconds) {
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kLengthSecondsFieldName,
-                        /*length_seconds=*/-1);
+  params_dict.SetIntKey(kLengthSecondsFieldName, kNegativeInt);
   EXPECT_TRUE(
       RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kUrandom,
              std::move(params_dict),
@@ -528,12 +525,10 @@
   chromeos::cros_healthd::FakeCrosHealthdClient::Get()
       ->SetRunRoutineResponseForTesting(run_routine_response);
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(
-      kExpectedStatusFieldName,
-      /*expected_status=*/static_cast<int>(
-          chromeos::cros_healthd::mojom::AcPowerStatusEnum::kConnected));
+  params_dict.SetIntKey(kExpectedStatusFieldName,
+                        static_cast<int>(kValidAcPowerStatusEnum));
   params_dict.SetStringKey(kExpectedPowerTypeFieldName,
-                           /*expected_power_type=*/"power_type");
+                           kValidExpectedAcPowerType);
   EXPECT_TRUE(
       RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kAcPower,
              std::move(params_dict),
@@ -554,10 +549,8 @@
   chromeos::cros_healthd::FakeCrosHealthdClient::Get()
       ->SetRunRoutineResponseForTesting(run_routine_response);
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(
-      kExpectedStatusFieldName,
-      /*expected_status=*/static_cast<int>(
-          chromeos::cros_healthd::mojom::AcPowerStatusEnum::kConnected));
+  params_dict.SetIntKey(kExpectedStatusFieldName,
+                        static_cast<int>(kValidAcPowerStatusEnum));
   EXPECT_TRUE(
       RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kAcPower,
              std::move(params_dict),
@@ -574,7 +567,7 @@
 TEST_F(DeviceCommandRunRoutineJobTest, RunAcPowerRoutineMissingExpectedStatus) {
   base::Value params_dict(base::Value::Type::DICTIONARY);
   params_dict.SetStringKey(kExpectedPowerTypeFieldName,
-                           /*expected_power_type=*/"power_type");
+                           kValidExpectedAcPowerType);
   EXPECT_TRUE(
       RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kAcPower,
              std::move(params_dict),
@@ -589,15 +582,15 @@
 // Test that an invalid value for the expectedStatus parameter causes the AC
 // power routine to fail.
 TEST_F(DeviceCommandRunRoutineJobTest, RunAcPowerRoutineInvalidExpectedStatus) {
-  base::Value params_dict(base::Value::Type::DICTIONARY);
-  auto expected_status =
+  constexpr auto kInvalidAcPowerStatusEnum =
       static_cast<chromeos::cros_healthd::mojom::AcPowerStatusEnum>(
           std::numeric_limits<std::underlying_type<
               chromeos::cros_healthd::mojom::AcPowerStatusEnum>::type>::max());
+  base::Value params_dict(base::Value::Type::DICTIONARY);
   params_dict.SetIntKey(kExpectedStatusFieldName,
-                        static_cast<int>(expected_status));
+                        static_cast<int>(kInvalidAcPowerStatusEnum));
   params_dict.SetStringKey(kExpectedPowerTypeFieldName,
-                           /*expected_power_type=*/"power_type");
+                           kValidExpectedAcPowerType);
   EXPECT_TRUE(
       RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kAcPower,
              std::move(params_dict),
@@ -615,8 +608,7 @@
   chromeos::cros_healthd::FakeCrosHealthdClient::Get()
       ->SetRunRoutineResponseForTesting(run_routine_response);
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kLengthSecondsFieldName,
-                        /*length_seconds=*/2342);
+  params_dict.SetIntKey(kLengthSecondsFieldName, kPositiveInt);
   EXPECT_TRUE(
       RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kCpuCache,
              std::move(params_dict),
@@ -647,8 +639,7 @@
 // fail.
 TEST_F(DeviceCommandRunRoutineJobTest, RunCpuCacheRoutineInvalidLengthSeconds) {
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kLengthSecondsFieldName,
-                        /*length_seconds=*/-1);
+  params_dict.SetIntKey(kLengthSecondsFieldName, kNegativeInt);
   EXPECT_TRUE(
       RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kCpuCache,
              std::move(params_dict),
@@ -666,8 +657,7 @@
   chromeos::cros_healthd::FakeCrosHealthdClient::Get()
       ->SetRunRoutineResponseForTesting(run_routine_response);
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kLengthSecondsFieldName,
-                        /*length_seconds=*/2342);
+  params_dict.SetIntKey(kLengthSecondsFieldName, kPositiveInt);
   EXPECT_TRUE(
       RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kCpuStress,
              std::move(params_dict),
@@ -700,8 +690,7 @@
 TEST_F(DeviceCommandRunRoutineJobTest,
        RunCpuStressRoutineInvalidLengthSeconds) {
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kLengthSecondsFieldName,
-                        /*length_seconds=*/-1);
+  params_dict.SetIntKey(kLengthSecondsFieldName, kNegativeInt);
   EXPECT_TRUE(
       RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kCpuStress,
              std::move(params_dict),
@@ -719,8 +708,7 @@
   chromeos::cros_healthd::FakeCrosHealthdClient::Get()
       ->SetRunRoutineResponseForTesting(run_routine_response);
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kLengthSecondsFieldName,
-                        /*length_seconds=*/2342);
+  params_dict.SetIntKey(kLengthSecondsFieldName, kPositiveInt);
   EXPECT_TRUE(RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::
                          kFloatingPointAccuracy,
                      std::move(params_dict),
@@ -755,8 +743,7 @@
 TEST_F(DeviceCommandRunRoutineJobTest,
        RunFloatingPointAccuracyRoutineInvalidLengthSeconds) {
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kLengthSecondsFieldName,
-                        /*length_seconds=*/-1);
+  params_dict.SetIntKey(kLengthSecondsFieldName, kNegativeInt);
   EXPECT_TRUE(
       RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::
                  kFloatingPointAccuracy,
@@ -775,8 +762,7 @@
   chromeos::cros_healthd::FakeCrosHealthdClient::Get()
       ->SetRunRoutineResponseForTesting(run_routine_response);
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kWearLevelThresholdFieldName,
-                        /*wear_level_threshold=*/50);
+  params_dict.SetIntKey(kWearLevelThresholdFieldName, kPositiveInt);
   EXPECT_TRUE(RunJob(
       chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kNvmeWearLevel,
       std::move(params_dict),
@@ -809,8 +795,7 @@
 TEST_F(DeviceCommandRunRoutineJobTest,
        RunNvmeWearLevelRoutineInvalidWearLevelThreshold) {
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kWearLevelThresholdFieldName,
-                        /*wear_level_threshold=*/-1);
+  params_dict.SetIntKey(kWearLevelThresholdFieldName, kNegativeInt);
   EXPECT_TRUE(RunJob(
       chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kNvmeWearLevel,
       std::move(params_dict),
@@ -823,15 +808,15 @@
 }
 
 TEST_F(DeviceCommandRunRoutineJobTest, RunNvmeSelfTestRoutineSuccess) {
+  constexpr auto kValidNvmeSelfTestTypeEnum =
+      chromeos::cros_healthd::mojom::NvmeSelfTestTypeEnum::kShortSelfTest;
   auto run_routine_response =
       chromeos::cros_healthd::mojom::RunRoutineResponse::New(kId, kStatus);
   chromeos::cros_healthd::FakeCrosHealthdClient::Get()
       ->SetRunRoutineResponseForTesting(run_routine_response);
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(
-      kNvmeSelfTestTypeFieldName,
-      /*nvme_self_test_type=*/static_cast<int>(
-          chromeos::cros_healthd::mojom::NvmeSelfTestTypeEnum::kShortSelfTest));
+  params_dict.SetIntKey(kNvmeSelfTestTypeFieldName,
+                        static_cast<int>(kValidNvmeSelfTestTypeEnum));
   EXPECT_TRUE(RunJob(
       chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kNvmeSelfTest,
       std::move(params_dict),
@@ -863,11 +848,13 @@
 // self test routine to fail.
 TEST_F(DeviceCommandRunRoutineJobTest,
        RunNvmeSelfTestRoutineInvalidSelfTestType) {
+  constexpr auto kInvalidNvmeSelfTestTypeEnum = static_cast<
+      chromeos::cros_healthd::mojom::NvmeSelfTestTypeEnum>(
+      std::numeric_limits<std::underlying_type<
+          chromeos::cros_healthd::mojom::NvmeSelfTestTypeEnum>::type>::max());
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  auto nvme_self_test_type = std::numeric_limits<std::underlying_type<
-      chromeos::cros_healthd::mojom::NvmeSelfTestTypeEnum>::type>::max();
   params_dict.SetIntKey(kNvmeSelfTestTypeFieldName,
-                        static_cast<int>(nvme_self_test_type));
+                        static_cast<int>(kInvalidNvmeSelfTestTypeEnum));
   EXPECT_TRUE(RunJob(
       chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kNvmeSelfTest,
       std::move(params_dict),
@@ -886,14 +873,10 @@
   chromeos::cros_healthd::FakeCrosHealthdClient::Get()
       ->SetRunRoutineResponseForTesting(run_routine_response);
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(
-      kTypeFieldName,
-      /*type=*/static_cast<int>(
-          chromeos::cros_healthd::mojom::DiskReadRoutineTypeEnum::kLinearRead));
-  params_dict.SetIntKey(kLengthSecondsFieldName,
-                        /*length_seconds=*/2342);
-  params_dict.SetIntKey(kFileSizeMbFieldName,
-                        /*file_size_mb=*/512);
+  params_dict.SetIntKey(kTypeFieldName,
+                        static_cast<int>(kValidDiskReadRoutineTypeEnum));
+  params_dict.SetIntKey(kLengthSecondsFieldName, kPositiveInt);
+  params_dict.SetIntKey(kFileSizeMbFieldName, kPositiveInt);
   EXPECT_TRUE(
       RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kDiskRead,
              std::move(params_dict),
@@ -909,10 +892,8 @@
 // fail.
 TEST_F(DeviceCommandRunRoutineJobTest, RunDiskReadRoutineMissingType) {
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kLengthSecondsFieldName,
-                        /*length_seconds=*/2342);
-  params_dict.SetIntKey(kFileSizeMbFieldName,
-                        /*file_size_mb=*/512);
+  params_dict.SetIntKey(kLengthSecondsFieldName, kPositiveInt);
+  params_dict.SetIntKey(kFileSizeMbFieldName, kPositiveInt);
   EXPECT_TRUE(
       RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kDiskRead,
              std::move(params_dict),
@@ -928,12 +909,9 @@
 // routine to fail.
 TEST_F(DeviceCommandRunRoutineJobTest, RunDiskReadRoutineMissingLengthSeconds) {
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(
-      kTypeFieldName,
-      /*type=*/static_cast<int>(
-          chromeos::cros_healthd::mojom::DiskReadRoutineTypeEnum::kLinearRead));
-  params_dict.SetIntKey(kFileSizeMbFieldName,
-                        /*file_size_mb=*/512);
+  params_dict.SetIntKey(kTypeFieldName,
+                        static_cast<int>(kValidDiskReadRoutineTypeEnum));
+  params_dict.SetIntKey(kFileSizeMbFieldName, kPositiveInt);
   EXPECT_TRUE(
       RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kDiskRead,
              std::move(params_dict),
@@ -949,12 +927,9 @@
 // to fail.
 TEST_F(DeviceCommandRunRoutineJobTest, RunDiskReadRoutineMissingFileSizeMb) {
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(
-      kTypeFieldName,
-      /*type=*/static_cast<int>(
-          chromeos::cros_healthd::mojom::DiskReadRoutineTypeEnum::kLinearRead));
-  params_dict.SetIntKey(kLengthSecondsFieldName,
-                        /*length_seconds=*/2342);
+  params_dict.SetIntKey(kTypeFieldName,
+                        static_cast<int>(kValidDiskReadRoutineTypeEnum));
+  params_dict.SetIntKey(kLengthSecondsFieldName, kPositiveInt);
   EXPECT_TRUE(
       RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kDiskRead,
              std::move(params_dict),
@@ -969,17 +944,16 @@
 // Test that an invalid value for the type parameter causes the disk read
 // routine to fail.
 TEST_F(DeviceCommandRunRoutineJobTest, RunDiskReadRoutineInvalidType) {
-  base::Value params_dict(base::Value::Type::DICTIONARY);
-  auto type =
+  constexpr auto kInvalidDiskReadRoutineTypeEnum =
       static_cast<chromeos::cros_healthd::mojom::DiskReadRoutineTypeEnum>(
           std::numeric_limits<std::underlying_type<
               chromeos::cros_healthd::mojom::DiskReadRoutineTypeEnum>::type>::
               max());
-  params_dict.SetIntKey(kTypeFieldName, static_cast<int>(type));
-  params_dict.SetIntKey(kLengthSecondsFieldName,
-                        /*length_seconds=*/2342);
-  params_dict.SetIntKey(kFileSizeMbFieldName,
-                        /*file_size_mb=*/512);
+  base::Value params_dict(base::Value::Type::DICTIONARY);
+  params_dict.SetIntKey(kTypeFieldName,
+                        static_cast<int>(kInvalidDiskReadRoutineTypeEnum));
+  params_dict.SetIntKey(kLengthSecondsFieldName, kPositiveInt);
+  params_dict.SetIntKey(kFileSizeMbFieldName, kPositiveInt);
   EXPECT_TRUE(
       RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kDiskRead,
              std::move(params_dict),
@@ -995,14 +969,10 @@
 // read routine to fail.
 TEST_F(DeviceCommandRunRoutineJobTest, RunDiskReadRoutineInvalidLengthSeconds) {
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(
-      kTypeFieldName,
-      /*type=*/static_cast<int>(
-          chromeos::cros_healthd::mojom::DiskReadRoutineTypeEnum::kLinearRead));
-  params_dict.SetIntKey(kLengthSecondsFieldName,
-                        /*length_seconds=*/-1);
-  params_dict.SetIntKey(kFileSizeMbFieldName,
-                        /*file_size_mb=*/512);
+  params_dict.SetIntKey(kTypeFieldName,
+                        static_cast<int>(kValidDiskReadRoutineTypeEnum));
+  params_dict.SetIntKey(kLengthSecondsFieldName, kNegativeInt);
+  params_dict.SetIntKey(kFileSizeMbFieldName, kPositiveInt);
   EXPECT_TRUE(
       RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kDiskRead,
              std::move(params_dict),
@@ -1018,14 +988,10 @@
 // routine to fail.
 TEST_F(DeviceCommandRunRoutineJobTest, RunDiskReadRoutineInvalidFileSizeMb) {
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(
-      kTypeFieldName,
-      /*type=*/static_cast<int>(
-          chromeos::cros_healthd::mojom::DiskReadRoutineTypeEnum::kLinearRead));
-  params_dict.SetIntKey(kLengthSecondsFieldName,
-                        /*length_seconds=*/2342);
-  params_dict.SetIntKey(kFileSizeMbFieldName,
-                        /*file_size_mb=*/-1);
+  params_dict.SetIntKey(kTypeFieldName,
+                        static_cast<int>(kValidDiskReadRoutineTypeEnum));
+  params_dict.SetIntKey(kLengthSecondsFieldName, kPositiveInt);
+  params_dict.SetIntKey(kFileSizeMbFieldName, kNegativeInt);
   EXPECT_TRUE(
       RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kDiskRead,
              std::move(params_dict),
@@ -1044,10 +1010,8 @@
   chromeos::cros_healthd::FakeCrosHealthdClient::Get()
       ->SetRunRoutineResponseForTesting(run_routine_response);
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kLengthSecondsFieldName,
-                        /*length_seconds=*/2342);
-  params_dict.SetIntKey(kMaxNumFieldName,
-                        /*max_num=*/100000);
+  params_dict.SetIntKey(kLengthSecondsFieldName, kPositiveInt);
+  params_dict.SetIntKey(kMaxNumFieldName, kPositiveInt);
   EXPECT_TRUE(
       RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kPrimeSearch,
              std::move(params_dict),
@@ -1064,8 +1028,7 @@
 TEST_F(DeviceCommandRunRoutineJobTest,
        RunPrimeSearchRoutineMissingLengthSeconds) {
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kMaxNumFieldName,
-                        /*max_num=*/100000);
+  params_dict.SetIntKey(kMaxNumFieldName, kPositiveInt);
   EXPECT_TRUE(
       RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kPrimeSearch,
              std::move(params_dict),
@@ -1081,8 +1044,7 @@
 // fail.
 TEST_F(DeviceCommandRunRoutineJobTest, RunPrimeSearchRoutineMissingMaxNum) {
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kLengthSecondsFieldName,
-                        /*length_seconds=*/2342);
+  params_dict.SetIntKey(kLengthSecondsFieldName, kPositiveInt);
   EXPECT_TRUE(
       RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kPrimeSearch,
              std::move(params_dict),
@@ -1099,10 +1061,8 @@
 TEST_F(DeviceCommandRunRoutineJobTest,
        RunPrimeSearchRoutineInvalidLengthSeconds) {
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kLengthSecondsFieldName,
-                        /*length_seconds=*/-1);
-  params_dict.SetIntKey(kMaxNumFieldName,
-                        /*max_num=*/100000);
+  params_dict.SetIntKey(kLengthSecondsFieldName, kNegativeInt);
+  params_dict.SetIntKey(kMaxNumFieldName, kPositiveInt);
   EXPECT_TRUE(
       RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kPrimeSearch,
              std::move(params_dict),
@@ -1118,10 +1078,8 @@
 // routine to fail.
 TEST_F(DeviceCommandRunRoutineJobTest, RunPrimeSearchRoutineInvalidMaxNum) {
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kLengthSecondsFieldName,
-                        /*length_seconds=*/2342);
-  params_dict.SetIntKey(kMaxNumFieldName,
-                        /*max_num=*/-1);
+  params_dict.SetIntKey(kLengthSecondsFieldName, kPositiveInt);
+  params_dict.SetIntKey(kMaxNumFieldName, kNegativeInt);
   EXPECT_TRUE(
       RunJob(chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kPrimeSearch,
              std::move(params_dict),
@@ -1139,9 +1097,8 @@
   chromeos::cros_healthd::FakeCrosHealthdClient::Get()
       ->SetRunRoutineResponseForTesting(run_routine_response);
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kLengthSecondsFieldName, /*length_seconds=*/10);
-  params_dict.SetIntKey(kMaximumDischargePercentAllowedFieldName,
-                        /*maximum_discharge_percent_allowed=*/76);
+  params_dict.SetIntKey(kLengthSecondsFieldName, kPositiveInt);
+  params_dict.SetIntKey(kMaximumDischargePercentAllowedFieldName, kPositiveInt);
   EXPECT_TRUE(RunJob(
       chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kBatteryDischarge,
       std::move(params_dict),
@@ -1158,8 +1115,7 @@
   // Test that leaving out the lengthSeconds parameter causes the routine to
   // fail.
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kMaximumDischargePercentAllowedFieldName,
-                        /*maximum_discharge_percent_allowed=*/76);
+  params_dict.SetIntKey(kMaximumDischargePercentAllowedFieldName, kPositiveInt);
   EXPECT_TRUE(RunJob(
       chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kBatteryDischarge,
       std::move(params_dict),
@@ -1176,7 +1132,7 @@
   // Test that leaving out the maximumDischargePercentAllowed parameter causes
   // the routine to fail.
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kLengthSecondsFieldName, /*length_seconds=*/10);
+  params_dict.SetIntKey(kLengthSecondsFieldName, kPositiveInt);
   EXPECT_TRUE(RunJob(
       chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kBatteryDischarge,
       std::move(params_dict),
@@ -1192,9 +1148,8 @@
        RunBatteryDischargeRoutineInvalidLengthSeconds) {
   // Test that a negative lengthSeconds parameter causes the routine to fail.
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kLengthSecondsFieldName, /*length_seconds=*/-10);
-  params_dict.SetIntKey(kMaximumDischargePercentAllowedFieldName,
-                        /*maximum_discharge_percent_allowed=*/76);
+  params_dict.SetIntKey(kLengthSecondsFieldName, kNegativeInt);
+  params_dict.SetIntKey(kMaximumDischargePercentAllowedFieldName, kPositiveInt);
   EXPECT_TRUE(RunJob(
       chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kBatteryDischarge,
       std::move(params_dict),
@@ -1211,9 +1166,8 @@
   // Test that a negative maximumDischargePercentAllowed parameter causes the
   // routine to fail.
   base::Value params_dict(base::Value::Type::DICTIONARY);
-  params_dict.SetIntKey(kLengthSecondsFieldName, /*length_seconds=*/10);
-  params_dict.SetIntKey(kMaximumDischargePercentAllowedFieldName,
-                        /*maximum_discharge_percent_allowed=*/-76);
+  params_dict.SetIntKey(kLengthSecondsFieldName, kPositiveInt);
+  params_dict.SetIntKey(kMaximumDischargePercentAllowedFieldName, kNegativeInt);
   EXPECT_TRUE(RunJob(
       chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kBatteryDischarge,
       std::move(params_dict),
diff --git a/chrome/browser/chromeos/policy/status_collector/device_status_collector.cc b/chrome/browser/chromeos/policy/status_collector/device_status_collector.cc
index 60c537c..8764d632 100644
--- a/chrome/browser/chromeos/policy/status_collector/device_status_collector.cc
+++ b/chrome/browser/chromeos/policy/status_collector/device_status_collector.cc
@@ -913,6 +913,14 @@
           backlight_info_out->set_brightness(backlight->brightness);
         }
       }
+      const auto& fan_info = probe_result->fan_info;
+      if (fan_info.has_value()) {
+        for (const auto& fan : fan_info.value()) {
+          em::FanInfo* const fan_info_out =
+              response_params_.device_status->add_fan_info();
+          fan_info_out->set_speed_rpm(fan->speed_rpm);
+        }
+      }
 
       for (const std::unique_ptr<SampledData>& sample_data : samples) {
         auto it = sample_data->battery_samples.find(battery_info->model_name);
@@ -1516,7 +1524,7 @@
 
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   std::vector<ProbeCategoryEnum> categories_to_probe = {
-      ProbeCategoryEnum::kCachedVpdData};
+      ProbeCategoryEnum::kCachedVpdData, ProbeCategoryEnum::kFan};
   if (report_storage_status_)
     categories_to_probe.push_back(ProbeCategoryEnum::kNonRemovableBlockDevices);
   if (report_power_status_)
diff --git a/chrome/browser/chromeos/policy/status_collector/device_status_collector_browsertest.cc b/chrome/browser/chromeos/policy/status_collector/device_status_collector_browsertest.cc
index 8d22c55..16ca554 100644
--- a/chrome/browser/chromeos/policy/status_collector/device_status_collector_browsertest.cc
+++ b/chrome/browser/chromeos/policy/status_collector/device_status_collector_browsertest.cc
@@ -170,6 +170,8 @@
 constexpr char kFakeBacklightPath[] = "/sys/class/backlight/fake_backlight";
 constexpr uint32_t kFakeMaxBrightness = 769;
 constexpr uint32_t kFakeBrightness = 124;
+// Fan test values:
+constexpr uint32_t kFakeSpeedRpm = 1225;
 
 // Time delta representing 1 hour time interval.
 constexpr TimeDelta kHour = TimeDelta::FromHours(1);
@@ -484,11 +486,13 @@
   chromeos::cros_healthd::mojom::BacklightInfo backlight_info(
       kFakeBacklightPath, kFakeMaxBrightness, kFakeBrightness);
   backlight_vector.push_back(backlight_info.Clone());
+  std::vector<chromeos::cros_healthd::mojom::FanInfoPtr> fan_vector;
+  chromeos::cros_healthd::mojom::FanInfo fan_info(kFakeSpeedRpm);
+  fan_vector.push_back(fan_info.Clone());
   chromeos::cros_healthd::mojom::TelemetryInfo fake_info(
       battery_info.Clone(), std::move(block_device_info),
       cached_vpd_info.Clone(), std::move(cpu_vector), timezone_info.Clone(),
-      memory_info.Clone(), std::move(backlight_vector),
-      base::nullopt /* fan_info */);
+      memory_info.Clone(), std::move(backlight_vector), std::move(fan_vector));
 
   // Create fake SampledData.
   em::CPUTempInfo fake_cpu_temp_sample;
@@ -2902,6 +2906,7 @@
   EXPECT_FALSE(device_status_.has_system_status());
   EXPECT_FALSE(device_status_.has_timezone_info());
   EXPECT_FALSE(device_status_.has_memory_info());
+  EXPECT_EQ(device_status_.fan_info_size(), 0);
 
   // When all of the relevant policies are set, expect the protobuf to have the
   // data from cros_healthd.
@@ -2989,6 +2994,11 @@
   EXPECT_EQ(backlight.path(), kFakeBacklightPath);
   EXPECT_EQ(backlight.max_brightness(), kFakeMaxBrightness);
   EXPECT_EQ(backlight.brightness(), kFakeBrightness);
+
+  // Verify the fan info.
+  ASSERT_EQ(device_status_.fan_info_size(), 1);
+  const auto& fan = device_status_.fan_info(0);
+  EXPECT_EQ(fan.speed_rpm(), kFakeSpeedRpm);
 }
 
 // Fake device state.
diff --git a/chrome/browser/devtools/devtools_sanity_browsertest.cc b/chrome/browser/devtools/devtools_sanity_browsertest.cc
index 77b07b5..8e9af66 100644
--- a/chrome/browser/devtools/devtools_sanity_browsertest.cc
+++ b/chrome/browser/devtools/devtools_sanity_browsertest.cc
@@ -245,7 +245,7 @@
   }
 
   void LoadTestPage(const std::string& test_page) {
-    GURL url = spawned_test_server()->GetURL(test_page);
+    GURL url(spawned_test_server()->GetURL("").Resolve(test_page));
     ui_test_utils::NavigateToURL(browser(), url);
   }
 
@@ -1499,6 +1499,11 @@
   RunTest("testConsoleContextNames", kPageWithContentScript);
 }
 
+IN_PROC_BROWSER_TEST_F(DevToolsExtensionTest, TestEvaluateOnChromeScheme) {
+  LoadExtension("chrome_scheme");
+  RunTest("waitForTestResultsAsMessage", std::string());
+}
+
 // Tests that scripts are not duplicated after Scripts Panel switch.
 IN_PROC_BROWSER_TEST_F(DevToolsSanityTest,
                        TestNoScriptDuplicatesOnPanelSwitch) {
diff --git a/chrome/browser/extensions/api/settings_private/prefs_util.cc b/chrome/browser/extensions/api/settings_private/prefs_util.cc
index 01a1b46..de0a5a2 100644
--- a/chrome/browser/extensions/api/settings_private/prefs_util.cc
+++ b/chrome/browser/extensions/api/settings_private/prefs_util.cc
@@ -483,8 +483,6 @@
   // Ambient Mode.
   (*s_whitelist)[ash::ambient::prefs::kAmbientModeEnabled] =
       settings_api::PrefType::PREF_TYPE_BOOLEAN;
-  (*s_whitelist)[ash::ambient::prefs::kAmbientModeTopicSource] =
-      settings_api::PrefType::PREF_TYPE_NUMBER;
 
   // Google Assistant.
   (*s_whitelist)[chromeos::assistant::prefs::kAssistantConsentStatus] =
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 9e1080a..ab612ae 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2647,6 +2647,11 @@
     "expiry_milestone": 80
   },
   {
+    "name": "interest-feed-v2",
+    "owners": [ "//chrome/android/feed/OWNERS", "feed@chromium.org" ],
+    "expiry_milestone": 95
+  },
+  {
     "name": "ios-breadcrumbs",
     "owners": [ "michaeldo" ],
     // Breadcrumbs is not a launching feature, but rather a tool used on
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index cdd496c..8d1c636 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -2419,6 +2419,11 @@
 const char kInterestFeedContentSuggestionsName[] =
     "Interest Feed Content Suggestions";
 
+const char kInterestFeedV2Name[] = "Interest Feed v2";
+const char kInterestFeedV2Description[] =
+    "Show content suggestions on the New Tab Page and Start Surface using the "
+    "new Feed Component.";
+
 const char kInterestFeedFeedbackDescription[] =
     "Allow the user to provide feedback from a feed card.";
 const char kInterestFeedFeedbackName[] = "Interest Feed Feedback";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 1b22543..7d3ea2b 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1403,6 +1403,9 @@
 extern const char kInterestFeedContentSuggestionsName[];
 extern const char kInterestFeedContentSuggestionsDescription[];
 
+extern const char kInterestFeedV2Name[];
+extern const char kInterestFeedV2Description[];
+
 extern const char kInterestFeedFeedbackName[];
 extern const char kInterestFeedFeedbackDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index d4a4e54..7ea219f 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -92,6 +92,7 @@
     &feature_engagement::kIPHChromeDuetTabSwitcherFeature,
     &feed::kInterestFeedContentSuggestions,
     &feed::kInterestFeedFeedback,
+    &feed::kInterestFeedV2,
     &feed::kReportFeedUserActions,
     &kAdjustWebApkInstallationSpace,
     &kAllowNewIncognitoTabIntents,
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index 1b25ad9..4208de56 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -18,7 +18,7 @@
  * Java accessor for base/feature_list.h state.
  *
  * This class provides methods to access values of feature flags registered in
- * |kFeaturesExposedToJava| in chrome/browser/android/chrome_feature_list.cc and as a constant
+ * |kFeaturesExposedToJava| in chrome/browser/android/flags/chrome_feature_list.cc and as a constant
  * in this class.
  *
  * This class also provides methods to access values of field trial parameters associated to those
@@ -304,6 +304,7 @@
     public static final String INSTANT_START = "InstantStart";
     public static final String INTEREST_FEED_CONTENT_SUGGESTIONS = "InterestFeedContentSuggestions";
     public static final String INTEREST_FEED_FEEDBACK = "InterestFeedFeedback";
+    public static final String INTEREST_FEED_V2 = "InterestFeedV2";
     public static final String KITKAT_SUPPORTED = "KitKatSupported";
     public static final String LOOKALIKE_NAVIGATION_URL_SUGGESTIONS_UI =
             "LookalikeUrlNavigationSuggestionsUI";
diff --git a/chrome/browser/geolocation/geolocation_permission_context_delegate_android.cc b/chrome/browser/geolocation/geolocation_permission_context_delegate_android.cc
index 19aeb0b5..bcc46241 100644
--- a/chrome/browser/geolocation/geolocation_permission_context_delegate_android.cc
+++ b/chrome/browser/geolocation/geolocation_permission_context_delegate_android.cc
@@ -9,6 +9,7 @@
 #include "chrome/browser/permissions/permission_update_infobar_delegate_android.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
+#include "components/permissions/android/android_permission_util.h"
 #include "components/permissions/permission_request_id.h"
 #include "components/search_engines/template_url.h"
 #include "components/search_engines/template_url_service.h"
@@ -23,20 +24,6 @@
 GeolocationPermissionContextDelegateAndroid::
     ~GeolocationPermissionContextDelegateAndroid() = default;
 
-bool GeolocationPermissionContextDelegateAndroid::
-    ShouldRequestAndroidLocationPermission(content::WebContents* web_contents) {
-  return PermissionUpdateInfoBarDelegate::ShouldShowPermissionInfoBar(
-             web_contents, {ContentSettingsType::GEOLOCATION}) ==
-         ShowPermissionInfoBarState::SHOW_PERMISSION_INFOBAR;
-}
-
-void GeolocationPermissionContextDelegateAndroid::RequestAndroidPermission(
-    content::WebContents* web_contents,
-    PermissionUpdatedCallback callback) {
-  PermissionUpdateInfoBarDelegate::Create(
-      web_contents, {ContentSettingsType::GEOLOCATION}, std::move(callback));
-}
-
 bool GeolocationPermissionContextDelegateAndroid::IsInteractable(
     content::WebContents* web_contents) {
   TabAndroid* tab = TabAndroid::FromWebContents(web_contents);
diff --git a/chrome/browser/geolocation/geolocation_permission_context_delegate_android.h b/chrome/browser/geolocation/geolocation_permission_context_delegate_android.h
index 5d8395d..65b26b8b 100644
--- a/chrome/browser/geolocation/geolocation_permission_context_delegate_android.h
+++ b/chrome/browser/geolocation/geolocation_permission_context_delegate_android.h
@@ -18,10 +18,6 @@
   ~GeolocationPermissionContextDelegateAndroid() override;
 
   // GeolocationPermissionContext::Delegate:
-  bool ShouldRequestAndroidLocationPermission(
-      content::WebContents* web_contents) override;
-  void RequestAndroidPermission(content::WebContents* web_contents,
-                                PermissionUpdatedCallback callback) override;
   bool IsInteractable(content::WebContents* web_contents) override;
   PrefService* GetPrefs(content::BrowserContext* browser_context) override;
   bool IsRequestingOriginDSE(content::BrowserContext* browser_context,
diff --git a/chrome/browser/media/DEPS b/chrome/browser/media/DEPS
index 7ee3cb7e..9419e7a 100644
--- a/chrome/browser/media/DEPS
+++ b/chrome/browser/media/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
   "+chrome/android/features/media_router/jni_headers",
+  "+components/webrtc",
   "+media/audio",
   "+media/base",
   "+media/cast",
diff --git a/chrome/browser/media/feeds/media_feeds_converter.cc b/chrome/browser/media/feeds/media_feeds_converter.cc
new file mode 100644
index 0000000..4a451cdf
--- /dev/null
+++ b/chrome/browser/media/feeds/media_feeds_converter.cc
@@ -0,0 +1,768 @@
+// Copyright 2020 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/media/feeds/media_feeds_converter.h"
+
+#include <numeric>
+#include <vector>
+
+#include "base/containers/flat_set.h"
+#include "base/no_destructor.h"
+#include "base/optional.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/media/feeds/media_feeds_store.mojom-forward.h"
+#include "chrome/browser/media/feeds/media_feeds_store.mojom-shared.h"
+#include "chrome/browser/media/feeds/media_feeds_store.mojom.h"
+#include "components/autofill/core/browser/validation.h"
+#include "components/schema_org/common/improved_metadata.mojom-forward.h"
+#include "components/schema_org/common/improved_metadata.mojom.h"
+#include "components/schema_org/schema_org_entity_names.h"
+#include "components/schema_org/schema_org_enums.h"
+#include "components/schema_org/schema_org_property_names.h"
+
+namespace media_feeds {
+
+using schema_org::improved::mojom::Entity;
+using schema_org::improved::mojom::EntityPtr;
+using schema_org::improved::mojom::Property;
+using schema_org::improved::mojom::PropertyPtr;
+using schema_org::improved::mojom::ValuesPtr;
+
+static int constexpr kMaxRatings = 5;
+static int constexpr kMaxGenres = 3;
+static int constexpr kMaxInteractionStatistics = 3;
+static int constexpr kMaxImages = 5;
+
+// Gets the property of entity with corresponding name. May be null if not found
+// or if the property has no values.
+Property* GetProperty(Entity* entity, const std::string& name) {
+  auto property = std::find_if(
+      entity->properties.begin(), entity->properties.end(),
+      [&name](const PropertyPtr& property) { return property->name == name; });
+  if (property == entity->properties.end())
+    return nullptr;
+  if (!(*property)->values)
+    return nullptr;
+  return property->get();
+}
+
+// Converts a property named property_name and store the result in the
+// converted_item struct using the convert_property callback. Returns true only
+// is the conversion was successful. If is_required is set, the property must be
+// found and be valid. If is_required is not set, returns false only if the
+// property is found and is invalid.
+template <typename T>
+bool ConvertProperty(
+    Entity* entity,
+    T* converted_item,
+    const std::string& property_name,
+    bool is_required,
+    base::OnceCallback<bool(const Property& property, T*)> convert_property) {
+  auto* property = GetProperty(entity, property_name);
+  if (!property)
+    return !is_required;
+  return std::move(convert_property).Run(*property, converted_item);
+}
+
+// Validates a property identified by name using the provided callback. Returns
+// true only if the property is valid.
+bool ValidateProperty(
+    Entity* entity,
+    const std::string& name,
+    base::OnceCallback<bool(const Property& property)> property_is_valid) {
+  auto property = std::find_if(
+      entity->properties.begin(), entity->properties.end(),
+      [&name](const PropertyPtr& property) { return property->name == name; });
+  if (property == entity->properties.end())
+    return false;
+  if (!(*property)->values)
+    return false;
+  return std::move(property_is_valid).Run(**property);
+}
+
+// Checks that the property contains at least one URL and that all URLs it
+// contains are valid.
+bool IsUrl(const Property& property) {
+  return !property.values->url_values.empty() &&
+         std::accumulate(property.values->url_values.begin(),
+                         property.values->url_values.end(), true,
+                         [](auto& accumulation, auto& url_value) {
+                           return accumulation && url_value.is_valid();
+                         });
+}
+
+// Checks that the property contains at least positive integer and that all
+// numbers it contains are positive integers.
+bool IsPositiveInteger(const Property& property) {
+  return !property.values->long_values.empty() &&
+         std::accumulate(property.values->long_values.begin(),
+                         property.values->long_values.end(), true,
+                         [](auto& accumulation, auto& long_value) {
+                           return accumulation && long_value > 0;
+                         });
+}
+
+// Checks that the property contains at least one non-empty string and that all
+// strings it contains are non-empty.
+bool IsNonEmptyString(const Property& property) {
+  return (!property.values->string_values.empty() &&
+          std::accumulate(property.values->string_values.begin(),
+                          property.values->string_values.end(), true,
+                          [](auto& accumulation, auto& string_value) {
+                            return accumulation && !string_value.empty();
+                          }));
+}
+
+// Checks that the property contains at least one valid email address.
+bool IsEmail(const Property& email) {
+  if (email.values->string_values.empty())
+    return false;
+
+  return autofill::IsValidEmailAddress(
+      base::ASCIIToUTF16(email.values->string_values[0]));
+}
+
+// Checks whether the media item type is supported.
+bool IsMediaItemType(const std::string& type) {
+  static const base::NoDestructor<base::flat_set<base::StringPiece>>
+      kSupportedTypes(base::flat_set<base::StringPiece>(
+          {schema_org::entity::kVideoObject, schema_org::entity::kMovie,
+           schema_org::entity::kTVSeries}));
+  return kSupportedTypes->find(type) != kSupportedTypes->end();
+}
+
+// Checks that the property contains at least one valid date / date-time.
+bool IsDateOrDateTime(const Property& property) {
+  return !property.values->date_time_values.empty();
+}
+
+// Gets a number from the property which may be stored either as a long or a
+// string.
+base::Optional<uint64_t> GetNumber(const Property& property) {
+  if (!property.values->long_values.empty())
+    return property.values->long_values[0];
+  if (!property.values->string_values.empty()) {
+    uint64_t number;
+    bool parsed_number =
+        base::StringToUint64(property.values->string_values[0], &number);
+    if (parsed_number)
+      return number;
+  }
+  return base::nullopt;
+}
+
+// Gets a list of media images from the property. The property should have at
+// least one media image and no more than kMaxImages. A media image is either a
+// valid URL string or an ImageObject entity containing a width, height, and
+// URL.
+base::Optional<std::vector<media_session::MediaImage>> GetMediaImage(
+    const Property& property) {
+  if (property.values->url_values.empty() &&
+      property.values->entity_values.empty()) {
+    return base::nullopt;
+  }
+
+  std::vector<media_session::MediaImage> images;
+
+  for (const auto& url : property.values->url_values) {
+    if (!url.is_valid())
+      continue;
+    media_session::MediaImage image;
+    image.src = url;
+
+    images.push_back(std::move(image));
+    if (images.size() == kMaxImages)
+      return images;
+  }
+
+  for (const auto& image_object : property.values->entity_values) {
+    if (image_object->type != schema_org::entity::kImageObject)
+      continue;
+
+    auto* width = GetProperty(image_object.get(), schema_org::property::kWidth);
+    if (!width || !IsPositiveInteger(*width))
+      continue;
+
+    auto* height =
+        GetProperty(image_object.get(), schema_org::property::kWidth);
+    if (!height || !IsPositiveInteger(*height))
+      continue;
+
+    auto* url = GetProperty(image_object.get(), schema_org::property::kUrl);
+    if (!url)
+      url = GetProperty(image_object.get(), schema_org::property::kEmbedUrl);
+    if (!IsUrl(*url))
+      continue;
+
+    media_session::MediaImage image;
+    image.src = url->values->url_values[0];
+    image.sizes.push_back(gfx::Size(width->values->long_values[0],
+                                    height->values->long_values[0]));
+
+    images.push_back(std::move(image));
+    if (images.size() == kMaxImages)
+      return images;
+  }
+  return images;
+}
+
+// Validates the provider property of an entity. Outputs the name and images
+// properties.
+bool ValidateProvider(const Property& provider,
+                      std::string* display_name,
+                      std::vector<media_session::MediaImage>* images) {
+  if (provider.values->entity_values.empty())
+    return false;
+
+  auto organization = std::find_if(
+      provider.values->entity_values.begin(),
+      provider.values->entity_values.end(), [](const EntityPtr& value) {
+        return value->type == schema_org::entity::kOrganization;
+      });
+
+  if (organization == provider.values->entity_values.end())
+    return false;
+
+  auto* name = GetProperty(organization->get(), schema_org::property::kName);
+  if (!name || !IsNonEmptyString(*name))
+    return false;
+  *display_name = name->values->string_values[0];
+
+  auto* logo = GetProperty(organization->get(), schema_org::property::kLogo);
+  if (!logo)
+    return false;
+  auto maybe_images = GetMediaImage(*logo);
+  if (!maybe_images.has_value() || maybe_images.value().empty())
+    return false;
+  *images = maybe_images.value();
+
+  return true;
+}
+
+// Gets the author property and stores the result in item. Returns true if the
+// author was valid.
+bool GetMediaItemAuthor(const Property& author, mojom::MediaFeedItem* item) {
+  item->author = mojom::Author::New();
+
+  if (IsNonEmptyString(author)) {
+    item->author->name = author.values->string_values[0];
+    return true;
+  }
+
+  if (author.values->entity_values.empty())
+    return false;
+
+  auto person = std::find_if(
+      author.values->entity_values.begin(), author.values->entity_values.end(),
+      [](const EntityPtr& value) {
+        return value->type == schema_org::entity::kPerson;
+      });
+
+  auto* name = GetProperty(person->get(), schema_org::property::kName);
+  if (!name || !IsNonEmptyString(*name))
+    return false;
+  item->author->name = name->values->string_values[0];
+
+  auto* url = GetProperty(person->get(), schema_org::property::kUrl);
+  if (url) {
+    if (!IsUrl(*url))
+      return false;
+    item->author->url = url->values->url_values[0];
+  }
+
+  return true;
+}
+
+// Gets the ratings property and stores the result in item. Returns true if the
+// ratings were valid.
+bool GetContentRatings(const Property& property, mojom::MediaFeedItem* item) {
+  if (property.values->entity_values.empty() ||
+      property.values->entity_values.size() > kMaxRatings)
+    return false;
+
+  for (const auto& rating : property.values->entity_values) {
+    mojom::ContentRatingPtr converted_rating = mojom::ContentRating::New();
+
+    if (rating->type != schema_org::entity::kRating)
+      return false;
+
+    auto* author = GetProperty(rating.get(), schema_org::property::kAuthor);
+    if (!author || !IsNonEmptyString(*author))
+      return false;
+
+    static const base::NoDestructor<base::flat_set<base::StringPiece>>
+        kRatingAgencies(base::flat_set<base::StringPiece>(
+            {"TVPG", "MPAA", "BBFC", "CSA", "AGCOM", "FSK", "SETSI", "ICAA",
+             "NA", "EIRIN", "KMRB", "CLASSIND", "MKRF", "CBFC", "KPI", "LSF",
+             "RTC"}));
+    if (!kRatingAgencies->contains(author->values->string_values[0]))
+      return false;
+    converted_rating->agency = author->values->string_values[0];
+
+    auto* rating_value =
+        GetProperty(rating.get(), schema_org::property::kRatingValue);
+    if (!rating_value || !IsNonEmptyString(*rating_value))
+      return false;
+    converted_rating->value = rating_value->values->string_values[0];
+
+    item->content_ratings.push_back(std::move(converted_rating));
+  }
+  return true;
+}
+
+// Gets the identifiers property and stores the result in item. Item should be a
+// struct with an identifiers field. Returns true if the identifiers were valid.
+template <typename T>
+bool GetIdentifiers(const Property& property, T* item) {
+  if (property.values->entity_values.empty())
+    return false;
+
+  std::vector<mojom::IdentifierPtr> identifiers;
+
+  for (const auto& identifier : property.values->entity_values) {
+    mojom::IdentifierPtr converted_identifier = mojom::Identifier::New();
+
+    if (identifier->type != schema_org::entity::kPropertyValue)
+      return false;
+
+    auto* property_id =
+        GetProperty(identifier.get(), schema_org::property::kPropertyID);
+    if (!property_id || !IsNonEmptyString(*property_id))
+      return false;
+    std::string property_id_str = property_id->values->string_values[0];
+    if (property_id_str == "TMS_ROOT_ID") {
+      converted_identifier->type = mojom::Identifier::Type::kTMSRootId;
+    } else if (property_id_str == "TMS_ID") {
+      converted_identifier->type = mojom::Identifier::Type::kTMSId;
+    } else if (property_id_str == "_PARTNER_ID_") {
+      converted_identifier->type = mojom::Identifier::Type::kPartnerId;
+    } else {
+      return false;
+    }
+
+    auto* value = GetProperty(identifier.get(), schema_org::property::kValue);
+    if (!value || !IsNonEmptyString(*value))
+      return false;
+    converted_identifier->value = value->values->string_values[0];
+
+    item->identifiers.push_back(std::move(converted_identifier));
+  }
+  return true;
+}
+
+// Gets the interaction type from a property containing an interaction type
+// string.
+base::Optional<mojom::InteractionCounterType> GetInteractionType(
+    const Property& property) {
+  if (property.values->string_values.empty())
+    return base::nullopt;
+  GURL type = GURL(property.values->string_values[0]);
+  if (!type.SchemeIsHTTPOrHTTPS() || type.host() != "schema.org")
+    return base::nullopt;
+
+  std::string type_path = type.path().substr(1);
+  if (type_path == schema_org::entity::kWatchAction) {
+    return mojom::InteractionCounterType::kWatch;
+  } else if (type_path == schema_org::entity::kLikeAction) {
+    return mojom::InteractionCounterType::kLike;
+  } else if (type_path == schema_org::entity::kDislikeAction) {
+    return mojom::InteractionCounterType::kDislike;
+  }
+  return base::nullopt;
+}
+
+// Gets the interaction statistics property and stores the result in item.
+// Returns true if the statistics were valid.
+bool GetInteractionStatistics(const Property& property,
+                              mojom::MediaFeedItem* item) {
+  if (property.values->entity_values.empty() ||
+      property.values->entity_values.size() > kMaxInteractionStatistics) {
+    return false;
+  }
+  for (const auto& stat : property.values->entity_values) {
+    if (stat->type != schema_org::entity::kInteractionCounter)
+      return false;
+
+    auto* interaction_type =
+        GetProperty(stat.get(), schema_org::property::kInteractionType);
+    if (!interaction_type)
+      return false;
+    auto type = GetInteractionType(*interaction_type);
+    if (!type.has_value() || item->interaction_counters.count(type.value()) > 0)
+      return false;
+
+    auto* user_interaction_count =
+        GetProperty(stat.get(), schema_org::property::kUserInteractionCount);
+    if (!user_interaction_count)
+      return false;
+    base::Optional<uint64_t> count = GetNumber(*user_interaction_count);
+    if (!count.has_value())
+      return false;
+    item->interaction_counters.insert(
+        std::pair<mojom::InteractionCounterType, uint64_t>(type.value(),
+                                                           count.value()));
+  }
+  if (item->interaction_counters.empty())
+    return false;
+
+  return true;
+}
+
+base::Optional<mojom::MediaFeedItemType> GetMediaItemType(
+    const std::string& schema_org_type) {
+  if (schema_org_type == schema_org::entity::kVideoObject) {
+    return mojom::MediaFeedItemType::kVideo;
+  } else if (schema_org_type == schema_org::entity::kMovie) {
+    return mojom::MediaFeedItemType::kMovie;
+  } else if (schema_org_type == schema_org::entity::kTVSeries) {
+    return mojom::MediaFeedItemType::kTVSeries;
+  }
+  return base::nullopt;
+}
+
+// Gets the isFamilyFriendly property and stores the result in item.
+bool GetIsFamilyFriendly(const Property& property, mojom::MediaFeedItem* item) {
+  if (property.values->bool_values.empty()) {
+    return false;
+  }
+
+  item->is_family_friendly = property.values->bool_values[0];
+  return true;
+}
+
+// Gets the watchAction and actionStatus properties from an embedded entity and
+// stores the result in item. Returns true if both the action and the action
+// status were valid.
+bool GetActionAndStatus(const Property& property, mojom::MediaFeedItem* item) {
+  if (property.values->entity_values.empty())
+    return false;
+
+  EntityPtr& action = property.values->entity_values[0];
+  if (action->type != schema_org::entity::kWatchAction)
+    return false;
+
+  item->action = mojom::Action::New();
+
+  auto* target = GetProperty(action.get(), schema_org::property::kTarget);
+  if (!target || !IsUrl(*target))
+    return false;
+  item->action->url = target->values->url_values[0];
+
+  auto* action_status =
+      GetProperty(action.get(), schema_org::property::kActionStatus);
+  if (action_status) {
+    if (!IsUrl(*action_status))
+      return false;
+
+    auto status = schema_org::enums::CheckValidEnumString(
+        "http://schema.org/ActionStatusType",
+        action_status->values->url_values[0]);
+    if (status == base::nullopt) {
+      return false;
+    } else if (status.value() ==
+               static_cast<int>(
+                   schema_org::enums::ActionStatusType::kActiveActionStatus)) {
+      item->action_status = mojom::MediaFeedItemActionStatus::kActive;
+      auto* start_time =
+          GetProperty(action.get(), schema_org::property::kStartTime);
+      if (!start_time || start_time->values->time_values.empty())
+        return false;
+      item->action->start_time = start_time->values->time_values[0];
+    } else if (status.value() ==
+               static_cast<int>(schema_org::enums::ActionStatusType::
+                                    kPotentialActionStatus)) {
+      item->action_status = mojom::MediaFeedItemActionStatus::kPotential;
+    } else if (status.value() ==
+               static_cast<int>(schema_org::enums::ActionStatusType::
+                                    kCompletedActionStatus)) {
+      item->action_status = mojom::MediaFeedItemActionStatus::kCompleted;
+    }
+  }
+  return true;
+}
+
+// Gets the TV episode stored in an embedded entity and stores the result in
+// item. Returns true if the TV episode was valid.
+bool GetEpisode(const Property& property, mojom::MediaFeedItem* item) {
+  if (property.values->entity_values.empty())
+    return false;
+
+  EntityPtr& episode = property.values->entity_values[0];
+  if (episode->type != schema_org::entity::kTVEpisode)
+    return false;
+
+  if (!item->tv_episode)
+    item->tv_episode = mojom::TVEpisode::New();
+
+  auto* episode_number =
+      GetProperty(episode.get(), schema_org::property::kEpisodeNumber);
+  if (!episode_number || !IsPositiveInteger(*episode_number))
+    return false;
+  item->tv_episode->episode_number = episode_number->values->long_values[0];
+
+  auto* name = GetProperty(episode.get(), schema_org::property::kName);
+  if (!name || !IsNonEmptyString(*name))
+    return false;
+  item->tv_episode->name = name->values->string_values[0];
+
+  if (!ConvertProperty<mojom::TVEpisode>(
+          episode.get(), item->tv_episode.get(),
+          schema_org::property::kIdentifier, false,
+          base::BindOnce(&GetIdentifiers<mojom::TVEpisode>))) {
+    return false;
+  }
+
+  auto* image = GetProperty(episode.get(), schema_org::property::kImage);
+  if (image) {
+    auto converted_images = GetMediaImage(*image);
+    if (!converted_images.has_value())
+      return false;
+    // TODO(sgbowen): Add an images field to TV episodes and store the converted
+    // images here.
+  }
+
+  if (!ConvertProperty<mojom::MediaFeedItem>(
+          episode.get(), item, schema_org::property::kPotentialAction, true,
+          base::BindOnce(&GetActionAndStatus))) {
+    return false;
+  }
+
+  return true;
+}
+
+// Gets the TV season stored in an embedded entity and stores the result in
+// item. Returns true if the TV season was valid.
+bool GetSeason(const Property& property, mojom::MediaFeedItem* item) {
+  if (property.values->entity_values.empty())
+    return false;
+
+  EntityPtr& season = property.values->entity_values[0];
+  if (season->type != schema_org::entity::kTVSeason)
+    return false;
+
+  if (!item->tv_episode)
+    item->tv_episode = mojom::TVEpisode::New();
+
+  auto* season_number =
+      GetProperty(season.get(), schema_org::property::kSeasonNumber);
+  if (!season_number || !IsPositiveInteger(*season_number))
+    return false;
+  item->tv_episode->season_number = season_number->values->long_values[0];
+
+  auto* number_episodes =
+      GetProperty(season.get(), schema_org::property::kNumberOfEpisodes);
+  if (!number_episodes || !IsPositiveInteger(*number_episodes))
+    return false;
+
+  if (!ConvertProperty<mojom::MediaFeedItem>(
+          season.get(), item, schema_org::property::kEpisode, false,
+          base::BindOnce(&GetIdentifiers<mojom::MediaFeedItem>))) {
+    return false;
+  }
+
+  return true;
+}
+
+// Gets the broadcastEvent entity from the property and store the result in item
+// as LiveDetails. Returns true if the broadcastEven was valid.
+bool GetLiveDetails(const Property& property, mojom::MediaFeedItem* item) {
+  if (property.values->entity_values.empty())
+    return false;
+
+  EntityPtr& publication = property.values->entity_values[0];
+  if (publication->type != schema_org::entity::kBroadcastEvent)
+    return false;
+
+  item->live = mojom::LiveDetails::New();
+
+  auto* start_date =
+      GetProperty(publication.get(), schema_org::property::kStartDate);
+  if (!start_date || !IsDateOrDateTime(*start_date))
+    return false;
+  item->live->start_time = start_date->values->date_time_values[0];
+
+  auto* end_date =
+      GetProperty(publication.get(), schema_org::property::kEndDate);
+  if (end_date) {
+    if (!IsDateOrDateTime(*end_date))
+      return false;
+    item->live->end_time = end_date->values->date_time_values[0];
+  }
+
+  return true;
+}
+
+// Gets the duration from the property and store the result in item. Returns
+// true if the duration was valid.
+bool GetDuration(const Property& property, mojom::MediaFeedItem* item) {
+  if (property.values->time_values.empty())
+    return false;
+
+  item->duration = property.values->time_values[0];
+  return true;
+}
+
+// Given the schema.org data_feed_items, iterate through and convert all feed
+// items into MediaFeedItemPtr types. Store the converted items in
+// converted_feed_items. Skips invalid feed items.
+void GetDataFeedItems(
+    const PropertyPtr& data_feed_items,
+    std::vector<mojom::MediaFeedItemPtr>* converted_feed_items) {
+  if (data_feed_items->values->entity_values.empty())
+    return;
+
+  base::flat_set<std::string> item_ids;
+
+  for (const auto& item : data_feed_items->values->entity_values) {
+    mojom::MediaFeedItemPtr converted_item = mojom::MediaFeedItem::New();
+    auto convert_property =
+        base::BindRepeating(&ConvertProperty<mojom::MediaFeedItem>, item.get(),
+                            converted_item.get());
+
+    auto type = GetMediaItemType(item->type);
+    if (!type.has_value())
+      continue;
+    converted_item->type = type.value();
+
+    // Check that the id is present and unique. This does not get converted.
+    if (item->id == "" || item_ids.find(item->id) != item_ids.end()) {
+      continue;
+    }
+    item_ids.insert(item->id);
+
+    auto* name = GetProperty(item.get(), schema_org::property::kName);
+    if (name && IsNonEmptyString(*name)) {
+      converted_item->name = base::ASCIIToUTF16(name->values->string_values[0]);
+    } else {
+      continue;
+    }
+
+    auto* date_published =
+        GetProperty(item.get(), schema_org::property::kDatePublished);
+    if (date_published && !date_published->values->date_time_values.empty()) {
+      converted_item->date_published =
+          date_published->values->date_time_values[0];
+    } else {
+      continue;
+    }
+
+    if (!convert_property.Run(schema_org::property::kIsFamilyFriendly, true,
+                              base::BindOnce(&GetIsFamilyFriendly))) {
+      continue;
+    }
+
+    auto* image = GetProperty(item.get(), schema_org::property::kImage);
+    if (!image)
+      continue;
+    auto converted_images = GetMediaImage(*image);
+    if (!converted_images.has_value())
+      continue;
+    converted_item->images = converted_images.value();
+
+    bool has_embedded_action =
+        item->type == schema_org::entity::kTVSeries &&
+        GetProperty(item.get(), schema_org::property::kEpisode);
+    if (!convert_property.Run(schema_org::property::kPotentialAction,
+                              !has_embedded_action,
+                              base::BindOnce(&GetActionAndStatus))) {
+      continue;
+    }
+
+    if (!convert_property.Run(schema_org::property::kInteractionStatistic,
+                              false,
+                              base::BindOnce(&GetInteractionStatistics))) {
+      continue;
+    }
+
+    if (!convert_property.Run(schema_org::property::kContentRating, false,
+                              base::BindOnce(&GetContentRatings))) {
+      continue;
+    }
+
+    auto* genre = GetProperty(item.get(), schema_org::property::kGenre);
+    if (genre) {
+      if (!IsNonEmptyString(*genre))
+        continue;
+      for (const auto& genre_value : genre->values->string_values) {
+        converted_item->genre.push_back(genre_value);
+        if (converted_item->genre.size() >= kMaxGenres)
+          continue;
+      }
+    }
+
+    if (!convert_property.Run(schema_org::property::kPublication, false,
+                              base::BindOnce(&GetLiveDetails))) {
+      continue;
+    }
+
+    if (!convert_property.Run(
+            schema_org::property::kIdentifier, false,
+            base::BindOnce(&GetIdentifiers<mojom::MediaFeedItem>))) {
+      continue;
+    }
+
+    if (converted_item->type == mojom::MediaFeedItemType::kVideo) {
+      if (!convert_property.Run(schema_org::property::kAuthor, true,
+                                base::BindOnce(&GetMediaItemAuthor))) {
+        continue;
+      }
+      if (!convert_property.Run(schema_org::property::kDuration,
+                                !converted_item->live,
+                                base::BindOnce(&GetDuration))) {
+        continue;
+      }
+    }
+
+    if (converted_item->type == mojom::MediaFeedItemType::kTVSeries) {
+      auto* num_episodes =
+          GetProperty(item.get(), schema_org::property::kNumberOfEpisodes);
+      if (!num_episodes || !IsPositiveInteger(*num_episodes))
+        continue;
+      auto* num_seasons =
+          GetProperty(item.get(), schema_org::property::kNumberOfSeasons);
+      if (!num_seasons || !IsPositiveInteger(*num_seasons))
+        continue;
+      if (!convert_property.Run(schema_org::property::kEpisode, false,
+                                base::BindOnce(&GetEpisode))) {
+        continue;
+      }
+      if (!convert_property.Run(schema_org::property::kContainsSeason, false,
+                                base::BindOnce(&GetSeason))) {
+        continue;
+      }
+    }
+
+    converted_feed_items->push_back(std::move(converted_item));
+  }
+}
+
+// static
+base::Optional<std::vector<mojom::MediaFeedItemPtr>> GetMediaFeeds(
+    EntityPtr entity) {
+  if (entity->type != "CompleteDataFeed")
+    return base::nullopt;
+
+  auto* provider = GetProperty(entity.get(), schema_org::property::kProvider);
+  std::string display_name;
+  std::vector<media_session::MediaImage> logos;
+  if (!ValidateProvider(*provider, &display_name, &logos))
+    return base::nullopt;
+
+  std::vector<mojom::MediaFeedItemPtr> media_feed_items;
+  auto data_feed_items = std::find_if(
+      entity->properties.begin(), entity->properties.end(),
+      [](const PropertyPtr& property) {
+        return property->name == schema_org::property::kDataFeedElement;
+      });
+  if (data_feed_items != entity->properties.end() &&
+      (*data_feed_items)->values) {
+    GetDataFeedItems(*data_feed_items, &media_feed_items);
+  }
+
+  return media_feed_items;
+}
+
+}  // namespace media_feeds
diff --git a/chrome/browser/media/feeds/media_feeds_converter.h b/chrome/browser/media/feeds/media_feeds_converter.h
new file mode 100644
index 0000000..eb9af1c
--- /dev/null
+++ b/chrome/browser/media/feeds/media_feeds_converter.h
@@ -0,0 +1,24 @@
+// Copyright 2020 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_MEDIA_FEEDS_MEDIA_FEEDS_CONVERTER_H_
+#define CHROME_BROWSER_MEDIA_FEEDS_MEDIA_FEEDS_CONVERTER_H_
+
+#include "chrome/browser/media/feeds/media_feeds_store.mojom.h"
+#include "components/schema_org/common/improved_metadata.mojom.h"
+
+namespace media_feeds {
+
+// Given a schema_org entity of type CompleteDataFeed, converts all items
+// contained in the feed to MediaFeedItemPtr type and returns them in a vector.
+// The feed should be valid according to https://wicg.github.io/media-feeds/. If
+// not, GetMediaFeeds returns an empty result. If the feed is valid, but some of
+// its feed items are not, GetMediaFeeds excludes the invalid feed items from
+// the returned result.
+base::Optional<std::vector<mojom::MediaFeedItemPtr>> GetMediaFeeds(
+    schema_org::improved::mojom::EntityPtr schema_org_entity);
+
+}  // namespace media_feeds
+
+#endif  // CHROME_BROWSER_MEDIA_FEEDS_MEDIA_FEEDS_CONVERTER_H_
diff --git a/chrome/browser/media/feeds/media_feeds_converter_unittest.cc b/chrome/browser/media/feeds/media_feeds_converter_unittest.cc
new file mode 100644
index 0000000..e6b1e8d7
--- /dev/null
+++ b/chrome/browser/media/feeds/media_feeds_converter_unittest.cc
@@ -0,0 +1,787 @@
+// Copyright 2020 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/media/feeds/media_feeds_converter.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/media/feeds/media_feeds_store.mojom-forward.h"
+#include "chrome/browser/media/feeds/media_feeds_store.mojom-shared.h"
+#include "chrome/browser/media/feeds/media_feeds_store.mojom.h"
+#include "components/schema_org/common/improved_metadata.mojom.h"
+#include "components/schema_org/extractor.h"
+#include "components/schema_org/schema_org_entity_names.h"
+#include "components/schema_org/schema_org_property_names.h"
+#include "services/network/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace media_feeds {
+
+using mojom::MediaFeedItem;
+using mojom::MediaFeedItemPtr;
+using schema_org::improved::mojom::Entity;
+using schema_org::improved::mojom::EntityPtr;
+using schema_org::improved::mojom::Property;
+using schema_org::improved::mojom::PropertyPtr;
+using schema_org::improved::mojom::Values;
+using schema_org::improved::mojom::ValuesPtr;
+
+class MediaFeedsConverterTest : public testing::Test {
+ public:
+  MediaFeedsConverterTest()
+      : extractor_({schema_org::entity::kCompleteDataFeed,
+                    schema_org::entity::kMovie,
+                    schema_org::entity::kWatchAction}) {}
+
+ protected:
+  Property* GetProperty(Entity* entity, const std::string& name);
+  PropertyPtr CreateStringProperty(const std::string& name,
+                                   const std::string& value);
+  PropertyPtr CreateLongProperty(const std::string& name, long value);
+  PropertyPtr CreateUrlProperty(const std::string& name, const GURL& value);
+  PropertyPtr CreateTimeProperty(const std::string& name, int hours);
+  PropertyPtr CreateDateTimeProperty(const std::string& name,
+                                     const std::string& value);
+  PropertyPtr CreateEntityProperty(const std::string& name, EntityPtr value);
+  EntityPtr ConvertJSONToEntityPtr(const std::string& json);
+  EntityPtr ValidWatchAction();
+  EntityPtr ValidMediaFeed();
+  EntityPtr ValidMediaFeedItem();
+  mojom::MediaFeedItemPtr ExpectedFeedItem();
+  EntityPtr AddItemToFeed(EntityPtr feed, EntityPtr item);
+
+ private:
+  schema_org::Extractor extractor_;
+};
+
+Property* MediaFeedsConverterTest::GetProperty(Entity* entity,
+                                               const std::string& name) {
+  auto property = std::find_if(
+      entity->properties.begin(), entity->properties.end(),
+      [&name](const PropertyPtr& property) { return property->name == name; });
+  DCHECK(property != entity->properties.end());
+  DCHECK((*property)->values);
+  return property->get();
+}
+
+PropertyPtr MediaFeedsConverterTest::CreateStringProperty(
+    const std::string& name,
+    const std::string& value) {
+  PropertyPtr property = Property::New();
+  property->name = name;
+  property->values = Values::New();
+  property->values->string_values.push_back(value);
+  return property;
+}
+
+PropertyPtr MediaFeedsConverterTest::CreateLongProperty(const std::string& name,
+                                                        long value) {
+  PropertyPtr property = Property::New();
+  property->name = name;
+  property->values = Values::New();
+  property->values->long_values.push_back(value);
+  return property;
+}
+
+PropertyPtr MediaFeedsConverterTest::CreateUrlProperty(const std::string& name,
+                                                       const GURL& value) {
+  PropertyPtr property = Property::New();
+  property->name = name;
+  property->values = Values::New();
+  property->values->url_values.push_back(value);
+  return property;
+}
+
+PropertyPtr MediaFeedsConverterTest::CreateTimeProperty(const std::string& name,
+                                                        int hours) {
+  PropertyPtr property = Property::New();
+  property->name = name;
+  property->values = Values::New();
+  property->values->time_values.push_back(base::TimeDelta::FromHours(hours));
+  return property;
+}
+
+PropertyPtr MediaFeedsConverterTest::CreateDateTimeProperty(
+    const std::string& name,
+    const std::string& value) {
+  PropertyPtr property = Property::New();
+  property->name = name;
+  property->values = Values::New();
+  base::Time time;
+  bool got_time = base::Time::FromString(value.c_str(), &time);
+  DCHECK(got_time);
+  property->values->date_time_values.push_back(time);
+  return property;
+}
+
+PropertyPtr MediaFeedsConverterTest::CreateEntityProperty(
+    const std::string& name,
+    EntityPtr value) {
+  PropertyPtr property = Property::New();
+  property->name = name;
+  property->values = Values::New();
+  property->values->entity_values.push_back(std::move(value));
+  return property;
+}
+
+EntityPtr MediaFeedsConverterTest::ConvertJSONToEntityPtr(
+    const std::string& json) {
+  return extractor_.Extract(json);
+}
+
+EntityPtr MediaFeedsConverterTest::ValidWatchAction() {
+  return extractor_.Extract(
+      R"END(
+      {
+        "@type": "WatchAction",
+        "target": "https://www.example.org",
+        "actionStatus": "https://schema.org/ActiveActionStatus",
+        "startTime": "01:00:00"
+      }
+    )END");
+}
+
+EntityPtr MediaFeedsConverterTest::ValidMediaFeed() {
+  return extractor_.Extract(
+      R"END(
+        {
+          "@type": "CompleteDataFeed",
+          "provider": {
+            "@type": "Organization",
+            "name": "Media Site",
+            "logo": "https://www.example.org/logo.jpg",
+            "member": {
+              "@type": "Person",
+              "name": "Becca Hughes",
+              "image": "https://www.example.org/profile_pic.jpg",
+              "email": "beccahughes@chromium.org"
+            }
+          }
+        }
+      )END");
+}
+
+EntityPtr MediaFeedsConverterTest::ValidMediaFeedItem() {
+  EntityPtr item = extractor_.Extract(
+      R"END(
+        {
+          "@type": "Movie",
+          "@id": "12345",
+          "name": "media feed",
+          "datePublished": "1970-01-01",
+          "image": "https://www.example.com/image.jpg",
+          "isFamilyFriendly": "https://schema.org/True"
+        }
+      )END");
+
+  item->properties.push_back(CreateEntityProperty(
+      schema_org::property::kPotentialAction, ValidWatchAction()));
+  return item;
+}
+
+mojom::MediaFeedItemPtr MediaFeedsConverterTest::ExpectedFeedItem() {
+  mojom::MediaFeedItemPtr expected_item = mojom::MediaFeedItem::New();
+  expected_item->type = mojom::MediaFeedItemType::kMovie;
+  expected_item->name = base::ASCIIToUTF16("media feed");
+
+  media_session::MediaImage expected_image;
+  expected_image.src = GURL("https://www.example.com/image.jpg");
+  expected_item->images.push_back(std::move(expected_image));
+
+  base::Time time;
+  bool got_time = base::Time::FromString("1970-01-01", &time);
+  DCHECK(got_time);
+  expected_item->date_published = time;
+
+  expected_item->is_family_friendly = true;
+
+  expected_item->action_status = mojom::MediaFeedItemActionStatus::kActive;
+  expected_item->action = mojom::Action::New();
+  expected_item->action->url = GURL("https://www.example.org");
+  expected_item->action->start_time = base::TimeDelta::FromHours(1);
+  return expected_item;
+}
+
+EntityPtr MediaFeedsConverterTest::AddItemToFeed(EntityPtr feed,
+                                                 EntityPtr item) {
+  PropertyPtr data_feed_items = Property::New();
+  data_feed_items->name = schema_org::property::kDataFeedElement;
+  data_feed_items->values = Values::New();
+  data_feed_items->values->entity_values.push_back(std::move(item));
+  feed->properties.push_back(std::move(data_feed_items));
+  return feed;
+}
+
+TEST_F(MediaFeedsConverterTest, SucceedsOnValidCompleteDataFeed) {
+  std::vector<MediaFeedItemPtr> items;
+
+  EntityPtr entity = ValidMediaFeed();
+
+  auto result = GetMediaFeeds(std::move(entity));
+
+  EXPECT_TRUE(result.has_value());
+  EXPECT_TRUE(result.value().empty());
+}
+
+TEST_F(MediaFeedsConverterTest, SucceedsOnValidCompleteDataFeedWithItem) {
+  EntityPtr entity = AddItemToFeed(ValidMediaFeed(), ValidMediaFeedItem());
+
+  auto result = GetMediaFeeds(std::move(entity));
+
+  EXPECT_TRUE(result.has_value());
+  ASSERT_EQ(result.value().size(), 1u);
+  EXPECT_EQ(ExpectedFeedItem(), result.value()[0]);
+}
+
+TEST_F(MediaFeedsConverterTest, FailsWrongType) {
+  EntityPtr entity = Entity::New();
+  entity->type = "something else";
+
+  EXPECT_FALSE(GetMediaFeeds(std::move(entity)).has_value());
+}
+
+TEST_F(MediaFeedsConverterTest, FailsInvalidProviderOrganizationName) {
+  EntityPtr entity = ValidMediaFeed();
+
+  Property* organization =
+      GetProperty(entity.get(), schema_org::property::kProvider);
+  Property* organization_name =
+      GetProperty(organization->values->entity_values[0].get(),
+                  schema_org::property::kName);
+
+  organization_name->values->string_values = {""};
+
+  EXPECT_FALSE(GetMediaFeeds(std::move(entity)).has_value());
+}
+
+TEST_F(MediaFeedsConverterTest, FailsInvalidProviderOrganizationLogo) {
+  EntityPtr entity = ValidMediaFeed();
+
+  Property* organization =
+      GetProperty(entity.get(), schema_org::property::kProvider);
+  Property* organization_name =
+      GetProperty(organization->values->entity_values[0].get(),
+                  schema_org::property::kLogo);
+
+  organization_name->values->url_values = {GURL("")};
+
+  EXPECT_FALSE(GetMediaFeeds(std::move(entity)).has_value());
+}
+
+// Fails because the media feed item name is empty.
+TEST_F(MediaFeedsConverterTest, FailsOnInvalidMediaFeedItemName) {
+  EntityPtr item = ValidMediaFeedItem();
+  auto* name = GetProperty(item.get(), schema_org::property::kName);
+  name->values->string_values[0] = "";
+
+  EntityPtr entity = AddItemToFeed(ValidMediaFeed(), std::move(item));
+
+  auto result = GetMediaFeeds(std::move(entity));
+
+  EXPECT_TRUE(result.has_value());
+  EXPECT_TRUE(result.value().empty());
+}
+
+// Fails because the date published is the wrong type (string instead of
+// base::Time).
+TEST_F(MediaFeedsConverterTest, FailsInvalidDatePublished) {
+  EntityPtr item = ValidMediaFeedItem();
+  auto* date_published =
+      GetProperty(item.get(), schema_org::property::kDatePublished);
+  auto& dates = date_published->values->date_time_values;
+  dates.erase(dates.begin());
+  date_published->values->string_values.push_back("1970-01-01");
+
+  EntityPtr entity = AddItemToFeed(ValidMediaFeed(), std::move(item));
+
+  auto result = GetMediaFeeds(std::move(entity));
+
+  EXPECT_TRUE(result.has_value());
+  EXPECT_TRUE(result.value().empty());
+}
+
+// Fails because the value of isFamilyFriendly property is not a parseable
+// boolean type.
+TEST_F(MediaFeedsConverterTest, FailsInvalidIsFamilyFriendly) {
+  EntityPtr item = ValidMediaFeedItem();
+  auto* is_family_friendly =
+      GetProperty(item.get(), schema_org::property::kIsFamilyFriendly);
+  is_family_friendly->values->string_values = {"True"};
+  is_family_friendly->values->bool_values.clear();
+
+  EntityPtr entity = AddItemToFeed(ValidMediaFeed(), std::move(item));
+
+  auto result = GetMediaFeeds(std::move(entity));
+
+  EXPECT_TRUE(result.has_value());
+  EXPECT_TRUE(result.value().empty());
+}
+
+// Fails because an active action does not contain a start time.
+TEST_F(MediaFeedsConverterTest, FailsInvalidPotentialAction) {
+  EntityPtr item = ValidMediaFeedItem();
+  auto* action =
+      GetProperty(item.get(), schema_org::property::kPotentialAction);
+  auto* start_time = GetProperty(action->values->entity_values[0].get(),
+                                 schema_org::property::kStartTime);
+  start_time->values->time_values = {};
+
+  EntityPtr entity = AddItemToFeed(ValidMediaFeed(), std::move(item));
+
+  auto result = GetMediaFeeds(std::move(entity));
+
+  EXPECT_TRUE(result.has_value());
+  EXPECT_TRUE(result.value().empty());
+}
+
+// Succeeds with a valid author and duration on a video object. For other types
+// of media, these fields are ignored, but they must be valid on video type.
+TEST_F(MediaFeedsConverterTest, SucceedsItemWithAuthorAndDuration) {
+  EntityPtr item = ValidMediaFeedItem();
+  item->type = schema_org::entity::kVideoObject;
+  EntityPtr author = Entity::New();
+  author->type = schema_org::entity::kPerson;
+  author->properties.push_back(
+      CreateStringProperty(schema_org::property::kName, "Becca Hughes"));
+  author->properties.push_back(CreateUrlProperty(
+      schema_org::property::kUrl, GURL("https://www.google.com")));
+  item->properties.push_back(
+      CreateEntityProperty(schema_org::property::kAuthor, std::move(author)));
+  item->properties.push_back(
+      CreateTimeProperty(schema_org::property::kDuration, 1));
+
+  EntityPtr entity = AddItemToFeed(ValidMediaFeed(), std::move(item));
+
+  mojom::MediaFeedItemPtr expected_item = ExpectedFeedItem();
+  expected_item->type = mojom::MediaFeedItemType::kVideo;
+  expected_item->author = mojom::Author::New();
+  expected_item->author->name = "Becca Hughes";
+  expected_item->author->url = GURL("https://www.google.com");
+  expected_item->duration = base::TimeDelta::FromHours(1);
+
+  auto result = GetMediaFeeds(std::move(entity));
+
+  EXPECT_TRUE(result.has_value());
+  ASSERT_EQ(result.value().size(), 1u);
+  EXPECT_EQ(expected_item, result.value()[0]);
+}
+
+// Fails because the author's name is empty.
+TEST_F(MediaFeedsConverterTest, FailsInvalidAuthor) {
+  EntityPtr item = ValidMediaFeedItem();
+  item->type = schema_org::entity::kVideoObject;
+  EntityPtr author = Entity::New();
+  author->type = schema_org::entity::kPerson;
+  author->properties.push_back(
+      CreateStringProperty(schema_org::property::kName, ""));
+  author->properties.push_back(CreateUrlProperty(
+      schema_org::property::kUrl, GURL("https://www.google.com")));
+  item->properties.push_back(
+      CreateEntityProperty(schema_org::property::kAuthor, std::move(author)));
+  item->properties.push_back(
+      CreateTimeProperty(schema_org::property::kDuration, 1));
+
+  EntityPtr entity = AddItemToFeed(ValidMediaFeed(), std::move(item));
+
+  auto result = GetMediaFeeds(std::move(entity));
+
+  EXPECT_TRUE(result.has_value());
+  EXPECT_TRUE(result.value().empty());
+}
+
+TEST_F(MediaFeedsConverterTest, SucceedsItemWithInteractionStatistic) {
+  EntityPtr item = ValidMediaFeedItem();
+
+  EntityPtr interaction_statistic = Entity::New();
+  interaction_statistic->type = schema_org::entity::kInteractionCounter;
+  interaction_statistic->properties.push_back(
+      CreateStringProperty(schema_org::property::kInteractionType,
+                           "https://schema.org/WatchAction"));
+  interaction_statistic->properties.push_back(
+      CreateStringProperty(schema_org::property::kUserInteractionCount, "1"));
+  item->properties.push_back(
+      CreateEntityProperty(schema_org::property::kInteractionStatistic,
+                           std::move(interaction_statistic)));
+
+  EntityPtr entity = AddItemToFeed(ValidMediaFeed(), std::move(item));
+
+  mojom::MediaFeedItemPtr expected_item = ExpectedFeedItem();
+  expected_item->interaction_counters = {
+      {mojom::InteractionCounterType::kWatch, 1}};
+
+  auto result = GetMediaFeeds(std::move(entity));
+
+  EXPECT_TRUE(result.has_value());
+  ASSERT_EQ(result.value().size(), 1u);
+  EXPECT_EQ(expected_item, result.value()[0]);
+}
+
+// Fails because the interaction statistic property has a duplicate of the watch
+// interaction type.
+TEST_F(MediaFeedsConverterTest, FailsInvalidInteractionStatistic) {
+  EntityPtr item = ValidMediaFeedItem();
+
+  PropertyPtr stats_property = Property::New();
+  stats_property->values = Values::New();
+  stats_property->name = schema_org::property::kInteractionStatistic;
+  {
+    EntityPtr interaction_statistic = Entity::New();
+    interaction_statistic->type = schema_org::entity::kInteractionCounter;
+    interaction_statistic->properties.push_back(
+        CreateStringProperty(schema_org::property::kInteractionType,
+                             "https://schema.org/WatchAction"));
+    interaction_statistic->properties.push_back(
+        CreateStringProperty(schema_org::property::kUserInteractionCount, "1"));
+    stats_property->values->entity_values.push_back(
+        std::move(interaction_statistic));
+  }
+  {
+    EntityPtr interaction_statistic = Entity::New();
+    interaction_statistic->type = schema_org::entity::kInteractionCounter;
+    interaction_statistic->properties.push_back(
+        CreateStringProperty(schema_org::property::kInteractionType,
+                             "https://schema.org/WatchAction"));
+    interaction_statistic->properties.push_back(
+        CreateStringProperty(schema_org::property::kUserInteractionCount, "3"));
+
+    stats_property->values->entity_values.push_back(
+        std::move(interaction_statistic));
+  }
+  item->properties.push_back(std::move(stats_property));
+
+  EntityPtr entity = AddItemToFeed(ValidMediaFeed(), std::move(item));
+
+  auto result = GetMediaFeeds(std::move(entity));
+
+  EXPECT_TRUE(result.has_value());
+  EXPECT_TRUE(result.value().empty());
+}
+
+TEST_F(MediaFeedsConverterTest, SucceedsItemWithRating) {
+  EntityPtr item = ValidMediaFeedItem();
+
+  {
+    EntityPtr rating = Entity::New();
+    rating->type = schema_org::entity::kRating;
+    rating->properties.push_back(
+        CreateStringProperty(schema_org::property::kAuthor, "MPAA"));
+    rating->properties.push_back(
+        CreateStringProperty(schema_org::property::kRatingValue, "G"));
+    item->properties.push_back(CreateEntityProperty(
+        schema_org::property::kContentRating, std::move(rating)));
+  }
+
+  EntityPtr entity = AddItemToFeed(ValidMediaFeed(), std::move(item));
+
+  mojom::MediaFeedItemPtr expected_item = ExpectedFeedItem();
+  mojom::ContentRatingPtr rating = mojom::ContentRating::New();
+  rating->agency = "MPAA";
+  rating->value = "G";
+  expected_item->content_ratings.push_back(std::move(rating));
+
+  auto result = GetMediaFeeds(std::move(entity));
+
+  EXPECT_TRUE(result.has_value());
+  ASSERT_EQ(result.value().size(), 1u);
+  EXPECT_EQ(expected_item, result.value()[0]);
+}
+
+// Fails because the rating property has a rating from an unknown agency.
+TEST_F(MediaFeedsConverterTest, FailsInvalidRating) {
+  EntityPtr item = ValidMediaFeedItem();
+
+  EntityPtr rating = Entity::New();
+  rating->type = schema_org::entity::kRating;
+  rating->properties.push_back(
+      CreateStringProperty(schema_org::property::kAuthor, "Google"));
+  rating->properties.push_back(
+      CreateStringProperty(schema_org::property::kRatingValue, "Googley"));
+  item->properties.push_back(CreateEntityProperty(
+      schema_org::property::kContentRating, std::move(rating)));
+
+  EntityPtr entity = AddItemToFeed(ValidMediaFeed(), std::move(item));
+
+  auto result = GetMediaFeeds(std::move(entity));
+
+  EXPECT_TRUE(result.has_value());
+  EXPECT_TRUE(result.value().empty());
+}
+
+TEST_F(MediaFeedsConverterTest, SucceedsItemWithGenre) {
+  EntityPtr item = ValidMediaFeedItem();
+
+  item->properties.push_back(
+      CreateStringProperty(schema_org::property::kGenre, "Action"));
+
+  EntityPtr entity = AddItemToFeed(ValidMediaFeed(), std::move(item));
+
+  mojom::MediaFeedItemPtr expected_item = ExpectedFeedItem();
+  expected_item->genre.push_back("Action");
+
+  auto result = GetMediaFeeds(std::move(entity));
+
+  EXPECT_TRUE(result.has_value());
+  ASSERT_EQ(result.value().size(), 1u);
+  EXPECT_EQ(expected_item, result.value()[0]);
+}
+
+TEST_F(MediaFeedsConverterTest, FailsItemWithInvalidGenre) {
+  EntityPtr item = ValidMediaFeedItem();
+
+  item->properties.push_back(
+      CreateStringProperty(schema_org::property::kGenre, ""));
+
+  EntityPtr entity = AddItemToFeed(ValidMediaFeed(), std::move(item));
+
+  auto result = GetMediaFeeds(std::move(entity));
+
+  EXPECT_TRUE(result.has_value());
+  EXPECT_TRUE(result.value().empty());
+}
+
+TEST_F(MediaFeedsConverterTest, SucceedsItemWithLiveDetails) {
+  EntityPtr item = ValidMediaFeedItem();
+
+  EntityPtr publication = Entity::New();
+  publication->type = schema_org::entity::kBroadcastEvent;
+  publication->properties.push_back(
+      CreateDateTimeProperty(schema_org::property::kStartDate, "2020-03-22"));
+  publication->properties.push_back(
+      CreateDateTimeProperty(schema_org::property::kEndDate, "2020-03-23"));
+  item->properties.push_back(CreateEntityProperty(
+      schema_org::property::kPublication, std::move(publication)));
+
+  EntityPtr entity = AddItemToFeed(ValidMediaFeed(), std::move(item));
+
+  mojom::MediaFeedItemPtr expected_item = ExpectedFeedItem();
+  expected_item->live = mojom::LiveDetails::New();
+  base::Time start_time, end_time;
+  bool parsed_start = base::Time::FromString("2020-03-22", &start_time);
+  bool parsed_end = base::Time::FromString("2020-03-23", &end_time);
+  DCHECK(parsed_start && parsed_end);
+  expected_item->live->start_time = start_time;
+  expected_item->live->end_time = end_time;
+
+  auto result = GetMediaFeeds(std::move(entity));
+
+  EXPECT_TRUE(result.has_value());
+  ASSERT_EQ(result.value().size(), 1u);
+  EXPECT_EQ(expected_item, result.value()[0]);
+}
+
+// Fails because the end date is string type instead of date type.
+TEST_F(MediaFeedsConverterTest, FailsItemWithInvalidLiveDetails) {
+  EntityPtr item = ValidMediaFeedItem();
+
+  EntityPtr publication = Entity::New();
+  publication->type = schema_org::entity::kBroadcastEvent;
+  publication->properties.push_back(
+      CreateDateTimeProperty(schema_org::property::kStartTime, "2020-03-22"));
+  publication->properties.push_back(
+      CreateStringProperty(schema_org::property::kEndTime, "2020-03-23"));
+  item->properties.push_back(CreateEntityProperty(
+      schema_org::property::kPublication, std::move(publication)));
+
+  EntityPtr entity = AddItemToFeed(ValidMediaFeed(), std::move(item));
+
+  auto result = GetMediaFeeds(std::move(entity));
+
+  EXPECT_TRUE(result.has_value());
+  EXPECT_TRUE(result.value().empty());
+}
+
+TEST_F(MediaFeedsConverterTest, SucceedsItemWithIdentifier) {
+  EntityPtr item = ValidMediaFeedItem();
+
+  {
+    EntityPtr identifier = Entity::New();
+    identifier->type = schema_org::entity::kPropertyValue;
+    identifier->properties.push_back(
+        CreateStringProperty(schema_org::property::kPropertyID, "TMS_ROOT_ID"));
+    identifier->properties.push_back(
+        CreateStringProperty(schema_org::property::kValue, "1"));
+    item->properties.push_back(CreateEntityProperty(
+        schema_org::property::kIdentifier, std::move(identifier)));
+  }
+
+  EntityPtr entity = AddItemToFeed(ValidMediaFeed(), std::move(item));
+
+  mojom::MediaFeedItemPtr expected_item = ExpectedFeedItem();
+  mojom::IdentifierPtr identifier = mojom::Identifier::New();
+  identifier->type = mojom::Identifier::Type::kTMSRootId;
+  identifier->value = "1";
+  expected_item->identifiers.push_back(std::move(identifier));
+
+  auto result = GetMediaFeeds(std::move(entity));
+
+  EXPECT_TRUE(result.has_value());
+  ASSERT_EQ(result.value().size(), 1u);
+  EXPECT_EQ(expected_item, result.value()[0]);
+}
+
+TEST_F(MediaFeedsConverterTest, SucceedsItemWithInvalidIdentifier) {
+  EntityPtr item = ValidMediaFeedItem();
+
+  {
+    EntityPtr identifier = Entity::New();
+    identifier->type = schema_org::entity::kPropertyValue;
+    identifier->properties.push_back(
+        CreateStringProperty(schema_org::property::kPropertyID, "Unknown"));
+    identifier->properties.push_back(
+        CreateStringProperty(schema_org::property::kValue, "1"));
+    item->properties.push_back(CreateEntityProperty(
+        schema_org::property::kPublication, std::move(identifier)));
+  }
+
+  EntityPtr entity = AddItemToFeed(ValidMediaFeed(), std::move(item));
+
+  auto result = GetMediaFeeds(std::move(entity));
+
+  EXPECT_TRUE(result.has_value());
+  EXPECT_TRUE(result.value().empty());
+}
+
+// Successfully converts a TV episode with embedded watch action and optional
+// identifiers.
+TEST_F(MediaFeedsConverterTest, SucceedsItemWithTVEpisode) {
+  EntityPtr item = ValidMediaFeedItem();
+  item->type = schema_org::entity::kTVSeries;
+  // Ignore the item's action field by changing the name. Use the action
+  // embedded in the TV episode instead.
+  GetProperty(item.get(), schema_org::property::kPotentialAction)->name =
+      "not an action";
+  item->properties.push_back(
+      CreateLongProperty(schema_org::property::kNumberOfEpisodes, 20));
+  item->properties.push_back(
+      CreateLongProperty(schema_org::property::kNumberOfSeasons, 6));
+
+  {
+    EntityPtr episode = Entity::New();
+    episode->type = schema_org::entity::kTVEpisode;
+    episode->properties.push_back(
+        CreateLongProperty(schema_org::property::kEpisodeNumber, 1));
+    episode->properties.push_back(
+        CreateStringProperty(schema_org::property::kName, "Pilot"));
+    EntityPtr identifier = Entity::New();
+    identifier->type = schema_org::entity::kPropertyValue;
+    identifier->properties.push_back(
+        CreateStringProperty(schema_org::property::kPropertyID, "TMS_ROOT_ID"));
+    identifier->properties.push_back(
+        CreateStringProperty(schema_org::property::kValue, "1"));
+    episode->properties.push_back(CreateEntityProperty(
+        schema_org::property::kIdentifier, std::move(identifier)));
+    episode->properties.push_back(CreateEntityProperty(
+        schema_org::property::kPotentialAction, ValidWatchAction()));
+    item->properties.push_back(CreateEntityProperty(
+        schema_org::property::kEpisode, std::move(episode)));
+  }
+
+  EntityPtr entity = AddItemToFeed(ValidMediaFeed(), std::move(item));
+
+  mojom::MediaFeedItemPtr expected_item = ExpectedFeedItem();
+  expected_item->type = mojom::MediaFeedItemType::kTVSeries;
+  expected_item->tv_episode = mojom::TVEpisode::New();
+  expected_item->tv_episode->episode_number = 1;
+  expected_item->tv_episode->name = "Pilot";
+  mojom::IdentifierPtr identifier = mojom::Identifier::New();
+  identifier->type = mojom::Identifier::Type::kTMSRootId;
+  identifier->value = "1";
+  expected_item->tv_episode->identifiers.push_back(std::move(identifier));
+
+  auto result = GetMediaFeeds(std::move(entity));
+
+  EXPECT_TRUE(result.has_value());
+  ASSERT_EQ(result.value().size(), 1u);
+  EXPECT_EQ(expected_item, result.value()[0]);
+}
+
+// Fails because TV episode is present, but TV episode name is empty.
+TEST_F(MediaFeedsConverterTest, FailsItemWithInvalidTVEpisode) {
+  EntityPtr item = ValidMediaFeedItem();
+  item->type = schema_org::entity::kTVSeries;
+  item->properties.push_back(
+      CreateLongProperty(schema_org::property::kNumberOfEpisodes, 20));
+  item->properties.push_back(
+      CreateLongProperty(schema_org::property::kNumberOfSeasons, 6));
+
+  EntityPtr episode = Entity::New();
+  episode->type = schema_org::entity::kTVEpisode;
+  episode->properties.push_back(
+      CreateLongProperty(schema_org::property::kEpisodeNumber, 1));
+  episode->properties.push_back(
+      CreateStringProperty(schema_org::property::kName, ""));
+  episode->properties.push_back(CreateEntityProperty(
+      schema_org::property::kPotentialAction, ValidWatchAction()));
+  item->properties.push_back(
+      CreateEntityProperty(schema_org::property::kEpisode, std::move(episode)));
+
+  EntityPtr entity = AddItemToFeed(ValidMediaFeed(), std::move(item));
+
+  auto result = GetMediaFeeds(std::move(entity));
+
+  EXPECT_TRUE(result.has_value());
+  EXPECT_TRUE(result.value().empty());
+}
+
+TEST_F(MediaFeedsConverterTest, SucceedsItemWithTVSeason) {
+  EntityPtr item = ValidMediaFeedItem();
+  item->type = schema_org::entity::kTVSeries;
+  item->properties.push_back(
+      CreateLongProperty(schema_org::property::kNumberOfEpisodes, 20));
+  item->properties.push_back(
+      CreateLongProperty(schema_org::property::kNumberOfSeasons, 6));
+
+  {
+    EntityPtr season = Entity::New();
+    season->type = schema_org::entity::kTVSeason;
+    season->properties.push_back(
+        CreateLongProperty(schema_org::property::kSeasonNumber, 1));
+    season->properties.push_back(
+        CreateLongProperty(schema_org::property::kNumberOfEpisodes, 20));
+    item->properties.push_back(CreateEntityProperty(
+        schema_org::property::kContainsSeason, std::move(season)));
+  }
+
+  EntityPtr entity = AddItemToFeed(ValidMediaFeed(), std::move(item));
+
+  mojom::MediaFeedItemPtr expected_item = ExpectedFeedItem();
+  expected_item->type = mojom::MediaFeedItemType::kTVSeries;
+  expected_item->tv_episode = mojom::TVEpisode::New();
+  expected_item->tv_episode->season_number = 1;
+
+  auto result = GetMediaFeeds(std::move(entity));
+
+  EXPECT_TRUE(result.has_value());
+  ASSERT_EQ(result.value().size(), 1u);
+  EXPECT_EQ(expected_item, result.value()[0]);
+}
+
+TEST_F(MediaFeedsConverterTest, FailsItemWithInvalidTVSeason) {
+  EntityPtr item = ValidMediaFeedItem();
+  item->type = schema_org::entity::kTVSeries;
+  item->properties.push_back(
+      CreateLongProperty(schema_org::property::kNumberOfEpisodes, 20));
+  item->properties.push_back(
+      CreateLongProperty(schema_org::property::kNumberOfSeasons, 6));
+
+  {
+    EntityPtr season = Entity::New();
+    season->type = schema_org::entity::kTVSeason;
+    season->properties.push_back(
+        CreateLongProperty(schema_org::property::kSeasonNumber, 1));
+    season->properties.push_back(
+        CreateLongProperty(schema_org::property::kNumberOfEpisodes, -1));
+    item->properties.push_back(CreateEntityProperty(
+        schema_org::property::kContainsSeason, std::move(season)));
+  }
+
+  EntityPtr entity = AddItemToFeed(ValidMediaFeed(), std::move(item));
+
+  auto result = GetMediaFeeds(std::move(entity));
+
+  EXPECT_TRUE(result.has_value());
+  EXPECT_TRUE(result.value().empty());
+}
+
+}  // namespace media_feeds
diff --git a/chrome/browser/media/feeds/media_feeds_fetcher.cc b/chrome/browser/media/feeds/media_feeds_fetcher.cc
index fba813f7..8f80e8c 100644
--- a/chrome/browser/media/feeds/media_feeds_fetcher.cc
+++ b/chrome/browser/media/feeds/media_feeds_fetcher.cc
@@ -64,6 +64,10 @@
   resource_request->redirect_mode = ::network::mojom::RedirectMode::kError;
   resource_request->attach_same_site_cookies = true;
   resource_request->site_for_cookies = net::SiteForCookies::FromUrl(url);
+  url::Origin origin = url::Origin::Create(url);
+  resource_request->trusted_params = network::ResourceRequest::TrustedParams();
+  resource_request->trusted_params->network_isolation_key =
+      net::NetworkIsolationKey(origin, origin);
 
   DCHECK(!pending_request_);
   pending_request_ = network::SimpleURLLoader::Create(
diff --git a/chrome/browser/media/history/media_history_store.cc b/chrome/browser/media/history/media_history_store.cc
index 5dab686..a8e4cc4 100644
--- a/chrome/browser/media/history/media_history_store.cc
+++ b/chrome/browser/media/history/media_history_store.cc
@@ -6,6 +6,7 @@
 
 #include "base/callback.h"
 #include "base/files/file_path.h"
+#include "base/files/file_util.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/stringprintf.h"
 #include "base/task_runner_util.h"
@@ -274,6 +275,17 @@
 
   base::UmaHistogramEnumeration(MediaHistoryStore::kInitResultHistogramName,
                                 MediaHistoryStore::InitResult::kSuccess);
+
+  // Get the size in bytes.
+  int64_t file_size = 0;
+  base::GetFileSize(db_path_, &file_size);
+  DCHECK_NE(0, file_size);
+
+  // Record the size in KB.
+  if (file_size > 0) {
+    base::UmaHistogramMemoryKB(MediaHistoryStore::kDatabaseSizeKbHistogramName,
+                               file_size / 1000);
+  }
 }
 
 sql::InitStatus MediaHistoryStoreInternal::CreateOrUpgradeIfNeeded() {
@@ -724,6 +736,9 @@
 const char MediaHistoryStore::kSessionWriteResultHistogramName[] =
     "Media.History.Session.WriteResult";
 
+const char MediaHistoryStore::kDatabaseSizeKbHistogramName[] =
+    "Media.History.DatabaseSize";
+
 MediaHistoryStore::MediaHistoryStore(
     Profile* profile,
     scoped_refptr<base::UpdateableSequencedTaskRunner> db_task_runner)
diff --git a/chrome/browser/media/history/media_history_store.h b/chrome/browser/media/history/media_history_store.h
index d46545c..faef435 100644
--- a/chrome/browser/media/history/media_history_store.h
+++ b/chrome/browser/media/history/media_history_store.h
@@ -60,6 +60,7 @@
   static const char kInitResultHistogramName[];
   static const char kPlaybackWriteResultHistogramName[];
   static const char kSessionWriteResultHistogramName[];
+  static const char kDatabaseSizeKbHistogramName[];
 
   // When we initialize the database we store the result in
   // |kInitResultHistogramName|. Do not change the numbering since this
diff --git a/chrome/browser/media/history/media_history_store_unittest.cc b/chrome/browser/media/history/media_history_store_unittest.cc
index 75f8bfa..b25afba 100644
--- a/chrome/browser/media/history/media_history_store_unittest.cc
+++ b/chrome/browser/media/history/media_history_store_unittest.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/media/history/media_history_store.h"
 
 #include "base/files/file_path.h"
+#include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/optional.h"
 #include "base/run_loop.h"
@@ -125,6 +126,14 @@
         temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Media History"));
     ASSERT_TRUE(db_.Open(db_file));
 
+    // Get the size in bytes.
+    int64_t file_size = 0;
+    base::GetFileSize(db_file, &file_size);
+    EXPECT_LT(0, file_size);
+
+    histogram_tester.ExpectUniqueSample(
+        MediaHistoryStore::kDatabaseSizeKbHistogramName, file_size / 1000, 1);
+
     // Set up the media history store for OTR.
     otr_service_ = std::make_unique<MediaHistoryKeyedService>(
         profile_->GetOffTheRecordProfile());
diff --git a/chrome/browser/media/media_access_handler.cc b/chrome/browser/media/media_access_handler.cc
index 3fd6509..aad9aac 100644
--- a/chrome/browser/media/media_access_handler.cc
+++ b/chrome/browser/media/media_access_handler.cc
@@ -9,7 +9,6 @@
 
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
 #include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
-#include "chrome/browser/profiles/profile.h"
 #include "content/public/browser/web_contents.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
 
@@ -68,14 +67,13 @@
     }
   }
 
-  Profile* profile =
-      Profile::FromBrowserContext(web_contents->GetBrowserContext());
-
   // If either or both audio and video devices were requested but not
   // specified by id, get the default devices.
   if (get_default_audio_device || get_default_video_device) {
-    MediaCaptureDevicesDispatcher::GetInstance()->GetDefaultDevicesForProfile(
-        profile, get_default_audio_device, get_default_video_device, &devices);
+    MediaCaptureDevicesDispatcher::GetInstance()
+        ->GetDefaultDevicesForBrowserContext(
+            web_contents->GetBrowserContext(), get_default_audio_device,
+            get_default_video_device, &devices);
   }
 
   std::unique_ptr<content::MediaStreamUI> ui;
diff --git a/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc b/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc
index 4e633de..b4cd47b 100644
--- a/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc
+++ b/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc
@@ -64,19 +64,6 @@
 
 namespace {
 
-// Finds a device in |devices| that has |device_id|, or NULL if not found.
-const blink::MediaStreamDevice* FindDeviceWithId(
-    const blink::MediaStreamDevices& devices,
-    const std::string& device_id) {
-  auto iter = devices.begin();
-  for (; iter != devices.end(); ++iter) {
-    if (iter->id == device_id) {
-      return &(*iter);
-    }
-  }
-  return NULL;
-}
-
 content::WebContents* WebContentsFromIds(int render_process_id,
                                          int render_frame_id) {
   content::WebContents* web_contents =
@@ -156,24 +143,6 @@
   observers_.RemoveObserver(observer);
 }
 
-const MediaStreamDevices&
-MediaCaptureDevicesDispatcher::GetAudioCaptureDevices() {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  if (is_device_enumeration_disabled_ || !test_audio_devices_.empty())
-    return test_audio_devices_;
-
-  return MediaCaptureDevices::GetInstance()->GetAudioCaptureDevices();
-}
-
-const MediaStreamDevices&
-MediaCaptureDevicesDispatcher::GetVideoCaptureDevices() {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  if (is_device_enumeration_disabled_ || !test_video_devices_.empty())
-    return test_video_devices_;
-
-  return MediaCaptureDevices::GetInstance()->GetVideoCaptureDevices();
-}
-
 void MediaCaptureDevicesDispatcher::ProcessMediaAccessRequest(
     content::WebContents* web_contents,
     const content::MediaStreamRequest& request,
@@ -233,35 +202,8 @@
   return false;
 }
 
-void MediaCaptureDevicesDispatcher::GetDefaultDevicesForProfile(
-    Profile* profile,
-    bool audio,
-    bool video,
-    blink::MediaStreamDevices* devices) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  DCHECK(audio || video);
-
-  PrefService* prefs = profile->GetPrefs();
-  std::string default_device;
-  if (audio) {
-    default_device = prefs->GetString(prefs::kDefaultAudioCaptureDevice);
-    const blink::MediaStreamDevice* device =
-        GetRequestedAudioDevice(default_device);
-    if (!device)
-      device = GetFirstAvailableAudioDevice();
-    if (device)
-      devices->push_back(*device);
-  }
-
-  if (video) {
-    default_device = prefs->GetString(prefs::kDefaultVideoCaptureDevice);
-    const blink::MediaStreamDevice* device =
-        GetRequestedVideoDevice(default_device);
-    if (!device)
-      device = GetFirstAvailableVideoDevice();
-    if (device)
-      devices->push_back(*device);
-  }
+void MediaCaptureDevicesDispatcher::DisableDeviceEnumerationForTesting() {
+  is_device_enumeration_disabled_ = true;
 }
 
 std::string MediaCaptureDevicesDispatcher::GetDefaultDeviceIDForProfile(
@@ -277,6 +219,62 @@
     return std::string();
 }
 
+const MediaStreamDevices&
+MediaCaptureDevicesDispatcher::GetAudioCaptureDevices() const {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  if (is_device_enumeration_disabled_ || !test_audio_devices_.empty())
+    return test_audio_devices_;
+
+  return MediaCaptureDevices::GetInstance()->GetAudioCaptureDevices();
+}
+
+const MediaStreamDevices&
+MediaCaptureDevicesDispatcher::GetVideoCaptureDevices() const {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  if (is_device_enumeration_disabled_ || !test_video_devices_.empty())
+    return test_video_devices_;
+
+  return MediaCaptureDevices::GetInstance()->GetVideoCaptureDevices();
+}
+
+void MediaCaptureDevicesDispatcher::GetDefaultDevicesForBrowserContext(
+    content::BrowserContext* context,
+    bool audio,
+    bool video,
+    blink::MediaStreamDevices* devices) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK(audio || video);
+
+  PrefService* prefs = Profile::FromBrowserContext(context)->GetPrefs();
+  std::string default_device;
+  if (audio) {
+    default_device = prefs->GetString(prefs::kDefaultAudioCaptureDevice);
+    const blink::MediaStreamDevice* device =
+        GetRequestedAudioDevice(default_device);
+    if (device) {
+      devices->push_back(*device);
+    } else {
+      const blink::MediaStreamDevices& audio_devices = GetAudioCaptureDevices();
+      if (!audio_devices.empty())
+        devices->push_back(audio_devices.front());
+    }
+  }
+
+  if (video) {
+    default_device = prefs->GetString(prefs::kDefaultVideoCaptureDevice);
+    const blink::MediaStreamDevice* device =
+        GetRequestedVideoDevice(default_device);
+    if (device) {
+      devices->push_back(*device);
+    } else {
+      const blink::MediaStreamDevices& video_devices = GetVideoCaptureDevices();
+      if (!video_devices.empty())
+        devices->push_back(video_devices.front());
+    }
+  }
+}
+
+#if 0
 const blink::MediaStreamDevice*
 MediaCaptureDevicesDispatcher::GetRequestedAudioDevice(
     const std::string& requested_audio_device_id) {
@@ -288,15 +286,6 @@
 }
 
 const blink::MediaStreamDevice*
-MediaCaptureDevicesDispatcher::GetFirstAvailableAudioDevice() {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  const blink::MediaStreamDevices& audio_devices = GetAudioCaptureDevices();
-  if (audio_devices.empty())
-    return NULL;
-  return &(*audio_devices.begin());
-}
-
-const blink::MediaStreamDevice*
 MediaCaptureDevicesDispatcher::GetRequestedVideoDevice(
     const std::string& requested_video_device_id) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
@@ -305,19 +294,7 @@
       FindDeviceWithId(video_devices, requested_video_device_id);
   return device;
 }
-
-const blink::MediaStreamDevice*
-MediaCaptureDevicesDispatcher::GetFirstAvailableVideoDevice() {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  const blink::MediaStreamDevices& video_devices = GetVideoCaptureDevices();
-  if (video_devices.empty())
-    return NULL;
-  return &(*video_devices.begin());
-}
-
-void MediaCaptureDevicesDispatcher::DisableDeviceEnumerationForTesting() {
-  is_device_enumeration_disabled_ = true;
-}
+#endif
 
 scoped_refptr<MediaStreamCaptureIndicator>
 MediaCaptureDevicesDispatcher::GetMediaStreamCaptureIndicator() {
diff --git a/chrome/browser/media/webrtc/media_capture_devices_dispatcher.h b/chrome/browser/media/webrtc/media_capture_devices_dispatcher.h
index 074c5d3..c5dd2b0 100644
--- a/chrome/browser/media/webrtc/media_capture_devices_dispatcher.h
+++ b/chrome/browser/media/webrtc/media_capture_devices_dispatcher.h
@@ -15,6 +15,7 @@
 #include "base/macros.h"
 #include "base/memory/singleton.h"
 #include "base/observer_list.h"
+#include "components/webrtc/media_stream_device_enumerator_impl.h"
 #include "content/public/browser/media_observer.h"
 #include "content/public/browser/media_stream_request.h"
 #include "content/public/browser/web_contents_delegate.h"
@@ -34,7 +35,9 @@
 
 // This singleton is used to receive updates about media events from the content
 // layer.
-class MediaCaptureDevicesDispatcher : public content::MediaObserver {
+class MediaCaptureDevicesDispatcher
+    : public content::MediaObserver,
+      public webrtc::MediaStreamDeviceEnumeratorImpl {
  public:
   class Observer {
    public:
@@ -74,8 +77,6 @@
   // on destruction.
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
-  const blink::MediaStreamDevices& GetAudioCaptureDevices();
-  const blink::MediaStreamDevices& GetVideoCaptureDevices();
 
   // Method called from WebCapturerDelegate implementations to process access
   // requests. |extension| is set to NULL if request was made from a drive-by
@@ -97,15 +98,10 @@
                                   blink::mojom::MediaStreamType type,
                                   const extensions::Extension* extension);
 
-  // Helper to get the default devices which can be used by the media request.
-  // Uses the first available devices if the default devices are not available.
-  // If the return list is empty, it means there is no available device on the
-  // OS.
-  // Called on the UI thread.
-  void GetDefaultDevicesForProfile(Profile* profile,
-                                   bool audio,
-                                   bool video,
-                                   blink::MediaStreamDevices* devices);
+  // Unittests that do not require actual device enumeration should call this
+  // API on the singleton. It is safe to call this multiple times on the
+  // signleton.
+  void DisableDeviceEnumerationForTesting();
 
   // Helper to get default device IDs. If the returned value is an empty string,
   // it means that there is no default device for the given device |type|. The
@@ -116,24 +112,16 @@
   std::string GetDefaultDeviceIDForProfile(Profile* profile,
                                            blink::mojom::MediaStreamType type);
 
-  // Helpers for picking particular requested devices, identified by raw id.
-  // If the device requested is not available it will return NULL.
-  const blink::MediaStreamDevice* GetRequestedAudioDevice(
-      const std::string& requested_audio_device_id);
-  const blink::MediaStreamDevice* GetRequestedVideoDevice(
-      const std::string& requested_video_device_id);
+  // webrtc::MediaStreamDeviceEnumeratorImpl:
+  const blink::MediaStreamDevices& GetAudioCaptureDevices() const override;
+  const blink::MediaStreamDevices& GetVideoCaptureDevices() const override;
+  void GetDefaultDevicesForBrowserContext(
+      content::BrowserContext* context,
+      bool audio,
+      bool video,
+      blink::MediaStreamDevices* devices) override;
 
-  // Returns the first available audio or video device, or NULL if no devices
-  // are available.
-  const blink::MediaStreamDevice* GetFirstAvailableAudioDevice();
-  const blink::MediaStreamDevice* GetFirstAvailableVideoDevice();
-
-  // Unittests that do not require actual device enumeration should call this
-  // API on the singleton. It is safe to call this multiple times on the
-  // signleton.
-  void DisableDeviceEnumerationForTesting();
-
-  // Overridden from content::MediaObserver:
+  // content::MediaObserver:
   void OnAudioCaptureDevicesChanged() override;
   void OnVideoCaptureDevicesChanged() override;
   void OnMediaRequestStateChanged(int render_process_id,
diff --git a/chrome/browser/media/webrtc/media_stream_devices_controller_browsertest.cc b/chrome/browser/media/webrtc/media_stream_devices_controller_browsertest.cc
index ad88c0d..8761339 100644
--- a/chrome/browser/media/webrtc/media_stream_devices_controller_browsertest.cc
+++ b/chrome/browser/media/webrtc/media_stream_devices_controller_browsertest.cc
@@ -14,7 +14,7 @@
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
 #include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
 #include "chrome/browser/media/webrtc/media_stream_device_permissions.h"
-#include "chrome/browser/media/webrtc/media_stream_devices_controller.h"
+#include "chrome/browser/media/webrtc/permission_bubble_media_access_handler.h"
 #include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
@@ -46,10 +46,10 @@
             blink::mojom::MediaStreamRequestResult::NUM_MEDIA_REQUEST_RESULTS) {
   }
 
-  // Dummy callback for when we deny the current request directly.
   void OnMediaStreamResponse(const blink::MediaStreamDevices& devices,
                              blink::mojom::MediaStreamRequestResult result,
                              std::unique_ptr<content::MediaStreamUI> ui) {
+    EXPECT_EQ(devices.empty(), !ui);
     media_stream_devices_ = devices;
     media_stream_result_ = result;
     quit_closure_.Run();
@@ -74,13 +74,15 @@
   }
 
   void RequestPermissions(content::WebContents* web_contents,
-                          const content::MediaStreamRequest& request,
-                          content::MediaResponseCallback callback) {
+                          const content::MediaStreamRequest& request) {
     base::RunLoop run_loop;
     ASSERT_TRUE(quit_closure_.is_null());
     quit_closure_ = run_loop.QuitClosure();
-    MediaStreamDevicesController::RequestPermissions(request,
-                                                     std::move(callback));
+    permission_bubble_media_access_handler_->HandleRequest(
+        web_contents, request,
+        base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
+                       base::Unretained(this)),
+        nullptr);
     run_loop.Run();
   }
 
@@ -176,6 +178,9 @@
 
     ASSERT_TRUE(embedded_test_server()->Start());
 
+    permission_bubble_media_access_handler_ =
+        std::make_unique<PermissionBubbleMediaAccessHandler>();
+
     permissions::PermissionRequestManager* manager =
         permissions::PermissionRequestManager::FromWebContents(
             browser()->tab_strip_model()->GetActiveWebContents());
@@ -205,6 +210,7 @@
   }
 
   void TearDownOnMainThread() override {
+    permission_bubble_media_access_handler_.reset();
     prompt_factory_.reset();
 
     WebRtcTestBase::TearDownOnMainThread();
@@ -220,6 +226,9 @@
   base::Closure quit_closure_;
 
   std::unique_ptr<permissions::MockPermissionPromptFactory> prompt_factory_;
+
+  std::unique_ptr<PermissionBubbleMediaAccessHandler>
+      permission_bubble_media_access_handler_;
 };
 
 // Request and allow microphone access.
@@ -230,10 +239,8 @@
   // settings are updated.
   prompt_factory()->set_response_type(
       permissions::PermissionRequestManager::ACCEPT_ALL);
-  RequestPermissions(
-      GetWebContents(), CreateRequest(example_audio_id(), std::string()),
-      base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
-                     base::Unretained(this)));
+  RequestPermissions(GetWebContents(),
+                     CreateRequest(example_audio_id(), std::string()));
 
   EXPECT_TRUE(GetContentSettings()->IsContentAllowed(
       ContentSettingsType::MEDIASTREAM_MIC));
@@ -259,10 +266,8 @@
   // settings are updated.
   prompt_factory()->set_response_type(
       permissions::PermissionRequestManager::ACCEPT_ALL);
-  RequestPermissions(
-      GetWebContents(), CreateRequest(std::string(), example_video_id()),
-      base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
-                     base::Unretained(this)));
+  RequestPermissions(GetWebContents(),
+                     CreateRequest(std::string(), example_video_id()));
 
   EXPECT_TRUE(GetContentSettings()->IsContentAllowed(
       ContentSettingsType::MEDIASTREAM_CAMERA));
@@ -288,10 +293,8 @@
   // settings are updated.
   prompt_factory()->set_response_type(
       permissions::PermissionRequestManager::ACCEPT_ALL);
-  RequestPermissions(
-      GetWebContents(), CreateRequest(example_audio_id(), std::string()),
-      base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
-                     base::Unretained(this)));
+  RequestPermissions(GetWebContents(),
+                     CreateRequest(example_audio_id(), std::string()));
 
   EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
       ContentSettingsType::MEDIASTREAM_MIC));
@@ -318,10 +321,8 @@
   // settings are updated.
   prompt_factory()->set_response_type(
       permissions::PermissionRequestManager::ACCEPT_ALL);
-  RequestPermissions(
-      GetWebContents(), CreateRequest(std::string(), example_video_id()),
-      base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
-                     base::Unretained(this)));
+  RequestPermissions(GetWebContents(),
+                     CreateRequest(std::string(), example_video_id()));
 
   EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
       ContentSettingsType::MEDIASTREAM_CAMERA));
@@ -350,10 +351,8 @@
   // settings are updated.
   prompt_factory()->set_response_type(
       permissions::PermissionRequestManager::ACCEPT_ALL);
-  RequestPermissions(
-      GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
-      base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
-                     base::Unretained(this)));
+  RequestPermissions(GetWebContents(),
+                     CreateRequest(example_audio_id(), example_video_id()));
 
   EXPECT_TRUE(GetContentSettings()->IsContentAllowed(
       ContentSettingsType::MEDIASTREAM_MIC));
@@ -386,10 +385,8 @@
   // settings are updated.
   prompt_factory()->set_response_type(
       permissions::PermissionRequestManager::ACCEPT_ALL);
-  RequestPermissions(
-      GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
-      base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
-                     base::Unretained(this)));
+  RequestPermissions(GetWebContents(),
+                     CreateRequest(example_audio_id(), example_video_id()));
 
   EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
       ContentSettingsType::MEDIASTREAM_MIC));
@@ -425,10 +422,8 @@
   // settings are updated.
   prompt_factory()->set_response_type(
       permissions::PermissionRequestManager::ACCEPT_ALL);
-  RequestPermissions(
-      GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
-      base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
-                     base::Unretained(this)));
+  RequestPermissions(GetWebContents(),
+                     CreateRequest(example_audio_id(), example_video_id()));
 
   EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
       ContentSettingsType::MEDIASTREAM_MIC));
@@ -464,10 +459,8 @@
   // settings are updated.
   prompt_factory()->set_response_type(
       permissions::PermissionRequestManager::ACCEPT_ALL);
-  RequestPermissions(
-      GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
-      base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
-                     base::Unretained(this)));
+  RequestPermissions(GetWebContents(),
+                     CreateRequest(example_audio_id(), example_video_id()));
 
   EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
       ContentSettingsType::MEDIASTREAM_MIC));
@@ -503,10 +496,8 @@
   // settings are updated.
   prompt_factory()->set_response_type(
       permissions::PermissionRequestManager::ACCEPT_ALL);
-  RequestPermissions(
-      GetWebContents(), CreateRequest(example_audio_id(), std::string()),
-      base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
-                     base::Unretained(this)));
+  RequestPermissions(GetWebContents(),
+                     CreateRequest(example_audio_id(), std::string()));
   EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
       ContentSettingsType::MEDIASTREAM_MIC));
   EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
@@ -518,10 +509,8 @@
 
   // Request cam and allow
   SetDevicePolicy(DEVICE_TYPE_VIDEO, ACCESS_ALLOWED);
-  RequestPermissions(
-      GetWebContents(), CreateRequest(std::string(), example_video_id()),
-      base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
-                     base::Unretained(this)));
+  RequestPermissions(GetWebContents(),
+                     CreateRequest(std::string(), example_video_id()));
   EXPECT_TRUE(GetContentSettings()->IsContentAllowed(
       ContentSettingsType::MEDIASTREAM_CAMERA));
   EXPECT_FALSE(GetContentSettings()->IsContentBlocked(
@@ -552,10 +541,8 @@
   // settings are updated.
   prompt_factory()->set_response_type(
       permissions::PermissionRequestManager::ACCEPT_ALL);
-  RequestPermissions(
-      GetWebContents(), CreateRequest(std::string(), example_video_id()),
-      base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
-                     base::Unretained(this)));
+  RequestPermissions(GetWebContents(),
+                     CreateRequest(std::string(), example_video_id()));
   EXPECT_TRUE(GetContentSettings()->IsContentAllowed(
       ContentSettingsType::MEDIASTREAM_CAMERA));
   EXPECT_FALSE(GetContentSettings()->IsContentBlocked(
@@ -587,10 +574,8 @@
   // settings are updated.
   prompt_factory()->set_response_type(
       permissions::PermissionRequestManager::ACCEPT_ALL);
-  RequestPermissions(
-      GetWebContents(), CreateRequest(example_audio_id(), std::string()),
-      base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
-                     base::Unretained(this)));
+  RequestPermissions(GetWebContents(),
+                     CreateRequest(example_audio_id(), std::string()));
   EXPECT_FALSE(GetContentSettings()->IsContentAllowed(
       ContentSettingsType::MEDIASTREAM_MIC));
   EXPECT_TRUE(GetContentSettings()->IsContentBlocked(
@@ -702,10 +687,8 @@
       prompt_factory()->set_response_type(
           permissions::PermissionRequestManager::NONE);
     }
-    RequestPermissions(
-        GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
-        base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
-                       base::Unretained(this)));
+    RequestPermissions(GetWebContents(),
+                       CreateRequest(example_audio_id(), example_video_id()));
 
     ASSERT_LE(prompt_factory()->TotalRequestCount(), 2);
     ASSERT_EQ(
@@ -733,10 +716,8 @@
 IN_PROC_BROWSER_TEST_F(MediaStreamDevicesControllerTest,
                        WebUIRequestAndAllowCam) {
   InitWithUrl(GURL(chrome::kChromeUIVersionURL));
-  RequestPermissions(
-      GetWebContents(), CreateRequest(std::string(), example_video_id()),
-      base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
-                     base::Unretained(this)));
+  RequestPermissions(GetWebContents(),
+                     CreateRequest(std::string(), example_video_id()));
 
   ASSERT_EQ(0, prompt_factory()->TotalRequestCount());
 
@@ -756,10 +737,8 @@
   // Test that a prompt is required.
   prompt_factory()->set_response_type(
       permissions::PermissionRequestManager::ACCEPT_ALL);
-  RequestPermissions(
-      GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
-      base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
-                     base::Unretained(this)));
+  RequestPermissions(GetWebContents(),
+                     CreateRequest(example_audio_id(), example_video_id()));
   ASSERT_EQ(2, prompt_factory()->TotalRequestCount());
   ASSERT_TRUE(prompt_factory()->RequestTypeSeen(
       permissions::PermissionRequestType::PERMISSION_MEDIASTREAM_CAMERA));
@@ -775,10 +754,8 @@
 
   // Check that re-requesting allows without prompting.
   prompt_factory()->ResetCounts();
-  RequestPermissions(
-      GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
-      base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
-                     base::Unretained(this)));
+  RequestPermissions(GetWebContents(),
+                     CreateRequest(example_audio_id(), example_video_id()));
   ASSERT_EQ(0, prompt_factory()->TotalRequestCount());
 
   ASSERT_EQ(blink::mojom::MediaStreamRequestResult::OK, media_stream_result());
@@ -798,9 +775,7 @@
   RequestPermissions(
       GetWebContents(),
       CreateRequestWithType(example_audio_id(), example_video_id(),
-                            blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY),
-      base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
-                     base::Unretained(this)));
+                            blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY));
   ASSERT_EQ(0, prompt_factory()->TotalRequestCount());
 
   ASSERT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
@@ -823,10 +798,7 @@
   request.render_frame_id = 0;
   request.render_process_id = 0;
 
-  RequestPermissions(
-      nullptr, request,
-      base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
-                     base::Unretained(this)));
+  RequestPermissions(nullptr, request);
   ASSERT_EQ(0, prompt_factory()->TotalRequestCount());
 
   ASSERT_EQ(blink::mojom::MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN,
@@ -856,10 +828,8 @@
   InitWithUrl(embedded_test_server()->GetURL("/simple.html"));
   SetDevicePolicy(DEVICE_TYPE_AUDIO, ACCESS_ALLOWED);
   SetDevicePolicy(DEVICE_TYPE_VIDEO, ACCESS_ALLOWED);
-  RequestPermissions(
-      GetWebContents(), CreateRequest(example_audio_id(), example_video_id()),
-      base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
-                     base::Unretained(this)));
+  RequestPermissions(GetWebContents(),
+                     CreateRequest(example_audio_id(), example_video_id()));
 
   ASSERT_EQ(0, prompt_factory()->TotalRequestCount());
 
@@ -891,10 +861,7 @@
   // Make the child frame the source of the request.
   request.render_process_id = child_frame->GetProcess()->GetID();
   request.render_frame_id = child_frame->GetRoutingID();
-  RequestPermissions(
-      GetWebContents(), request,
-      base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
-                     base::Unretained(this)));
+  RequestPermissions(GetWebContents(), request);
 
   ASSERT_EQ(0, prompt_factory()->TotalRequestCount());
 
@@ -928,10 +895,7 @@
   // Make the child frame the source of the request.
   request.render_process_id = child_frame->GetProcess()->GetID();
   request.render_frame_id = child_frame->GetRoutingID();
-  RequestPermissions(
-      GetWebContents(), request,
-      base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
-                     base::Unretained(this)));
+  RequestPermissions(GetWebContents(), request);
 
   ASSERT_EQ(0, prompt_factory()->TotalRequestCount());
 
@@ -952,9 +916,7 @@
   RequestPermissions(
       GetWebContents(),
       CreateRequestWithType(example_audio_id(), std::string(),
-                            blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY),
-      base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
-                     base::Unretained(this)));
+                            blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY));
 
   EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, media_stream_result());
   EXPECT_TRUE(CheckDevicesListContains(
@@ -970,9 +932,7 @@
   RequestPermissions(
       GetWebContents(),
       CreateRequestWithType(std::string(), example_video_id(),
-                            blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY),
-      base::BindOnce(&MediaStreamDevicesControllerTest::OnMediaStreamResponse,
-                     base::Unretained(this)));
+                            blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY));
 
   EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, media_stream_result());
   EXPECT_FALSE(CheckDevicesListContains(
diff --git a/chrome/browser/media/webrtc/media_stream_infobar_browsertest.cc b/chrome/browser/media/webrtc/media_stream_infobar_browsertest.cc
index 542e453..a96025d1 100644
--- a/chrome/browser/media/webrtc/media_stream_infobar_browsertest.cc
+++ b/chrome/browser/media/webrtc/media_stream_infobar_browsertest.cc
@@ -7,7 +7,6 @@
 #include "base/macros.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
-#include "chrome/browser/media/webrtc/media_stream_devices_controller.h"
 #include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
 #include "chrome/browser/media/webrtc/webrtc_browsertest_common.h"
 #include "chrome/browser/profiles/profile.h"
diff --git a/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc b/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
index 50b1ffab..d53732e2 100644
--- a/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
+++ b/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
@@ -12,14 +12,19 @@
 #include "base/metrics/field_trial.h"
 #include "base/task/post_task.h"
 #include "build/build_config.h"
+#include "chrome/browser/content_settings/tab_specific_content_settings.h"
+#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
+#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
 #include "chrome/browser/media/webrtc/media_stream_device_permissions.h"
-#include "chrome/browser/media/webrtc/media_stream_devices_controller.h"
 #include "chrome/browser/permissions/permission_manager_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/pref_names.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/permissions/permission_manager.h"
 #include "components/permissions/permission_result.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_service.h"
+#include "components/webrtc/media_stream_devices_controller.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/notification_service.h"
@@ -53,6 +58,66 @@
 using system_media_permissions::SystemPermission;
 #endif
 
+namespace {
+
+void UpdateTabSpecificContentSettings(
+    content::WebContents* web_contents,
+    const content::MediaStreamRequest& request,
+    ContentSetting audio_setting,
+    ContentSetting video_setting) {
+  if (!web_contents)
+    return;
+
+  auto* content_settings =
+      TabSpecificContentSettings::FromWebContents(web_contents);
+  if (!content_settings)
+    return;
+
+  TabSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
+      TabSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED;
+  std::string selected_audio_device;
+  std::string selected_video_device;
+  std::string requested_audio_device = request.requested_audio_device_id;
+  std::string requested_video_device = request.requested_video_device_id;
+
+  // TODO(raymes): Why do we use the defaults here for the selected devices?
+  // Shouldn't we just use the devices that were actually selected?
+  Profile* profile =
+      Profile::FromBrowserContext(web_contents->GetBrowserContext());
+  if (audio_setting != CONTENT_SETTING_DEFAULT) {
+    selected_audio_device =
+        requested_audio_device.empty()
+            ? profile->GetPrefs()->GetString(prefs::kDefaultAudioCaptureDevice)
+            : requested_audio_device;
+    microphone_camera_state |=
+        TabSpecificContentSettings::MICROPHONE_ACCESSED |
+        (audio_setting == CONTENT_SETTING_ALLOW
+             ? 0
+             : TabSpecificContentSettings::MICROPHONE_BLOCKED);
+  }
+
+  if (video_setting != CONTENT_SETTING_DEFAULT) {
+    selected_video_device =
+        requested_video_device.empty()
+            ? profile->GetPrefs()->GetString(prefs::kDefaultVideoCaptureDevice)
+            : requested_video_device;
+    microphone_camera_state |=
+        TabSpecificContentSettings::CAMERA_ACCESSED |
+        (video_setting == CONTENT_SETTING_ALLOW
+             ? 0
+             : TabSpecificContentSettings::CAMERA_BLOCKED);
+  }
+
+  content_settings->OnMediaStreamPermissionSet(
+      PermissionManagerFactory::GetForProfile(profile)->GetCanonicalOrigin(
+          ContentSettingsType::MEDIASTREAM_CAMERA, request.security_origin,
+          web_contents->GetLastCommittedURL()),
+      microphone_camera_state, selected_audio_device, selected_video_device,
+      requested_audio_device, requested_video_device);
+}
+
+}  // namespace
+
 struct PermissionBubbleMediaAccessHandler::PendingAccessRequest {
   PendingAccessRequest(const content::MediaStreamRequest& request,
                        RepeatingMediaResponseCallback callback)
@@ -175,10 +240,11 @@
   }
 #endif
 
-  MediaStreamDevicesController::RequestPermissions(
-      request, base::BindOnce(
-                   &PermissionBubbleMediaAccessHandler::OnAccessRequestResponse,
-                   base::Unretained(this), web_contents, request_id));
+  webrtc::MediaStreamDevicesController::RequestPermissions(
+      request, MediaCaptureDevicesDispatcher::GetInstance(),
+      base::BindOnce(
+          &PermissionBubbleMediaAccessHandler::OnMediaStreamRequestResponse,
+          base::Unretained(this), web_contents, request_id, request));
 }
 
 void PermissionBubbleMediaAccessHandler::UpdateMediaRequestState(
@@ -210,6 +276,42 @@
   }
 }
 
+// static
+void PermissionBubbleMediaAccessHandler::RegisterProfilePrefs(
+    user_prefs::PrefRegistrySyncable* prefs) {
+  prefs->RegisterBooleanPref(prefs::kVideoCaptureAllowed, true);
+  prefs->RegisterBooleanPref(prefs::kAudioCaptureAllowed, true);
+  prefs->RegisterListPref(prefs::kVideoCaptureAllowedUrls);
+  prefs->RegisterListPref(prefs::kAudioCaptureAllowedUrls);
+}
+
+void PermissionBubbleMediaAccessHandler::OnMediaStreamRequestResponse(
+    content::WebContents* web_contents,
+    int request_id,
+    content::MediaStreamRequest request,
+    const blink::MediaStreamDevices& devices,
+    blink::mojom::MediaStreamRequestResult result,
+    bool blocked_by_feature_policy,
+    ContentSetting audio_setting,
+    ContentSetting video_setting) {
+  // If the kill switch is, or the request was blocked because of feature
+  // policy we don't update the tab context.
+  if (result != blink::mojom::MediaStreamRequestResult::KILL_SWITCH_ON &&
+      !blocked_by_feature_policy) {
+    UpdateTabSpecificContentSettings(web_contents, request, audio_setting,
+                                     video_setting);
+  }
+
+  std::unique_ptr<content::MediaStreamUI> ui;
+  if (!devices.empty()) {
+    ui = MediaCaptureDevicesDispatcher::GetInstance()
+             ->GetMediaStreamCaptureIndicator()
+             ->RegisterMediaStream(web_contents, devices);
+  }
+  OnAccessRequestResponse(web_contents, request_id, devices, result,
+                          std::move(ui));
+}
+
 void PermissionBubbleMediaAccessHandler::OnAccessRequestResponse(
     content::WebContents* web_contents,
     int request_id,
diff --git a/chrome/browser/media/webrtc/permission_bubble_media_access_handler.h b/chrome/browser/media/webrtc/permission_bubble_media_access_handler.h
index 53da4bb7..dabea7e 100644
--- a/chrome/browser/media/webrtc/permission_bubble_media_access_handler.h
+++ b/chrome/browser/media/webrtc/permission_bubble_media_access_handler.h
@@ -9,10 +9,15 @@
 
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/media/media_access_handler.h"
+#include "components/content_settings/core/common/content_settings.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
 
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
 // MediaAccessHandler for permission bubble requests.
 class PermissionBubbleMediaAccessHandler
     : public MediaAccessHandler,
@@ -40,12 +45,24 @@
                                blink::mojom::MediaStreamType stream_type,
                                content::MediaRequestState state) override;
 
+  // Registers the prefs backing the audio and video policies.
+  static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
  private:
   struct PendingAccessRequest;
   using RequestsMap = std::map<int, PendingAccessRequest>;
   using RequestsMaps = std::map<content::WebContents*, RequestsMap>;
 
   void ProcessQueuedAccessRequest(content::WebContents* web_contents);
+  void OnMediaStreamRequestResponse(
+      content::WebContents* web_contents,
+      int request_id,
+      content::MediaStreamRequest request,
+      const blink::MediaStreamDevices& devices,
+      blink::mojom::MediaStreamRequestResult result,
+      bool blocked_by_feature_policy,
+      ContentSetting audio_setting,
+      ContentSetting video_setting);
   void OnAccessRequestResponse(content::WebContents* web_contents,
                                int request_id,
                                const blink::MediaStreamDevices& devices,
diff --git a/chrome/browser/payments/BUILD.gn b/chrome/browser/payments/BUILD.gn
index 6b282ac..73f7be0b 100644
--- a/chrome/browser/payments/BUILD.gn
+++ b/chrome/browser/payments/BUILD.gn
@@ -72,9 +72,13 @@
   if (is_android) {
     sources += [
       "android/android_payment_app_finder_unittest.cc",
+      "android/payment_app_service_bridge_unittest.cc",
       "android/payment_manifest_verifier_unittest.cc",
     ]
 
-    deps += [ "//chrome/android:native_j_unittests_jni_headers" ]
+    deps += [
+      "//chrome/android:native_j_unittests_jni_headers",
+      "//components/payments/content:utils",
+    ]
   }
 }
diff --git a/chrome/browser/payments/android/BUILD.gn b/chrome/browser/payments/android/BUILD.gn
index 49e98d5..c37f61f 100644
--- a/chrome/browser/payments/android/BUILD.gn
+++ b/chrome/browser/payments/android/BUILD.gn
@@ -12,6 +12,7 @@
   sources = [
     "../../../android/java/src/org/chromium/chrome/browser/payments/CanMakePaymentQuery.java",
     "../../../android/java/src/org/chromium/chrome/browser/payments/JourneyLogger.java",
+    "../../../android/java/src/org/chromium/chrome/browser/payments/PaymentAppServiceBridge.java",
     "../../../android/java/src/org/chromium/chrome/browser/payments/PaymentManifestWebDataService.java",
     "../../../android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java",
     "../../../android/java/src/org/chromium/chrome/browser/payments/SslValidityChecker.java",
diff --git a/chrome/browser/payments/android/payment_app_service_bridge.cc b/chrome/browser/payments/android/payment_app_service_bridge.cc
new file mode 100644
index 0000000..5d9ee84
--- /dev/null
+++ b/chrome/browser/payments/android/payment_app_service_bridge.cc
@@ -0,0 +1,297 @@
+// Copyright 2020 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/payments/android/payment_app_service_bridge.h"
+
+#include <string>
+#include <vector>
+
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "chrome/browser/payments/android/jni_headers/PaymentAppServiceBridge_jni.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/web_data_service_factory.h"
+#include "components/payments/content/android/byte_buffer_helper.h"
+#include "components/payments/content/payment_app_service.h"
+#include "components/payments/content/payment_app_service_factory.h"
+#include "components/payments/content/payment_manifest_web_data_service.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents.h"
+#include "third_party/blink/public/mojom/payments/payment_app.mojom.h"
+#include "third_party/blink/public/mojom/payments/payment_request.mojom.h"
+#include "url/origin.h"
+
+namespace {
+
+using ::base::android::AttachCurrentThread;
+using ::base::android::ConvertJavaStringToUTF8;
+using ::base::android::ConvertUTF8ToJavaString;
+using ::base::android::JavaParamRef;
+using ::base::android::JavaRef;
+using ::base::android::ScopedJavaGlobalRef;
+using ::payments::android::DeserializeFromJavaByteBufferArray;
+using ::payments::mojom::BasicCardNetwork;
+using ::payments::mojom::PaymentMethodData;
+using ::payments::mojom::PaymentMethodDataPtr;
+
+// Helper to get the PaymentAppService associated with |render_frame_host|'s
+// WebContents.
+payments::PaymentAppService* GetPaymentAppService(
+    content::RenderFrameHost* render_frame_host) {
+  content::WebContents* web_contents =
+      content::WebContents::FromRenderFrameHost(render_frame_host);
+  return payments::PaymentAppServiceFactory::GetForContext(
+      web_contents ? web_contents->GetBrowserContext() : nullptr);
+}
+
+void OnPaymentAppCreated(const JavaRef<jobject>& jcallback) {
+  JNIEnv* env = AttachCurrentThread();
+  Java_PaymentAppServiceCallback_onPaymentAppCreated(env, jcallback);
+}
+
+void OnPaymentAppCreationError(const JavaRef<jobject>& jcallback,
+                               const std::string& error_message) {
+  JNIEnv* env = AttachCurrentThread();
+  Java_PaymentAppServiceCallback_onPaymentAppCreationError(
+      env, jcallback, ConvertUTF8ToJavaString(env, error_message));
+}
+
+void OnDoneCreatingPaymentApps(const JavaRef<jobject>& jcallback) {
+  JNIEnv* env = AttachCurrentThread();
+  Java_PaymentAppServiceCallback_onDoneCreatingPaymentApps(env, jcallback);
+}
+
+}  // namespace
+
+/* static */
+void JNI_PaymentAppServiceBridge_Create(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& jrender_frame_host,
+    const JavaParamRef<jstring>& jtop_origin,
+    const JavaParamRef<jobjectArray>& jmethod_data,
+    jboolean jmay_crawl_for_installable_payment_apps,
+    const JavaParamRef<jobject>& jcallback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  auto* render_frame_host =
+      content::RenderFrameHost::FromJavaRenderFrameHost(jrender_frame_host);
+  std::string top_origin = ConvertJavaStringToUTF8(jtop_origin);
+
+  std::vector<PaymentMethodDataPtr> method_data;
+  DCHECK(DeserializeFromJavaByteBufferArray(env, jmethod_data, &method_data));
+
+  scoped_refptr<payments::PaymentManifestWebDataService> web_data_service =
+      WebDataServiceFactory::GetPaymentManifestWebDataForProfile(
+          Profile::FromBrowserContext(
+              content::WebContents::FromRenderFrameHost(render_frame_host)
+                  ->GetBrowserContext()),
+          ServiceAccessType::EXPLICIT_ACCESS);
+
+  payments::PaymentAppService* service =
+      GetPaymentAppService(render_frame_host);
+  auto* bridge = payments::PaymentAppServiceBridge::Create(
+      service->GetNumberOfFactories(), render_frame_host, GURL(top_origin),
+      std::move(method_data), web_data_service,
+      jmay_crawl_for_installable_payment_apps,
+      base::BindRepeating(&OnPaymentAppCreated,
+                          ScopedJavaGlobalRef<jobject>(env, jcallback)),
+      base::BindRepeating(&OnPaymentAppCreationError,
+                          ScopedJavaGlobalRef<jobject>(env, jcallback)),
+      base::BindOnce(&OnDoneCreatingPaymentApps,
+                     ScopedJavaGlobalRef<jobject>(env, jcallback)));
+
+  service->Create(bridge->GetWeakPtr());
+}
+
+namespace payments {
+namespace {
+
+// A singleton class to maintain  ownership of PaymentAppServiceBridge objects
+// until Remove() is called.
+class PaymentAppServiceBridgeStorage {
+ public:
+  static PaymentAppServiceBridgeStorage* GetInstance() {
+    return base::Singleton<PaymentAppServiceBridgeStorage>::get();
+  }
+
+  PaymentAppServiceBridge* Add(std::unique_ptr<PaymentAppServiceBridge> owned) {
+    DCHECK(owned);
+    PaymentAppServiceBridge* key = owned.get();
+    owner_[key] = std::move(owned);
+    return key;
+  }
+
+  void Remove(PaymentAppServiceBridge* owned) {
+    size_t number_of_deleted_objects = owner_.erase(owned);
+    DCHECK_EQ(1U, number_of_deleted_objects);
+  }
+
+ private:
+  friend struct base::DefaultSingletonTraits<PaymentAppServiceBridgeStorage>;
+  PaymentAppServiceBridgeStorage() = default;
+  ~PaymentAppServiceBridgeStorage() = default;
+
+  std::map<PaymentAppServiceBridge*, std::unique_ptr<PaymentAppServiceBridge>>
+      owner_;
+};
+
+}  // namespace
+
+/* static */
+PaymentAppServiceBridge* PaymentAppServiceBridge::Create(
+    size_t number_of_factories,
+    content::RenderFrameHost* render_frame_host,
+    const GURL& top_origin,
+    std::vector<mojom::PaymentMethodDataPtr> request_method_data,
+    scoped_refptr<PaymentManifestWebDataService> web_data_service,
+    bool may_crawl_for_installable_payment_apps,
+    PaymentAppCreatedCallback payment_app_created_callback,
+    PaymentAppCreationErrorCallback payment_app_creation_error_callback,
+    base::OnceClosure done_creating_payment_apps_callback) {
+  std::unique_ptr<PaymentAppServiceBridge> bridge(new PaymentAppServiceBridge(
+      number_of_factories, render_frame_host, top_origin,
+      std::move(request_method_data), std::move(web_data_service),
+      may_crawl_for_installable_payment_apps,
+      std::move(payment_app_created_callback),
+      std::move(payment_app_creation_error_callback),
+      std::move(done_creating_payment_apps_callback)));
+  return PaymentAppServiceBridgeStorage::GetInstance()->Add(std::move(bridge));
+}
+
+PaymentAppServiceBridge::PaymentAppServiceBridge(
+    size_t number_of_factories,
+    content::RenderFrameHost* render_frame_host,
+    const GURL& top_origin,
+    std::vector<mojom::PaymentMethodDataPtr> request_method_data,
+    scoped_refptr<PaymentManifestWebDataService> web_data_service,
+    bool may_crawl_for_installable_payment_apps,
+    PaymentAppCreatedCallback payment_app_created_callback,
+    PaymentAppCreationErrorCallback payment_app_creation_error_callback,
+    base::OnceClosure done_creating_payment_apps_callback)
+    : number_of_pending_factories_(number_of_factories),
+      web_contents_(
+          content::WebContents::FromRenderFrameHost(render_frame_host)),
+      render_frame_host_(render_frame_host),
+      top_origin_(top_origin),
+      frame_origin_(render_frame_host->GetLastCommittedURL()),
+      frame_security_origin_(render_frame_host->GetLastCommittedOrigin()),
+      request_method_data_(std::move(request_method_data)),
+      payment_manifest_web_data_service_(web_data_service),
+      may_crawl_for_installable_payment_apps_(
+          may_crawl_for_installable_payment_apps),
+      payment_app_created_callback_(std::move(payment_app_created_callback)),
+      payment_app_creation_error_callback_(
+          std::move(payment_app_creation_error_callback)),
+      done_creating_payment_apps_callback_(
+          std::move(done_creating_payment_apps_callback)) {}
+
+PaymentAppServiceBridge::~PaymentAppServiceBridge() = default;
+
+base::WeakPtr<PaymentAppServiceBridge> PaymentAppServiceBridge::GetWeakPtr() {
+  return weak_ptr_factory_.GetWeakPtr();
+}
+
+content::WebContents* PaymentAppServiceBridge::GetWebContents() {
+  return web_contents_;
+}
+const GURL& PaymentAppServiceBridge::GetTopOrigin() {
+  return top_origin_;
+}
+
+const GURL& PaymentAppServiceBridge::GetFrameOrigin() {
+  return frame_origin_;
+}
+
+const url::Origin& PaymentAppServiceBridge::GetFrameSecurityOrigin() {
+  return frame_security_origin_;
+}
+
+content::RenderFrameHost* PaymentAppServiceBridge::GetInitiatorRenderFrameHost()
+    const {
+  return render_frame_host_;
+}
+
+const std::vector<mojom::PaymentMethodDataPtr>&
+PaymentAppServiceBridge::GetMethodData() const {
+  return request_method_data_;
+}
+
+scoped_refptr<PaymentManifestWebDataService>
+PaymentAppServiceBridge::GetPaymentManifestWebDataService() const {
+  return payment_manifest_web_data_service_;
+}
+
+bool PaymentAppServiceBridge::MayCrawlForInstallablePaymentApps() {
+  return may_crawl_for_installable_payment_apps_;
+}
+
+const std::vector<autofill::AutofillProfile*>&
+PaymentAppServiceBridge::GetBillingProfiles() {
+  NOTREACHED();
+  return dummy_profiles_;
+}
+
+bool PaymentAppServiceBridge::IsRequestedAutofillDataAvailable() {
+  // PaymentAppService flow should have short-circuited before this point.
+  NOTREACHED();
+  return false;
+}
+
+ContentPaymentRequestDelegate*
+PaymentAppServiceBridge::GetPaymentRequestDelegate() const {
+  NOTREACHED();
+  return nullptr;
+}
+
+PaymentRequestSpec* PaymentAppServiceBridge::GetSpec() const {
+  NOTREACHED();
+  return nullptr;
+}
+
+void PaymentAppServiceBridge::OnPaymentAppInstalled(const url::Origin& origin,
+                                                    int64_t registration_id) {
+  // PaymentAppService flow should have short-circuited before this point.
+  NOTREACHED();
+}
+
+void PaymentAppServiceBridge::OnPaymentAppCreated(
+    std::unique_ptr<PaymentApp> app) {
+  // PaymentAppService flow should have short-circuited before this point.
+  NOTREACHED();
+}
+
+bool PaymentAppServiceBridge::SkipCreatingNativePaymentApps() const {
+  return true;
+}
+
+void PaymentAppServiceBridge::OnCreatingNativePaymentAppsSkipped(
+    const content::PaymentAppProvider::PaymentApps& apps,
+    const ServiceWorkerPaymentAppFinder::InstallablePaymentApps&
+        installable_apps) {
+  // TODO(crbug.com/1063118): call back to Java with apps information.
+  payment_app_created_callback_.Run();
+}
+
+void PaymentAppServiceBridge::OnPaymentAppCreationError(
+    const std::string& error_message) {
+  payment_app_creation_error_callback_.Run(error_message);
+}
+
+void PaymentAppServiceBridge::OnDoneCreatingPaymentApps() {
+  if (number_of_pending_factories_ > 1U) {
+    number_of_pending_factories_--;
+    return;
+  }
+
+  DCHECK_EQ(1U, number_of_pending_factories_);
+  std::move(done_creating_payment_apps_callback_).Run();
+  PaymentAppServiceBridgeStorage::GetInstance()->Remove(this);
+}
+
+}  // namespace payments
diff --git a/chrome/browser/payments/android/payment_app_service_bridge.h b/chrome/browser/payments/android/payment_app_service_bridge.h
new file mode 100644
index 0000000..ccd3cd33
--- /dev/null
+++ b/chrome/browser/payments/android/payment_app_service_bridge.h
@@ -0,0 +1,123 @@
+// Copyright 2020 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_PAYMENTS_ANDROID_PAYMENT_APP_SERVICE_BRIDGE_H_
+#define CHROME_BROWSER_PAYMENTS_ANDROID_PAYMENT_APP_SERVICE_BRIDGE_H_
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/memory/weak_ptr.h"
+#include "components/payments/content/payment_app_factory.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+class GURL;
+
+namespace autofill {
+class AutofillProfile;
+}  // namespace autofill
+
+namespace content {
+class RenderFrameHost;
+class WebContents;
+}  // namespace content
+
+namespace payments {
+
+// A bridge that holds parameters needed by PaymentAppService and redirects
+// callbacks from PaymentAppFactory to callbacks set by the caller.
+class PaymentAppServiceBridge : public PaymentAppFactory::Delegate {
+ public:
+  // TODO(crbug.com/1063118): add more parameter to this callback to actually
+  // pass payment app data back to Java side.
+  using PaymentAppCreatedCallback = base::RepeatingCallback<void()>;
+  using PaymentAppCreationErrorCallback =
+      base::RepeatingCallback<void(const std::string&)>;
+
+  // Creates a new PaymentAppServiceBridge. This object is self-deleting; its
+  // memory is freed when OnDoneCreatingPaymentApps() is called
+  // |number_of_factories| times.
+  static PaymentAppServiceBridge* Create(
+      size_t number_of_factories,
+      content::RenderFrameHost* render_frame_host,
+      const GURL& top_origin,
+      std::vector<mojom::PaymentMethodDataPtr> request_method_data,
+      scoped_refptr<PaymentManifestWebDataService> web_data_service,
+      bool may_crawl_for_installable_payment_apps,
+      PaymentAppCreatedCallback payment_app_created_callback,
+      PaymentAppCreationErrorCallback payment_app_creation_error_callback,
+      base::OnceClosure done_creating_payment_apps_callback);
+
+  ~PaymentAppServiceBridge() override;
+
+  // Not copiable or movable.
+  PaymentAppServiceBridge(const PaymentAppServiceBridge&) = delete;
+  PaymentAppServiceBridge& operator=(const PaymentAppServiceBridge&) = delete;
+
+  base::WeakPtr<PaymentAppServiceBridge> GetWeakPtr();
+
+  // PaymentAppFactory::Delegate
+  content::WebContents* GetWebContents() override;
+  const GURL& GetTopOrigin() override;
+  const GURL& GetFrameOrigin() override;
+  const url::Origin& GetFrameSecurityOrigin() override;
+  content::RenderFrameHost* GetInitiatorRenderFrameHost() const override;
+  const std::vector<mojom::PaymentMethodDataPtr>& GetMethodData()
+      const override;
+  scoped_refptr<PaymentManifestWebDataService>
+  GetPaymentManifestWebDataService() const override;
+  bool MayCrawlForInstallablePaymentApps() override;
+  const std::vector<autofill::AutofillProfile*>& GetBillingProfiles() override;
+  bool IsRequestedAutofillDataAvailable() override;
+  ContentPaymentRequestDelegate* GetPaymentRequestDelegate() const override;
+  PaymentRequestSpec* GetSpec() const override;
+  void OnPaymentAppInstalled(const url::Origin& origin,
+                             int64_t registration_id) override;
+  void OnPaymentAppCreated(std::unique_ptr<PaymentApp> app) override;
+  void OnPaymentAppCreationError(const std::string& error_message) override;
+  bool SkipCreatingNativePaymentApps() const override;
+  void OnCreatingNativePaymentAppsSkipped(
+      const content::PaymentAppProvider::PaymentApps& apps,
+      const ServiceWorkerPaymentAppFinder::InstallablePaymentApps&
+          installable_apps) override;
+  void OnDoneCreatingPaymentApps() override;
+
+ private:
+  // Prevents direct instantiation. Callers should use Create() instead.
+  PaymentAppServiceBridge(
+      size_t number_of_factories,
+      content::RenderFrameHost* render_frame_host,
+      const GURL& top_origin,
+      std::vector<mojom::PaymentMethodDataPtr> request_method_data,
+      scoped_refptr<PaymentManifestWebDataService> web_data_service,
+      bool may_crawl_for_installable_payment_apps,
+      PaymentAppCreatedCallback payment_app_created_callback,
+      PaymentAppCreationErrorCallback payment_app_creation_error_callback,
+      base::OnceClosure done_creating_payment_apps_callback);
+
+  size_t number_of_pending_factories_;
+  content::WebContents* web_contents_;
+  content::RenderFrameHost* render_frame_host_;
+  const GURL top_origin_;
+  const GURL frame_origin_;
+  const url::Origin frame_security_origin_;
+  std::vector<mojom::PaymentMethodDataPtr> request_method_data_;
+  scoped_refptr<PaymentManifestWebDataService>
+      payment_manifest_web_data_service_;
+  bool may_crawl_for_installable_payment_apps_;
+  std::vector<autofill::AutofillProfile*> dummy_profiles_;
+
+  PaymentAppCreatedCallback payment_app_created_callback_;
+  PaymentAppCreationErrorCallback payment_app_creation_error_callback_;
+  base::OnceClosure done_creating_payment_apps_callback_;
+
+  base::WeakPtrFactory<PaymentAppServiceBridge> weak_ptr_factory_{this};
+};
+
+}  // namespace payments
+
+#endif  // CHROME_BROWSER_PAYMENTS_ANDROID_PAYMENT_APP_SERVICE_BRIDGE_H_
diff --git a/chrome/browser/payments/android/payment_app_service_bridge_unittest.cc b/chrome/browser/payments/android/payment_app_service_bridge_unittest.cc
new file mode 100644
index 0000000..2d731b0
--- /dev/null
+++ b/chrome/browser/payments/android/payment_app_service_bridge_unittest.cc
@@ -0,0 +1,114 @@
+// Copyright 2020 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/payments/android/payment_app_service_bridge.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/payments/content/payment_manifest_web_data_service.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/navigation_simulator.h"
+#include "content/public/test/test_web_contents_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/mojom/payments/payment_app.mojom.h"
+
+namespace payments {
+
+class MockCallback {
+ public:
+  MockCallback() = default;
+  MOCK_METHOD0(NotifyPaymentAppCreated, void(void));
+  MOCK_METHOD1(NotifyPaymentAppCreationError, void(const std::string& error));
+  MOCK_METHOD0(NotifyDoneCreatingPaymentApps, void(void));
+};
+
+class PaymentAppServiceBridgeUnitTest : public ::testing::Test {
+ public:
+  PaymentAppServiceBridgeUnitTest()
+      : top_origin_("https://shop.example"),
+        frame_origin_("https://merchant.example") {
+    web_contents_ =
+        test_web_contents_factory_.CreateWebContents(&browser_context_);
+    content::NavigationSimulator::NavigateAndCommitFromBrowser(web_contents_,
+                                                               frame_origin_);
+  }
+
+  mojom::PaymentMethodDataPtr MakePaymentMethodData(
+      const std::string& supported_method) {
+    mojom::PaymentMethodDataPtr out = mojom::PaymentMethodData::New();
+    out->supported_method = supported_method;
+    return out;
+  }
+
+ protected:
+  content::BrowserTaskEnvironment task_environment_;
+  TestingProfile browser_context_;
+  content::TestWebContentsFactory test_web_contents_factory_;
+  content::WebContents* web_contents_;
+  GURL top_origin_;
+  GURL frame_origin_;
+  scoped_refptr<PaymentManifestWebDataService> web_data_service_;
+};
+
+TEST_F(PaymentAppServiceBridgeUnitTest, Smoke) {
+  std::vector<mojom::PaymentMethodDataPtr> method_data;
+  method_data.push_back(MakePaymentMethodData("basic-card"));
+  method_data.push_back(MakePaymentMethodData("https://ph.example"));
+
+  MockCallback mock_callback;
+  base::WeakPtr<PaymentAppServiceBridge> bridge =
+      PaymentAppServiceBridge::Create(
+          /* number_of_factories= */ 3, web_contents_->GetMainFrame(),
+          top_origin_, std::move(method_data), web_data_service_,
+          /* may_crawl_for_installable_payment_apps= */ true,
+          base::BindRepeating(&MockCallback::NotifyPaymentAppCreated,
+                              base::Unretained(&mock_callback)),
+          base::BindRepeating(&MockCallback::NotifyPaymentAppCreationError,
+                              base::Unretained(&mock_callback)),
+          base::BindOnce(&MockCallback::NotifyDoneCreatingPaymentApps,
+                         base::Unretained(&mock_callback)))
+          ->GetWeakPtr();
+
+  EXPECT_TRUE(bridge->SkipCreatingNativePaymentApps());
+  EXPECT_EQ(web_contents_, bridge->GetWebContents());
+  EXPECT_EQ(top_origin_, bridge->GetTopOrigin());
+  EXPECT_EQ(frame_origin_, bridge->GetFrameOrigin());
+  EXPECT_EQ("https://merchant.example",
+            bridge->GetFrameSecurityOrigin().Serialize());
+  EXPECT_EQ(web_contents_->GetMainFrame(),
+            bridge->GetInitiatorRenderFrameHost());
+  EXPECT_EQ(2U, bridge->GetMethodData().size());
+  EXPECT_EQ("basic-card", bridge->GetMethodData()[0]->supported_method);
+  EXPECT_EQ("https://ph.example", bridge->GetMethodData()[1]->supported_method);
+  EXPECT_TRUE(bridge->MayCrawlForInstallablePaymentApps());
+
+  content::PaymentAppProvider::PaymentApps apps;
+  ServiceWorkerPaymentAppFinder::InstallablePaymentApps installables;
+
+  EXPECT_CALL(mock_callback, NotifyPaymentAppCreated());
+  bridge->OnCreatingNativePaymentAppsSkipped(apps, installables);
+
+  EXPECT_CALL(mock_callback, NotifyPaymentAppCreationError("some error"));
+  bridge->OnPaymentAppCreationError("some error");
+
+  // NotifyDoneCreatingPaymentApps() is only called after
+  // OnDoneCreatingPaymentApps() is called for each payment factories in
+  // |bridge|.
+  bridge->OnDoneCreatingPaymentApps();
+  bridge->OnDoneCreatingPaymentApps();
+
+  EXPECT_CALL(mock_callback, NotifyDoneCreatingPaymentApps());
+  bridge->OnDoneCreatingPaymentApps();
+
+  // |bridge| cleans itself up after NotifyDoneCreatingPaymentApps().
+  CHECK_EQ(nullptr, bridge.get());
+}
+
+}  // namespace payments
diff --git a/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy.cc b/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy.cc
index 7f68ad5..63e15a0 100644
--- a/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy.cc
+++ b/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy.cc
@@ -64,7 +64,7 @@
     : page_loader_(std::make_unique<mechanism::PageLoader>()) {
   DCHECK(!g_background_tab_loading_policy);
   g_background_tab_loading_policy = this;
-  simultaneous_tab_loads_ = CalculateMaxSimultaneousTabLoads(
+  max_simultaneous_tab_loads_ = CalculateMaxSimultaneousTabLoads(
       kMinSimultaneousTabLoads, kMaxSimultaneousTabLoads,
       kCoresPerSimultaneousTabLoad, base::SysInfo::NumberOfProcessors());
 }
@@ -86,6 +86,9 @@
   if (!page_node->IsLoading()) {
     // Once the PageNode finishes loading, stop tracking it within this policy.
     RemovePageNode(page_node);
+
+    // Since there is a free loading slot, load more tab if needed.
+    MaybeLoadSomeTabs();
     return;
   }
   // The PageNode started loading, either because of this policy or because of
@@ -105,6 +108,10 @@
 void BackgroundTabLoadingPolicy::OnBeforePageNodeRemoved(
     const PageNode* page_node) {
   RemovePageNode(page_node);
+
+  // There may be free loading slots, check and load more tabs if that's the
+  // case.
+  MaybeLoadSomeTabs();
 }
 
 void BackgroundTabLoadingPolicy::ScheduleLoadForRestoredTabs(
@@ -115,8 +122,8 @@
     page_nodes_to_load_.push_back(page_node);
     DCHECK(
         TabPropertiesDecorator::Data::FromPageNode(page_node)->IsInTabStrip());
-    InitiateLoad(page_node);
   }
+  MaybeLoadSomeTabs();
 }
 
 void BackgroundTabLoadingPolicy::SetMockLoaderForTesting(
@@ -124,11 +131,16 @@
   page_loader_ = std::move(loader);
 }
 
+void BackgroundTabLoadingPolicy::SetMaxSimultaneousLoadsForTesting(
+    size_t loading_slots) {
+  max_simultaneous_tab_loads_ = loading_slots;
+}
+
 BackgroundTabLoadingPolicy* BackgroundTabLoadingPolicy::GetInstance() {
   return g_background_tab_loading_policy;
 }
 
-void BackgroundTabLoadingPolicy::InitiateLoad(PageNode* page_node) {
+void BackgroundTabLoadingPolicy::InitiateLoad(const PageNode* page_node) {
   // Mark |page_node| as load initiated. Ensure that InitiateLoad is only called
   // for a PageNode that is tracked by the policy.
   size_t num_removed = base::Erase(page_nodes_to_load_, page_node);
@@ -145,6 +157,44 @@
   base::Erase(page_nodes_loading_, page_node);
 }
 
+void BackgroundTabLoadingPolicy::MaybeLoadSomeTabs() {
+  // Continue to load tabs while possible. This is in a loop with a
+  // recalculation of GetMaxNewTabLoads() as reentrancy can cause conditions
+  // to change as each tab load is initiated.
+  while (GetMaxNewTabLoads() > 0)
+    LoadNextTab();
+}
+
+size_t BackgroundTabLoadingPolicy::GetMaxNewTabLoads() const {
+  // This takes into account all tabs currently loading across the browser,
+  // including ones that BackgroundTabLoadingPolicy isn't explicitly managing.
+  // This ensures that BackgroundTabLoadingPolicy respects user interaction
+  // first and foremost. There's a small race between when we initiated loading
+  // and when PageNodeObserver notifies us that it has actually started, so we
+  // also make use of |page_nodes_initiated_| to track these.
+  size_t loading_tab_count =
+      page_nodes_load_initiated_.size() + page_nodes_loading_.size();
+
+  // Determine the number of free loading slots available.
+  size_t page_nodes_to_load = 0;
+  if (loading_tab_count < max_simultaneous_tab_loads_)
+    page_nodes_to_load = max_simultaneous_tab_loads_ - loading_tab_count;
+
+  // Cap the number of loads by the actual number of tabs remaining.
+  page_nodes_to_load = std::min(page_nodes_to_load, page_nodes_to_load_.size());
+
+  return page_nodes_to_load;
+}
+
+void BackgroundTabLoadingPolicy::LoadNextTab() {
+  DCHECK(!page_nodes_to_load_.empty());
+
+  // Find the next PageNode to load.
+  const PageNode* page_node = page_nodes_to_load_.front();
+
+  InitiateLoad(page_node);
+}
+
 }  // namespace policies
 
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy.h b/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy.h
index e9ea718..9386a79 100644
--- a/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy.h
+++ b/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy.h
@@ -42,6 +42,7 @@
   void ScheduleLoadForRestoredTabs(std::vector<PageNode*> page_nodes);
 
   void SetMockLoaderForTesting(std::unique_ptr<mechanism::PageLoader> loader);
+  void SetMaxSimultaneousLoadsForTesting(size_t loading_slots);
 
   // Returns the instance of BackgroundTabLoadingPolicy within the graph.
   static BackgroundTabLoadingPolicy* GetInstance();
@@ -49,12 +50,25 @@
  private:
   // Move the PageNode from |page_nodes_to_load_| to
   // |page_nodes_load_initiated_| and make the call to load the PageNode.
-  void InitiateLoad(PageNode* page_node);
+  void InitiateLoad(const PageNode* page_node);
 
   // Removes the PageNode from all the sets of PageNodes that the policy is
   // tracking.
   void RemovePageNode(const PageNode* page_node);
 
+  // Initiates the load of enough tabs to fill all loading slots. No-ops if all
+  // loading slots are occupied.
+  void MaybeLoadSomeTabs();
+
+  // Determines the number of tab loads that can be started at the moment to
+  // avoid exceeding the number of loading slots.
+  size_t GetMaxNewTabLoads() const;
+
+  // Loads the next tab. This should only be called if there is a next tab to
+  // load. This will always start loading a next tab even if the number of
+  // simultaneously loading tabs is exceeded.
+  void LoadNextTab();
+
   // The mechanism used to load the pages.
   std::unique_ptr<performance_manager::mechanism::PageLoader> page_loader_;
 
@@ -72,7 +86,7 @@
 
   // The number of simultaneous tab loads that are permitted by policy. This
   // is computed based on the number of cores on the machine.
-  size_t simultaneous_tab_loads_;
+  size_t max_simultaneous_tab_loads_;
 };
 
 }  // namespace policies
diff --git a/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy_unittest.cc b/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy_unittest.cc
index 66b0c89..b7dbe15 100644
--- a/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy_unittest.cc
+++ b/chrome/browser/performance_manager/graph/policies/background_tab_loading_policy_unittest.cc
@@ -49,6 +49,10 @@
     auto mock_loader = std::make_unique<MockPageLoader>();
     mock_loader_ = mock_loader.get();
     policy_->SetMockLoaderForTesting(std::move(mock_loader));
+
+    // Set a value explicitly for MaxSimultaneousLoad threshold to avoid a
+    // dependency on the number of cores of the machine on which the test runs.
+    policy_->SetMaxSimultaneousLoadsForTesting(4);
   }
 
   void TearDown() override { graph()->TakeFromGraph(policy_); }
@@ -82,6 +86,45 @@
   policy()->ScheduleLoadForRestoredTabs(raw_page_nodes);
 }
 
+TEST_F(BackgroundTabLoadingPolicyTest, AllLoadingSlotsUsed) {
+  // Create 4 PageNode to restore.
+  std::vector<
+      performance_manager::TestNodeWrapper<performance_manager::PageNodeImpl>>
+      page_nodes;
+  std::vector<PageNode*> raw_page_nodes;
+
+  // Create vector of PageNode to restore.
+  for (int i = 0; i < 4; i++) {
+    page_nodes.push_back(CreateNode<performance_manager::PageNodeImpl>());
+    raw_page_nodes.push_back(page_nodes.back().get());
+
+    // Set |is_tab| property as this is a requirement to pass the PageNode to
+    // ScheduleLoadForRestoredTabs().
+    TabPropertiesDecorator::SetIsTabForTesting(raw_page_nodes.back(), true);
+  }
+  PageNodeImpl* page_node_impl = page_nodes[0].get();
+
+  EXPECT_CALL(*loader(), LoadPageNode(raw_page_nodes[0]));
+  EXPECT_CALL(*loader(), LoadPageNode(raw_page_nodes[1]));
+
+  // Use 2 loading slots, which means only 2 of the PageNodes should immediately
+  // be scheduled to load.
+  policy()->SetMaxSimultaneousLoadsForTesting(2);
+
+  policy()->ScheduleLoadForRestoredTabs(raw_page_nodes);
+  testing::Mock::VerifyAndClear(loader());
+
+  // Simulate load start of a PageNode that initiated load.
+  page_node_impl->SetIsLoading(true);
+
+  // The policy should allow one more PageNode to load after a PageNode finishes
+  // loading.
+  EXPECT_CALL(*loader(), LoadPageNode(raw_page_nodes[2]));
+
+  // Simulate load finish of a PageNode.
+  page_node_impl->SetIsLoading(false);
+}
+
 }  // namespace policies
 
 }  // namespace performance_manager
diff --git a/chrome/browser/permissions/chrome_permissions_client.cc b/chrome/browser/permissions/chrome_permissions_client.cc
index 3e7df14..a513ff71 100644
--- a/chrome/browser/permissions/chrome_permissions_client.cc
+++ b/chrome/browser/permissions/chrome_permissions_client.cc
@@ -32,6 +32,7 @@
 #include "chrome/browser/android/search_permissions/search_permissions_service.h"
 #include "chrome/browser/infobars/infobar_service.h"
 #include "chrome/browser/permissions/grouped_permission_infobar_delegate_android.h"
+#include "chrome/browser/permissions/permission_update_infobar_delegate_android.h"
 #else
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/ui/permission_bubble/permission_prompt.h"
@@ -267,6 +268,14 @@
   return nullptr;
 }
 
+void ChromePermissionsClient::RepromptForAndroidPermissions(
+    content::WebContents* web_contents,
+    const std::vector<ContentSettingsType>& content_settings_types,
+    PermissionsUpdatedCallback callback) {
+  PermissionUpdateInfoBarDelegate::Create(web_contents, content_settings_types,
+                                          std::move(callback));
+}
+
 base::android::ScopedJavaLocalRef<jobject>
 ChromePermissionsClient::GetJavaObject() {
   return Java_ChromePermissionsClient_get(base::android::AttachCurrentThread());
diff --git a/chrome/browser/permissions/chrome_permissions_client.h b/chrome/browser/permissions/chrome_permissions_client.h
index 08fff06..763ea6a 100644
--- a/chrome/browser/permissions/chrome_permissions_client.h
+++ b/chrome/browser/permissions/chrome_permissions_client.h
@@ -60,6 +60,10 @@
       content::WebContents* web_contents,
       ContentSettingsType type,
       base::WeakPtr<permissions::PermissionPromptAndroid> prompt) override;
+  void RepromptForAndroidPermissions(
+      content::WebContents* web_contents,
+      const std::vector<ContentSettingsType>& content_settings_types,
+      PermissionsUpdatedCallback callback) override;
   base::android::ScopedJavaLocalRef<jobject> GetJavaObject() override;
   int MapToJavaDrawableId(int resource_id) override;
 #else
diff --git a/chrome/browser/permissions/permission_request_manager_browsertest.cc b/chrome/browser/permissions/permission_request_manager_browsertest.cc
index 65b26c8..39a0c25a 100644
--- a/chrome/browser/permissions/permission_request_manager_browsertest.cc
+++ b/chrome/browser/permissions/permission_request_manager_browsertest.cc
@@ -15,7 +15,6 @@
 #include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h"
 #include "chrome/browser/custom_handlers/register_protocol_handler_permission_request.h"
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
-#include "chrome/browser/media/webrtc/media_stream_devices_controller.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
diff --git a/chrome/browser/permissions/permission_update_infobar_delegate_android.cc b/chrome/browser/permissions/permission_update_infobar_delegate_android.cc
index 8aebdb4..3400891 100644
--- a/chrome/browser/permissions/permission_update_infobar_delegate_android.cc
+++ b/chrome/browser/permissions/permission_update_infobar_delegate_android.cc
@@ -15,8 +15,8 @@
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/infobars/core/infobar.h"
+#include "components/permissions/android/android_permission_util.h"
 #include "components/permissions/permission_uma_util.h"
-#include "components/permissions/permission_util.h"
 #include "components/strings/grit/components_strings.h"
 #include "content/public/browser/web_contents.h"
 #include "ui/android/window_android.h"
@@ -29,8 +29,9 @@
     content::WebContents* web_contents,
     const std::vector<ContentSettingsType>& content_settings_types,
     PermissionUpdatedCallback callback) {
-  DCHECK(ShouldShowPermissionInfoBar(web_contents, content_settings_types) ==
-         ShowPermissionInfoBarState::SHOW_PERMISSION_INFOBAR)
+  DCHECK_EQ(permissions::ShouldRepromptUserForPermissions(
+                web_contents, content_settings_types),
+            permissions::PermissionRepromptState::kShow)
       << "Caller should check ShouldShowPermissionInfobar before creating the "
       << "infobar.";
 
@@ -41,8 +42,8 @@
 
   for (ContentSettingsType content_settings_type : content_settings_types) {
     int previous_size = permissions.size();
-    permissions::PermissionUtil::GetAndroidPermissionsForContentSetting(
-        content_settings_type, &permissions);
+    permissions::GetAndroidPermissionsForContentSetting(content_settings_type,
+                                                        &permissions);
 
     bool has_all_permissions = true;
     for (auto it = permissions.begin() + previous_size; it != permissions.end();
@@ -95,38 +96,6 @@
       permission_msg_id, std::move(callback));
 }
 
-// static
-ShowPermissionInfoBarState
-PermissionUpdateInfoBarDelegate::ShouldShowPermissionInfoBar(
-    content::WebContents* web_contents,
-    const std::vector<ContentSettingsType>& content_settings_types) {
-  if (!web_contents)
-    return ShowPermissionInfoBarState::CANNOT_SHOW_PERMISSION_INFOBAR;
-
-  auto* window_android = web_contents->GetNativeView()->GetWindowAndroid();
-  if (!window_android)
-    return ShowPermissionInfoBarState::CANNOT_SHOW_PERMISSION_INFOBAR;
-
-  for (ContentSettingsType content_settings_type : content_settings_types) {
-    std::vector<std::string> android_permissions;
-    permissions::PermissionUtil::GetAndroidPermissionsForContentSetting(
-        content_settings_type, &android_permissions);
-
-    for (const auto& android_permission : android_permissions) {
-      if (!window_android->HasPermission(android_permission)) {
-        permissions::PermissionUmaUtil::
-            RecordMissingPermissionInfobarShouldShow(true,
-                                                     content_settings_types);
-        return ShowPermissionInfoBarState::SHOW_PERMISSION_INFOBAR;
-      }
-    }
-  }
-
-  permissions::PermissionUmaUtil::RecordMissingPermissionInfobarShouldShow(
-      false, content_settings_types);
-  return ShowPermissionInfoBarState::NO_NEED_TO_SHOW_PERMISSION_INFOBAR;
-}
-
 void PermissionUpdateInfoBarDelegate::OnPermissionResult(
     JNIEnv* env,
     const JavaParamRef<jobject>& obj,
diff --git a/chrome/browser/permissions/permission_update_infobar_delegate_android.h b/chrome/browser/permissions/permission_update_infobar_delegate_android.h
index 07955c5..5ea3c42 100644
--- a/chrome/browser/permissions/permission_update_infobar_delegate_android.h
+++ b/chrome/browser/permissions/permission_update_infobar_delegate_android.h
@@ -19,18 +19,6 @@
 class WebContents;
 }
 
-// The states that indicate if a permission infobar should/could be shown or
-// not.
-enum class ShowPermissionInfoBarState {
-  // No need to show the infobar as the permissions have been already granted.
-  NO_NEED_TO_SHOW_PERMISSION_INFOBAR = 0,
-  // Show the the permission infobar.
-  SHOW_PERMISSION_INFOBAR,
-  // Can't show the permission infobar due to an internal state issue like
-  // the WebContents or the AndroidWindow are not available.
-  CANNOT_SHOW_PERMISSION_INFOBAR
-};
-
 // An infobar delegate to be used for requesting missing Android runtime
 // permissions for previously allowed ContentSettingsTypes.
 class PermissionUpdateInfoBarDelegate : public ConfirmInfoBarDelegate {
@@ -66,12 +54,6 @@
       int permission_msg_id,
       PermissionUpdatedCallback callback);
 
-  // Returns an indicator of whether a permission infobar should be shown or
-  // not or cannot be shown.
-  static ShowPermissionInfoBarState ShouldShowPermissionInfoBar(
-      content::WebContents* web_contents,
-      const std::vector<ContentSettingsType>& content_settings_types);
-
   void OnPermissionResult(JNIEnv* env,
                           const base::android::JavaParamRef<jobject>& obj,
                           jboolean all_permissions_granted);
diff --git a/chrome/browser/policy/DEPS b/chrome/browser/policy/DEPS
index 8275742..8a91319 100644
--- a/chrome/browser/policy/DEPS
+++ b/chrome/browser/policy/DEPS
@@ -1,4 +1,7 @@
 specific_include_rules = {
+  "media_stream_policy_browsertest\.cc": [
+    "+components/webrtc",
+  ],
   "policy_browsertest\.cc": [
     "+ash/shell.h",
   ]
diff --git a/chrome/browser/policy/media_stream_policy_browsertest.cc b/chrome/browser/policy/media_stream_policy_browsertest.cc
index 53d1d12..99619a7 100644
--- a/chrome/browser/policy/media_stream_policy_browsertest.cc
+++ b/chrome/browser/policy/media_stream_policy_browsertest.cc
@@ -1,9 +1,9 @@
 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
+
 #include "base/task/post_task.h"
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
-#include "chrome/browser/media/webrtc/media_stream_devices_controller.h"
 #include "chrome/browser/policy/policy_test_utils.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/test/base/ui_test_utils.h"
@@ -11,6 +11,7 @@
 #include "components/permissions/test/mock_permission_prompt_factory.h"
 #include "components/policy/core/common/policy_map.h"
 #include "components/policy/policy_constants.h"
+#include "components/webrtc/media_stream_devices_controller.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/render_process_host.h"
@@ -104,7 +105,9 @@
 
   void Accept(const blink::MediaStreamDevices& devices,
               blink::mojom::MediaStreamRequestResult result,
-              std::unique_ptr<content::MediaStreamUI> ui) {
+              bool blocked_by_feature_policy,
+              ContentSetting audio_setting,
+              ContentSetting video_setting) {
     if (policy_value_ || request_url_allowed_via_whitelist_) {
       ASSERT_EQ(1U, devices.size());
       ASSERT_EQ("fake_dev", devices[0].id);
@@ -119,9 +122,10 @@
                       blink::mojom::MediaStreamType::NO_SERVICE));
     // TODO(raymes): Test MEDIA_DEVICE_OPEN (Pepper) which grants both webcam
     // and microphone permissions at the same time.
-    MediaStreamDevicesController::RequestPermissions(
-        request, base::Bind(&MediaStreamDevicesControllerBrowserTest::Accept,
-                            base::Unretained(this)));
+    webrtc::MediaStreamDevicesController::RequestPermissions(
+        request, MediaCaptureDevicesDispatcher::GetInstance(),
+        base::Bind(&MediaStreamDevicesControllerBrowserTest::Accept,
+                   base::Unretained(this)));
     quit_closure_.Run();
   }
 
@@ -131,9 +135,10 @@
                       blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
     // TODO(raymes): Test MEDIA_DEVICE_OPEN (Pepper) which grants both webcam
     // and microphone permissions at the same time.
-    MediaStreamDevicesController::RequestPermissions(
-        request, base::Bind(&MediaStreamDevicesControllerBrowserTest::Accept,
-                            base::Unretained(this)));
+    webrtc::MediaStreamDevicesController::RequestPermissions(
+        request, MediaCaptureDevicesDispatcher::GetInstance(),
+        base::Bind(&MediaStreamDevicesControllerBrowserTest::Accept,
+                   base::Unretained(this)));
     quit_closure_.Run();
   }
 
diff --git a/chrome/browser/portal/portal_browsertest.cc b/chrome/browser/portal/portal_browsertest.cc
index 858aa81..2a85870 100644
--- a/chrome/browser/portal/portal_browsertest.cc
+++ b/chrome/browser/portal/portal_browsertest.cc
@@ -9,6 +9,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/devtools/devtools_window_testing.h"
+#include "chrome/browser/interstitials/security_interstitial_page_test_utils.h"
 #include "chrome/browser/pdf/pdf_extension_test_util.h"
 #include "chrome/browser/task_manager/providers/task.h"
 #include "chrome/browser/task_manager/task_manager_browsertest_util.h"
@@ -21,8 +22,12 @@
 #include "chrome/grit/generated_resources.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/navigation_controller.h"
+#include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/web_contents.h"
+#include "content/public/common/page_type.h"
 #include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_navigation_observer.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -263,3 +268,55 @@
 
   EXPECT_TRUE(pdf_extension_test_util::EnsurePDFHasLoaded(portal_contents));
 }
+
+// Test that we do not show main frame interstitials in portal contents. We
+// should treat portals like subframes in terms of how to display the error to
+// the user.
+IN_PROC_BROWSER_TEST_F(PortalBrowserTest, ShowSubFrameErrorPage) {
+  net::EmbeddedTestServer bad_https_server(net::EmbeddedTestServer::TYPE_HTTPS);
+  bad_https_server.AddDefaultHandlers(GetChromeTestDataDir());
+  bad_https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_EXPIRED);
+  ASSERT_TRUE(bad_https_server.Start());
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  GURL url(embedded_test_server()->GetURL("/title1.html"));
+  ui_test_utils::NavigateToURL(browser(), url);
+  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
+
+  GURL bad_cert_url(bad_https_server.GetURL("/title1.html"));
+  content::TestNavigationObserver portal_navigation_observer(nullptr, 1);
+  portal_navigation_observer.StartWatchingNewWebContents();
+  ASSERT_EQ(true,
+            content::EvalJs(
+                contents, content::JsReplace(
+                              "new Promise((resolve) => {"
+                              "  let portal = document.createElement('portal');"
+                              "  portal.src = $1;"
+                              "  portal.onload = () => { resolve(true); };"
+                              "  document.body.appendChild(portal);"
+                              "});",
+                              bad_cert_url)));
+  portal_navigation_observer.StopWatchingNewWebContents();
+  portal_navigation_observer.Wait();
+  EXPECT_FALSE(portal_navigation_observer.last_navigation_succeeded());
+  EXPECT_EQ(net::ERR_CERT_DATE_INVALID,
+            portal_navigation_observer.last_net_error_code());
+
+  std::vector<WebContents*> inner_web_contents =
+      contents->GetInnerWebContents();
+  ASSERT_EQ(1u, inner_web_contents.size());
+  WebContents* portal_contents = inner_web_contents[0];
+
+  content::NavigationController& controller = portal_contents->GetController();
+  content::NavigationEntry* entry = controller.GetLastCommittedEntry();
+  ASSERT_TRUE(entry);
+  EXPECT_EQ(content::PAGE_TYPE_ERROR, entry->GetPageType());
+
+  // Ensure that this is the net error page and not an interstitial.
+  ASSERT_FALSE(
+      chrome_browser_interstitials::IsShowingInterstitial(portal_contents));
+  // Also ensure that the error page is using its subframe layout.
+  ASSERT_EQ(true, content::EvalJs(
+                      portal_contents,
+                      "document.documentElement.hasAttribute('subframe');"));
+}
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 7d01e4ac..cfe5bce 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -33,7 +33,7 @@
 #include "chrome/browser/media/media_storage_id_salt.h"
 #include "chrome/browser/media/router/media_router_feature.h"
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
-#include "chrome/browser/media/webrtc/media_stream_devices_controller.h"
+#include "chrome/browser/media/webrtc/permission_bubble_media_access_handler.h"
 #include "chrome/browser/media/webrtc/system_media_capture_permissions_stats_mac.h"
 #include "chrome/browser/memory/enterprise_memory_limit_pref_observer.h"
 #include "chrome/browser/metrics/chrome_metrics_service_client.h"
@@ -554,6 +554,11 @@
 const char kInvalidatorInvalidationState[] = "invalidator.invalidation_state";
 const char kInvalidatorSavedInvalidations[] = "invalidator.saved_invalidations";
 
+#if defined(OS_CHROMEOS)
+// Deprecated 4/2020
+const char kAmbientModeTopicSource[] = "settings.ambient_mode.topic_source";
+#endif  // defined(OS_CHROMEOS)
+
 // Register prefs used only for migration (clearing or moving to a new key).
 void RegisterProfilePrefsForMigration(
     user_prefs::PrefRegistrySyncable* registry) {
@@ -652,6 +657,10 @@
   registry->RegisterStringPref(kInvalidatorClientId, std::string());
 
   chrome_browser_net::RegisterDNSProbesSettingBackupPref(registry);
+
+#if defined(OS_CHROMEOS)
+  registry->RegisterIntegerPref(kAmbientModeTopicSource, 0);
+#endif  // defined(OS_CHROMEOS)
 }
 
 }  // namespace
@@ -873,7 +882,6 @@
   MediaDeviceIDSalt::RegisterProfilePrefs(registry);
   MediaEngagementService::RegisterProfilePrefs(registry);
   MediaStorageIdSalt::RegisterProfilePrefs(registry);
-  MediaStreamDevicesController::RegisterProfilePrefs(registry);
   NavigationCorrectionTabObserver::RegisterProfilePrefs(registry);
   NotificationDisplayServiceImpl::RegisterProfilePrefs(registry);
   NotifierStateTracker::RegisterProfilePrefs(registry);
@@ -887,6 +895,7 @@
   password_bubble_experiment::RegisterPrefs(registry);
   password_manager::PasswordManager::RegisterProfilePrefs(registry);
   payments::RegisterProfilePrefs(registry);
+  PermissionBubbleMediaAccessHandler::RegisterProfilePrefs(registry);
   PlatformNotificationServiceImpl::RegisterProfilePrefs(registry);
   policy::DeveloperToolsPolicyHandler::RegisterProfilePrefs(registry);
   policy::URLBlacklistManager::RegisterProfilePrefs(registry);
@@ -1326,4 +1335,9 @@
   // Added 3/2020.
   profile_prefs->ClearPref(kDataReductionNetworkProperties);
   chrome_browser_net::MigrateDNSProbesSettingToOrFromBackup(profile_prefs);
+
+#if defined(OS_CHROMEOS)
+  // Added 4/2020.
+  profile_prefs->ClearPref(kAmbientModeTopicSource);
+#endif
 }
diff --git a/chrome/browser/profiles/off_the_record_profile_impl.cc b/chrome/browser/profiles/off_the_record_profile_impl.cc
index 313a0a7a3..31cf4f0 100644
--- a/chrome/browser/profiles/off_the_record_profile_impl.cc
+++ b/chrome/browser/profiles/off_the_record_profile_impl.cc
@@ -40,7 +40,6 @@
 #include "chrome/browser/prefs/incognito_mode_prefs.h"
 #include "chrome/browser/prefs/pref_service_syncable_util.h"
 #include "chrome/browser/profiles/profile_key.h"
-#include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ssl/stateful_ssl_host_state_delegate_factory.h"
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/transition_manager/full_browser_transition_manager.h"
@@ -146,12 +145,13 @@
 void OffTheRecordProfileImpl::Init() {
   FullBrowserTransitionManager::Get()->OnProfileCreated(this);
 
+  // Must be done before CreateBrowserContextServices(), since some of them
+  // change behavior based on whether the provided context is a guest session.
+  set_is_guest_profile(profile_->IsGuestSession());
+
   BrowserContextDependencyManager::GetInstance()->CreateBrowserContextServices(
       this);
 
-  set_is_guest_profile(
-      profile_->GetPath() == ProfileManager::GetGuestProfilePath());
-
   // Always crash when incognito is not available.
   // Guest profiles may always be OTR. Check IncognitoModePrefs otherwise.
   CHECK(profile_->IsGuestSession() || profile_->IsSystemProfile() ||
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/ambient_mode_page/BUILD.gn
index 10651a61..3e19c60 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/BUILD.gn
@@ -23,6 +23,7 @@
     "//ui/webui/resources/js:cr",
     "//ui/webui/resources/js:i18n_behavior",
     "//ui/webui/resources/js:load_time_data",
+    "//ui/webui/resources/js:web_ui_listener_behavior",
   ]
 }
 
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.js b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.js
index 5be7940..4a2ccaf2 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.js
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_browser_proxy.js
@@ -10,7 +10,14 @@
 cr.define('settings', function() {
   /** @interface */
   class AmbientModeBrowserProxy {
+    /**
+     * Retrieves the initial settings from server, such as topic source. As a
+     * response, the C++ sends the 'topic-source-changed' WebUIListener event.
+     */
     onAmbientModePageReady() {}
+
+    /** Updates the selected topic source to server. */
+    onTopicSourceSelectedChanged(selected) {}
   }
 
   /** @implements {settings.AmbientModeBrowserProxy} */
@@ -19,6 +26,11 @@
     onAmbientModePageReady() {
       chrome.send('onAmbientModePageReady');
     }
+
+    /** @override */
+    onTopicSourceSelectedChanged(selected) {
+      chrome.send('onTopicSourceSelectedChanged', [selected]);
+    }
   }
 
   // The singleton instance_ is replaced with a test version of this wrapper
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.html b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.html
index 8284aeb..c0adb501 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.html
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.html
@@ -3,6 +3,7 @@
 <link rel="import" href="ambient_mode_browser_proxy.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button.html">
 <link rel="import" href="chrome://resources/html/i18n_behavior.html">
+<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
 <link rel="import" href="../../controls/settings_radio_group.html">
 <link rel="import" href="../../controls/settings_toggle_button.html">
 <link rel="import" href="../../settings_shared_css.html">
@@ -31,19 +32,24 @@
       <label id="topicSourceDescription" class="settings-box-text">
           $i18n{ambientModeTopicSourceTitle}
       </label>
-      <settings-radio-group id="topicSourceRadioGroup"
-          class="list-frame"
-          aria-labelledby="topicSourceDescription"
-          pref="{{prefs.settings.ambient_mode.topic_source}}">
-        <cr-radio-button name="[[topicSource_.GOOGLE_PHOTOS]]"
-            class="list-item underbar"
-            label="$i18n{ambientModeTopicSourceGooglePhotos}">
-        </cr-radio-button>
-        <cr-radio-button name="[[topicSource_.ART_GALLERY]]"
-            class="list-item underbar"
-            label="$i18n{ambientModeTopicSourceArtGallery}">
-        </cr-radio-button>
-      </settings-radio-group>
+      <div class="list-frame">
+        <!-- Disable the radio buttons before the selection is fetched from
+        server. -->
+        <cr-radio-group id="topicSourceRadioGroup"
+            aria-labelledby="topicSourceDescription"
+            selected="[[topicSourceSelected_]]"
+            on-selected-changed="onTopicSourceSelectedChanged_"
+            disabled="[[!isValidTopicSource_(topicSourceSelected_)]]">
+          <cr-radio-button name="[[topicSource_.GOOGLE_PHOTOS]]"
+              class="list-item underbar"
+              label="$i18n{ambientModeTopicSourceGooglePhotos}">
+          </cr-radio-button>
+          <cr-radio-button name="[[topicSource_.ART_GALLERY]]"
+              class="list-item underbar"
+              label="$i18n{ambientModeTopicSourceArtGallery}">
+          </cr-radio-button>
+        </cr-radio-group>
+      </div>
     </template>
   </template>
   <script src="ambient_mode_page.js"></script>
diff --git a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.js b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.js
index 109219d..49a6302e 100644
--- a/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.js
+++ b/chrome/browser/resources/settings/chromeos/ambient_mode_page/ambient_mode_page.js
@@ -9,19 +9,20 @@
 Polymer({
   is: 'settings-ambient-mode-page',
 
-  behaviors: [I18nBehavior, PrefsBehavior],
+  behaviors: [I18nBehavior, PrefsBehavior, WebUIListenerBehavior],
 
   properties: {
     prefs: Object,
 
     /**
-     * Enum values for the 'settings.ambient_mode.topic_source' preference.
-     * Values need to stay in sync with the |ash::ambient::prefs::TopicSource|.
+     * Enum values for topicSourceRadioGroup.
+     * Values need to stay in sync with the enum |ash::ambient::TopicSource|.
      * @private {!Object<string, number>}
      */
     topicSource_: {
       type: Object,
       value: {
+        UNKNOWN: -1,
         GOOGLE_PHOTOS: 0,
         ART_GALLERY: 1,
       },
@@ -29,12 +30,11 @@
     },
 
     /** @private */
-    isAmbientModeEnabled_: {
-      type: Boolean,
+    topicSourceSelected_: {
+      type: String,
       value() {
-        return loadTimeData.getBoolean('isAmbientModeEnabled');
+        return this.topicSource_.UNKNOWN;
       },
-      readOnly: true,
     },
   },
 
@@ -48,6 +48,10 @@
 
   /** @override */
   ready() {
+    this.addWebUIListener('topic-source-changed', (topicSource) => {
+      this.topicSourceSelected_ = topicSource;
+    });
+
     this.browserProxy_.onAmbientModePageReady();
   },
 
@@ -59,4 +63,19 @@
   getAmbientModeOnOffLabel_(toggleValue) {
     return this.i18n(toggleValue ? 'ambientModeOn' : 'ambientModeOff');
   },
+
+  /** @private */
+  onTopicSourceSelectedChanged_() {
+    return this.browserProxy_.onTopicSourceSelectedChanged(
+        this.$$('#topicSourceRadioGroup').selected);
+  },
+
+  /**
+   * @param {number} topicSource
+   * @return {boolean}
+   * @private
+   */
+  isValidTopicSource_(topicSource) {
+    return this.topicSourceSelected_ !== this.topicSource_.UNKNOWN;
+  },
 });
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_controls.html b/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_controls.html
index f95eafd..e06d09e 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_controls.html
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_controls.html
@@ -17,6 +17,8 @@
     <style include="settings-shared">
       :host {
         --shown-avatar-size: 40px;
+        --sync-icon-border-size: 2px;
+        --sync-icon-size: 16px;
       }
 
       #avatarContainer {
@@ -31,6 +33,45 @@
         width: var(--shown-avatar-size);
       }
 
+      /* Similar to browser settings-sync-account-control styling. */
+      #syncIconContainer {
+        align-items: center;
+        background: var(--google-green-700);
+        border: var(--sync-icon-border-size) solid white;
+        border-radius: 50%;
+        display: flex;
+        height: var(--sync-icon-size);
+        position: absolute;
+        right: -6px;
+        top: calc(var(--shown-avatar-size) - var(--sync-icon-size) -
+            var(--sync-icon-border-size));
+        width: var(--sync-icon-size);
+      }
+
+      :host-context([dir='rtl']) #syncIconContainer {
+        left: -6px;
+        right: initial;
+      }
+
+      #syncIconContainer.sync-problem {
+        background: var(--settings-error-color);
+      }
+
+      #syncIconContainer.sync-paused {
+        background: var(--google-blue-500);
+      }
+
+      #syncIconContainer.sync-disabled {
+        background: var(--google-grey-400);
+      }
+
+      #syncIconContainer iron-icon {
+        fill: white;
+        height: 12px;
+        margin: auto;
+        width: 12px;
+      }
+
       .settings-box {
         border-top: none;
       }
@@ -56,7 +97,14 @@
     <div class="settings-box first two-line">
       <div id="avatarContainer">
         <img id="avatarIcon" alt="" src="[[profileIconUrl]]">
-        <!-- TODO(jamescook): Sync status overlay icon. -->
+        <div id="syncIconContainer" hidden="[[!osSyncFeatureEnabled]]"
+            class$="[[getSyncIconStyle_(
+                syncStatus.hasError, syncStatus.statusAction,
+                syncStatus.disabled)]]">
+          <iron-icon icon$="[[getSyncIcon_(
+              syncStatus.hasError, syncStatus.statusAction,
+              syncStatus.disabled)]]"></iron-icon>
+        </div>
       </div>
       <div class="middle two-line no-min-width">
         <div class="flex text-elide settings-box-text">
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_controls.js b/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_controls.js
index 29283e2e..04a9902 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_controls.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/os_sync_controls.js
@@ -144,6 +144,45 @@
   },
 
   /**
+   * Returns the CSS class for the sync status icon.
+   * @return {string}
+   * @private
+   */
+  getSyncIconStyle_() {
+    if (this.syncStatus.disabled) {
+      return 'sync-disabled';
+    }
+    if (!this.syncStatus.hasError) {
+      return 'sync';
+    }
+    // Specific error cases below.
+    if (this.syncStatus.hasUnrecoverableError) {
+      return 'sync-problem';
+    }
+    if (this.syncStatus.statusAction == settings.StatusAction.REAUTHENTICATE) {
+      return 'sync-paused';
+    }
+    return 'sync-problem';
+  },
+
+  /**
+   * Returns the image to use for the sync status icon. The value must match
+   * one of iron-icon's settings:(*) icon names.
+   * @return {string}
+   * @private
+   */
+  getSyncIcon_() {
+    switch (this.getSyncIconStyle_()) {
+      case 'sync-problem':
+        return 'settings:sync-problem';
+      case 'sync-paused':
+        return 'settings:sync-disabled';
+      default:
+        return 'cr:sync';
+    }
+  },
+
+  /**
    * Handler for when the sync preferences are updated.
    * @private
    */
diff --git a/chrome/browser/resources/settings/chromeos/os_route.js b/chrome/browser/resources/settings/chromeos/os_route.js
index 2727191..029ddf0 100644
--- a/chrome/browser/resources/settings/chromeos/os_route.js
+++ b/chrome/browser/resources/settings/chromeos/os_route.js
@@ -97,7 +97,7 @@
 
     r.ADVANCED = new settings.Route('/advanced');
 
-    r.PRIVACY = r.ADVANCED.createSection('/privacy', 'privacy');
+    r.OS_PRIVACY = r.ADVANCED.createSection('/osPrivacy', 'osPrivacy');
 
     // Languages and input
     r.OS_LANGUAGES = r.ADVANCED.createSection('/osLanguages', 'osLanguages');
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.html b/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.html
index 0553a329..d395434 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.html
+++ b/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.html
@@ -238,7 +238,7 @@
               $i18n{dateTimePageTitle}
             </div>
           </a>
-          <a href="/privacy">
+          <a href="/osPrivacy">
             <div class="item">
               <iron-icon icon="cr:security"></iron-icon>
               $i18n{privacyPageTitle}
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.html b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.html
index ab29cef8..e4ec72f6 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.html
+++ b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.html
@@ -222,7 +222,7 @@
             </settings-date-time-page>
           </settings-section>
           <settings-section page-title="$i18n{privacyPageTitle}"
-              section="privacy">
+              section="osPrivacy">
             <os-settings-privacy-page prefs="{{prefs}}">
             </os-settings-privacy-page>
           </settings-section>
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_routes.js b/chrome/browser/resources/settings/chromeos/os_settings_routes.js
index dd9fdd4..ba295dd5 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_routes.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings_routes.js
@@ -54,6 +54,7 @@
  *   OS_LANGUAGES_DETAILS: !settings.Route,
  *   OS_LANGUAGES_INPUT_METHODS: !settings.Route,
  *   OS_PRINTING: !settings.Route,
+ *   OS_PRIVACY: !settings.Route,
  *   OS_RESET: !settings.Route,
  *   OS_SEARCH: !settings.Route,
  *   OS_SYNC: !settings.Route,
@@ -66,7 +67,6 @@
  *   POWER: !settings.Route,
  *   PRIVACY: !settings.Route,
  *   SEARCH: !settings.Route,
- *   RESET: !settings.Route,
  *   SIGN_OUT: !settings.Route,
  *   SMART_LOCK: !settings.Route,
  *   SMB_SHARES: !settings.Route,
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.html b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.html
index 4db0fea..1918375 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.html
+++ b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.html
@@ -16,8 +16,10 @@
   <template>
     <style include="settings-shared">
       :host {
+        --cr-toolbar-field-width: 480px;
         display: flex;
-        flex-basis: var(--cr-toolbar-field-width, 680px);
+        flex-basis: var(--cr-toolbar-field-width);
+        flex-direction: row;
         transition: width 150ms cubic-bezier(0.4, 0, 0.2, 1);
       }
 
@@ -30,15 +32,36 @@
         flex-basis: 100%;
       }
 
+      cr-toolbar-search-field {
+        --cr-toolbar-search-field-border-radius: 20px;
+        transition: height 150ms;
+      }
+
+      :host(:focus-within) cr-toolbar-search-field {
+        --cr-toolbar-search-field-background: var(--cr-card-background-color);
+      }
+
+      :host(:focus-within[should-show-dropdown_]) cr-toolbar-search-field {
+        --cr-toolbar-search-field-border-radius: 20px 20px 0 0;
+        border-bottom: var(--cr-separator-line);
+        height: 48px;
+      }
+
+      :host(:focus-within:not([narrow])) cr-toolbar-search-field {
+        box-shadow: var(--cr-menu-shadow);
+      }
+
       :host([narrow]) iron-dropdown {
         left: 0;
         right: 0;
       }
 
       iron-dropdown [slot='dropdown-content'] {
-        background-color: var(--cr-card-background-color);
-        box-shadow: var(--cr-card-shadow);
+        background-color: var(--cr-menu-background-color);
+        border-radius: 0 0 20px 20px;
+        box-shadow: var(--cr-menu-shadow);
         display: table;
+        padding: 8px 0 13px 0;
       }
 
       :host([narrow]) iron-dropdown [slot='dropdown-content'] {
@@ -58,24 +81,26 @@
       <!--  As part of iron-dropdown's behavior, the slot 'dropdown-content' is
             hidden until iron-dropdown's opened attribute is set true, or when
             iron-dropdown's open() is called on the JS side. -->
-      <iron-list id="searchResultList" slot="dropdown-content" selection-enabled
-          items="[[searchResults_]]" selected-item="{{selectedItem_}}"
-          on-selected-item-changed="onSelectedItemChanged_">
-        <!-- TODO(crbug/1056909): Use template dom-if if searchResults_ is
-             empty, show 'No Results' UI -->
-        <template>
-          <os-search-result-row actionable search-result="[[item]]"
-              selected="[[isItemSelected_(item, selectedItem_)]]"
-              tabindex$="[[getRowTabIndex_(item, selectedItem_)]]"
-              iron-list-tab-index$="[[getRowTabIndex_(item, selectedItem_)]]"
-              on-navigated-to-result-route="onNavigatedtoResultRowRoute_"
-              last-focused="{{lastFocused_}}"
-              list-blurred="{{listBlurred_}}"
-              focus-row-index="[[index]]"
-              first$="[[!index]]">
-          </os-search-result-row>
-        </template>
-      </iron-list>
+      <div slot="dropdown-content">
+        <iron-list id="searchResultList" selection-enabled
+            items="[[searchResults_]]" selected-item="{{selectedItem_}}"
+            on-selected-item-changed="onSelectedItemChanged_">
+          <!-- TODO(crbug/1056909): Use template dom-if if searchResults_ is
+               empty, show 'No Results' UI -->
+          <template>
+            <os-search-result-row actionable search-result="[[item]]"
+                selected="[[isItemSelected_(item, selectedItem_)]]"
+                tabindex$="[[getRowTabIndex_(item, selectedItem_)]]"
+                iron-list-tab-index$="[[getRowTabIndex_(item, selectedItem_)]]"
+                on-navigated-to-result-route="onNavigatedtoResultRowRoute_"
+                last-focused="{{lastFocused_}}"
+                list-blurred="{{listBlurred_}}"
+                focus-row-index="[[index]]"
+                first$="[[!index]]">
+            </os-search-result-row>
+          </template>
+        </iron-list>
+      </div>
     </iron-dropdown>
   </template>
   <script src="os_settings_search_box.js"></script>
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.js b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.js
index c8107578..4926bdb 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.js
@@ -125,6 +125,7 @@
       type: Boolean,
       value: false,
       computed: 'computeShouldShowDropdown_(searchResults_)',
+      reflectToAttribute: true,
     },
 
     /**
diff --git a/chrome/browser/ui/ash/chrome_new_window_client.cc b/chrome/browser/ui/ash/chrome_new_window_client.cc
index 7819b5ba..2d4ededd 100644
--- a/chrome/browser/ui/ash/chrome_new_window_client.cc
+++ b/chrome/browser/ui/ash/chrome_new_window_client.cc
@@ -121,6 +121,7 @@
      {ChromePage::PLUGINVMSHAREDPATHS, chrome::kPluginVmSharedPathsSubPage},
      {ChromePage::OSACCESSIBILITY, chrome::kOsAccessibilitySubPage},
      {ChromePage::OSPRINTING, chrome::kOsPrintingSubPage},
+     {ChromePage::OSPRIVACY, chrome::kOsPrivacySubPage},
      {ChromePage::OSRESET, chrome::kOsResetSubPage},
      {ChromePage::OSSEARCH, chrome::kOsSearchSubPage},
      {ChromePage::POINTEROVERLAY, chrome::kPointerOverlaySubPage},
diff --git a/chrome/browser/ui/ash/chrome_new_window_client_browsertest.cc b/chrome/browser/ui/ash/chrome_new_window_client_browsertest.cc
index 3b80ffa..d3ef6ff 100644
--- a/chrome/browser/ui/ash/chrome_new_window_client_browsertest.cc
+++ b/chrome/browser/ui/ash/chrome_new_window_client_browsertest.cc
@@ -330,6 +330,8 @@
                      base_url.Resolve(chrome::kPluginVmSharedPathsSubPage));
   TestOpenChromePage(ChromePage::OSSEARCH,
                      base_url.Resolve(chrome::kOsSearchSubPage));
+  TestOpenChromePage(ChromePage::OSPRIVACY,
+                     base_url.Resolve(chrome::kOsPrivacySubPage));
   TestOpenChromePage(ChromePage::SMARTLOCKSETTINGS,
                      base_url.Resolve(chrome::kSmartLockSettingsSubPage));
   TestOpenChromePage(ChromePage::STYLUS,
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_bar_view_test.cc b/chrome/browser/ui/views/bookmarks/bookmark_bar_view_test.cc
index 27303c8..31e82b4 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_bar_view_test.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_bar_view_test.cc
@@ -325,7 +325,7 @@
     // clipboard selection from the X server (for the 'paste' item), so mock it
     // out.
     ui::TestClipboard::CreateForCurrentThread();
-    GetWidget()->Activate();
+    window()->Activate();
 #endif
   }
 
@@ -1056,7 +1056,7 @@
 
     // Send a down event, which should select the first item.
     ASSERT_TRUE(ui_controls::SendKeyPressNotifyWhenDone(
-        window_->GetNativeWindow(), ui::VKEY_DOWN, false, false, false, false,
+        window()->GetNativeWindow(), ui::VKEY_DOWN, false, false, false, false,
         CreateEventTask(this, &BookmarkBarViewTest10::Step3)));
   }
 
@@ -1069,7 +1069,7 @@
 
     // Send a key down event, which should select the next item.
     ASSERT_TRUE(ui_controls::SendKeyPressNotifyWhenDone(
-        window_->GetNativeWindow(), ui::VKEY_DOWN, false, false, false, false,
+        window()->GetNativeWindow(), ui::VKEY_DOWN, false, false, false, false,
         CreateEventTask(this, &BookmarkBarViewTest10::Step4)));
   }
 
@@ -1082,7 +1082,7 @@
 
     // Send a right arrow to force the menu to open.
     ASSERT_TRUE(ui_controls::SendKeyPressNotifyWhenDone(
-        window_->GetNativeWindow(), ui::VKEY_RIGHT, false, false, false, false,
+        window()->GetNativeWindow(), ui::VKEY_RIGHT, false, false, false, false,
         CreateEventTask(this, &BookmarkBarViewTest10::Step5)));
   }
 
@@ -1098,7 +1098,7 @@
 
     // Send a left arrow to close the submenu.
     ASSERT_TRUE(ui_controls::SendKeyPressNotifyWhenDone(
-        window_->GetNativeWindow(), ui::VKEY_LEFT, false, false, false, false,
+        window()->GetNativeWindow(), ui::VKEY_LEFT, false, false, false, false,
         CreateEventTask(this, &BookmarkBarViewTest10::Step6)));
   }
 
@@ -1113,7 +1113,7 @@
 
     // Send a down arrow to go down to f1b.
     ASSERT_TRUE(ui_controls::SendKeyPressNotifyWhenDone(
-        window_->GetNativeWindow(), ui::VKEY_DOWN, false, false, false, false,
+        window()->GetNativeWindow(), ui::VKEY_DOWN, false, false, false, false,
         CreateEventTask(this, &BookmarkBarViewTest10::Step7)));
   }
 
@@ -1126,7 +1126,7 @@
 
     // Send a down arrow to wrap back to f1a.
     ASSERT_TRUE(ui_controls::SendKeyPressNotifyWhenDone(
-        window_->GetNativeWindow(), ui::VKEY_DOWN, false, false, false, false,
+        window()->GetNativeWindow(), ui::VKEY_DOWN, false, false, false, false,
         CreateEventTask(this, &BookmarkBarViewTest10::Step8)));
   }
 
@@ -1139,8 +1139,8 @@
 
     // Send enter, which should select the item.
     ASSERT_TRUE(ui_controls::SendKeyPressNotifyWhenDone(
-        window_->GetNativeWindow(), ui::VKEY_RETURN, false, false, false, false,
-        CreateEventTask(this, &BookmarkBarViewTest10::Step9)));
+        window()->GetNativeWindow(), ui::VKEY_RETURN, false, false, false,
+        false, CreateEventTask(this, &BookmarkBarViewTest10::Step9)));
   }
 
   void Step9() {
@@ -1194,8 +1194,8 @@
   void Step3() {
     // Send escape so that the context menu hides.
     ASSERT_TRUE(ui_controls::SendKeyPressNotifyWhenDone(
-        window_->GetNativeWindow(), ui::VKEY_ESCAPE, false, false, false, false,
-        CreateEventTask(this, &BookmarkBarViewTest11::Step4)));
+        window()->GetNativeWindow(), ui::VKEY_ESCAPE, false, false, false,
+        false, CreateEventTask(this, &BookmarkBarViewTest11::Step4)));
   }
 
   void Step4() {
@@ -1426,8 +1426,8 @@
 
     // Send escape so that the context menu hides.
     ASSERT_TRUE(ui_controls::SendKeyPressNotifyWhenDone(
-        window_->GetNativeWindow(), ui::VKEY_ESCAPE, false, false, false, false,
-        CreateEventTask(this, &BookmarkBarViewTest14::Step3)));
+        window()->GetNativeWindow(), ui::VKEY_ESCAPE, false, false, false,
+        false, CreateEventTask(this, &BookmarkBarViewTest14::Step3)));
   }
 
   void Step3() {
@@ -1542,8 +1542,7 @@
     ASSERT_TRUE(button->state() == views::Button::STATE_PRESSED);
 
     // Close the window.
-    window_->Close();
-    window_ = NULL;
+    window()->Close();
 
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE, CreateEventTask(this, &BookmarkBarViewTest16::Done));
@@ -1962,7 +1961,7 @@
   void OnWidgetDragWillStart(views::Widget* widget) override {
     // Watch for main window destruction instead of menu dragging.
     widget_observer()->RemoveAll();
-    widget_observer()->Add(window_);
+    widget_observer()->Add(window());
 
     BookmarkBarViewDragTestBase::OnWidgetDragWillStart(widget);
   }
@@ -1988,8 +1987,7 @@
     // window alone may not exit this message loop.
     BookmarkBarViewDragTestBase::OnDragEntered();
 
-    window_->Close();
-    window_ = nullptr;
+    window()->Close();
   }
 
   const BookmarkNode* GetDroppedNode() const override {
@@ -2031,8 +2029,7 @@
 
     // Navigate down to highlight the first menu item.
     ASSERT_TRUE(ui_controls::SendKeyPressNotifyWhenDone(
-        GetWidget()->GetNativeWindow(), ui::VKEY_DOWN, false, false, false,
-        false,  // No modifer keys
+        window()->GetNativeWindow(), ui::VKEY_DOWN, false, false, false, false,
         CreateEventTask(this, &BookmarkBarViewTest23::Step3)));
   }
 
@@ -2043,10 +2040,9 @@
     ASSERT_TRUE(menu->GetSubmenu()->IsShowing());
 
     // Open the context menu via the keyboard.
-    ASSERT_TRUE(ui_controls::SendKeyPress(GetWidget()->GetNativeWindow(),
+    ASSERT_TRUE(ui_controls::SendKeyPress(window()->GetNativeWindow(),
                                           ui::VKEY_APPS, false, false, false,
-                                          false  // No modifer keys
-                                          ));
+                                          false));
     // The BookmarkContextMenuNotificationObserver triggers Step4.
   }
 
@@ -2102,8 +2098,7 @@
 
     // Navigate down to highlight the first menu item.
     ASSERT_TRUE(ui_controls::SendKeyPressNotifyWhenDone(
-        GetWidget()->GetNativeWindow(), ui::VKEY_DOWN, false, false, false,
-        false,  // No modifer keys
+        window()->GetNativeWindow(), ui::VKEY_DOWN, false, false, false, false,
         CreateEventTask(this, &BookmarkBarViewTest24::Step3)));
   }
 
@@ -2114,10 +2109,9 @@
     ASSERT_TRUE(menu->GetSubmenu()->IsShowing());
 
     // Open the context menu via the keyboard.
-    ASSERT_TRUE(ui_controls::SendKeyPress(GetWidget()->GetNativeWindow(),
+    ASSERT_TRUE(ui_controls::SendKeyPress(window()->GetNativeWindow(),
                                           ui::VKEY_APPS, false, false, false,
-                                          false  // No modifer keys
-                                          ));
+                                          false));
     // The BookmarkContextMenuNotificationObserver triggers Step4.
   }
 
@@ -2130,8 +2124,8 @@
 
     // Send escape to close the context menu.
     ASSERT_TRUE(ui_controls::SendKeyPressNotifyWhenDone(
-        window_->GetNativeWindow(), ui::VKEY_ESCAPE, false, false, false, false,
-        CreateEventTask(this, &BookmarkBarViewTest24::Step5)));
+        window()->GetNativeWindow(), ui::VKEY_ESCAPE, false, false, false,
+        false, CreateEventTask(this, &BookmarkBarViewTest24::Step5)));
   }
 
   void Step5() {
@@ -2145,8 +2139,8 @@
 
     // Send escape to close the main menu.
     ASSERT_TRUE(ui_controls::SendKeyPressNotifyWhenDone(
-        window_->GetNativeWindow(), ui::VKEY_ESCAPE, false, false, false, false,
-        CreateEventTask(this, &BookmarkBarViewTest24::Done)));
+        window()->GetNativeWindow(), ui::VKEY_ESCAPE, false, false, false,
+        false, CreateEventTask(this, &BookmarkBarViewTest24::Done)));
   }
 
   BookmarkContextMenuNotificationObserver observer_;
@@ -2177,7 +2171,7 @@
 
     // Send KEYCODE key event, which should close the menu.
     ASSERT_TRUE(ui_controls::SendKeyPressNotifyWhenDone(
-        window_->GetNativeWindow(), KEYCODE, false, false, false, false,
+        window()->GetNativeWindow(), KEYCODE, false, false, false, false,
         CreateEventTask(this, &BookmarkBarViewTest25::Step3)));
   }
 
@@ -2220,9 +2214,8 @@
     // Send WM_CANCELMODE, which should close the menu. The message is sent
     // synchronously, however, we post a task to make sure that the message is
     // processed completely before finishing the test.
-    ::SendMessage(
-        GetWidget()->GetNativeView()->GetHost()->GetAcceleratedWidget(),
-        WM_CANCELMODE, 0, 0);
+    ::SendMessage(window()->GetNativeView()->GetHost()->GetAcceleratedWidget(),
+                  WM_CANCELMODE, 0, 0);
 
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
diff --git a/chrome/browser/ui/views/crostini/crostini_browser_test_util.cc b/chrome/browser/ui/views/crostini/crostini_browser_test_util.cc
index 93fe3d9..6fd2207 100644
--- a/chrome/browser/ui/views/crostini/crostini_browser_test_util.cc
+++ b/chrome/browser/ui/views/crostini/crostini_browser_test_util.cc
@@ -130,32 +130,3 @@
           component_updater::CrOSComponentManager::Error::INSTALL_FAILURE,
           base::FilePath(), base::FilePath()));
 }
-
-class WebContentsWaiter : public content::WebContentsObserver {
- public:
-  enum Operation { LOAD };  // Add other operations as required.
-  explicit WebContentsWaiter(content::WebContents* contents,
-                             Operation operation)
-      : content::WebContentsObserver(contents), operation_(operation) {}
-
-  ~WebContentsWaiter() override = default;
-
-  void Wait() { run_loop_.Run(); }
-
-  // content::WebContentsObserver:
-  void DidFinishLoad(content::RenderFrameHost* render_frame_host,
-                     const GURL& validated_url) override {
-    if (operation_ == LOAD) {
-      run_loop_.Quit();
-    }
-  }
-
- private:
-  base::RunLoop run_loop_;
-  Operation operation_;
-};
-
-void CrostiniDialogBrowserTest::WaitForLoadFinished(
-    content::WebContents* contents) {
-  WebContentsWaiter(contents, WebContentsWaiter::LOAD).Wait();
-}
diff --git a/chrome/browser/ui/views/crostini/crostini_browser_test_util.h b/chrome/browser/ui/views/crostini/crostini_browser_test_util.h
index 468ac07..3958c91 100644
--- a/chrome/browser/ui/views/crostini/crostini_browser_test_util.h
+++ b/chrome/browser/ui/views/crostini/crostini_browser_test_util.h
@@ -15,10 +15,6 @@
 
 class CrostiniBrowserTestChromeBrowserMainExtraParts;
 
-namespace content {
-class WebContents;
-}
-
 // Common base for Crostini dialog broswer tests. Allows tests to set network
 // connection type.
 class CrostiniDialogBrowserTest : public DialogBrowserTest {
@@ -36,8 +32,6 @@
 
   void UnregisterTermina();
 
-  void WaitForLoadFinished(content::WebContents* contents);
-
  protected:
   const bool register_termina_;
 
diff --git a/chrome/browser/ui/views/crostini/crostini_update_component_view_browsertest.cc b/chrome/browser/ui/views/crostini/crostini_update_component_view_browsertest.cc
index bd64705..0d5e7f2 100644
--- a/chrome/browser/ui/views/crostini/crostini_update_component_view_browsertest.cc
+++ b/chrome/browser/ui/views/crostini/crostini_update_component_view_browsertest.cc
@@ -24,6 +24,25 @@
 #include "content/public/browser/web_contents_observer.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+class LoadFinishedWaiter : public content::WebContentsObserver {
+ public:
+  explicit LoadFinishedWaiter(content::WebContents* contents)
+      : content::WebContentsObserver(contents) {}
+
+  ~LoadFinishedWaiter() override = default;
+
+  void Wait() { run_loop_.Run(); }
+
+  // content::WebContentsObserver:
+  void DidFinishLoad(content::RenderFrameHost* render_frame_host,
+                     const GURL& validated_url) override {
+    run_loop_.Quit();
+  }
+
+ private:
+  base::RunLoop run_loop_;
+};
+
 class CrostiniUpdateComponentViewBrowserTest
     : public CrostiniDialogBrowserTest {
  public:
@@ -134,8 +153,8 @@
     Browser* terminal_browser = web_app::FindSystemWebAppBrowser(
         browser()->profile(), web_app::SystemAppType::TERMINAL);
     CHECK_NE(nullptr, terminal_browser);
-    WaitForLoadFinished(
-        terminal_browser->tab_strip_model()->GetWebContentsAt(0));
+    LoadFinishedWaiter(terminal_browser->tab_strip_model()->GetWebContentsAt(0))
+        .Wait();
   }
 
   ExpectView();
diff --git a/chrome/browser/ui/views/menu_test_base.cc b/chrome/browser/ui/views/menu_test_base.cc
index 4778c14..9f7203f7 100644
--- a/chrome/browser/ui/views/menu_test_base.cc
+++ b/chrome/browser/ui/views/menu_test_base.cc
@@ -28,8 +28,8 @@
 }
 
 void MenuTestBase::KeyPress(ui::KeyboardCode keycode, base::OnceClosure next) {
-  ui_controls::SendKeyPressNotifyWhenDone(GetWidget()->GetNativeWindow(),
-                                          keycode, false, false, false, false,
+  ui_controls::SendKeyPressNotifyWhenDone(window()->GetNativeWindow(), keycode,
+                                          false, false, false, false,
                                           std::move(next));
 }
 
diff --git a/chrome/browser/ui/views/test/view_event_test_base.cc b/chrome/browser/ui/views/test/view_event_test_base.cc
index e4c7c18..c9a76ff 100644
--- a/chrome/browser/ui/views/test/view_event_test_base.cc
+++ b/chrome/browser/ui/views/test/view_event_test_base.cc
@@ -17,36 +17,63 @@
 #include "ui/base/clipboard/clipboard.h"
 #include "ui/base/ime/init/input_method_initializer.h"
 #include "ui/compositor/test/test_context_factories.h"
+#include "ui/views/layout/fill_layout.h"
 #include "ui/views/view.h"
 #include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
 
 namespace {
 
-// View subclass that allows you to specify the preferred size.
+// View that keeps its preferred size in sync with what |harness| requests.
 class TestView : public views::View {
  public:
-  explicit TestView(ViewEventTestBase* harness) : harness_(harness) {}
+  explicit TestView(ViewEventTestBase* harness) : harness_(harness) {
+    SetLayoutManager(std::make_unique<views::FillLayout>());
+    AddChildView(harness_->CreateContentsView());
+  }
+  TestView(const TestView&) = delete;
+  TestView& operator=(const TestView&) = delete;
+  ~TestView() override = default;
 
   gfx::Size CalculatePreferredSize() const override {
     return harness_->GetPreferredSizeForContents();
   }
 
-  void Layout() override {
-    // Permit a test to remove the view being tested from the hierarchy, then
-    // still handle a _NET_WM_STATE event on Linux during teardown that triggers
-    // layout.
-    if (!children().empty())
-      children().front()->SetBoundsRect(GetLocalBounds());
+ private:
+  ViewEventTestBase* harness_;
+};
+
+}  // namespace
+
+class TestBaseWidgetDelegate : public views::WidgetDelegate {
+ public:
+  explicit TestBaseWidgetDelegate(ViewEventTestBase* harness)
+      : harness_(harness) {}
+  TestBaseWidgetDelegate(const TestBaseWidgetDelegate&) = delete;
+  TestBaseWidgetDelegate& operator=(const TestBaseWidgetDelegate&) = delete;
+  ~TestBaseWidgetDelegate() override = default;
+
+  // views::WidgetDelegate:
+  bool CanResize() const override { return true; }
+  void WindowClosing() override { harness_->window_ = nullptr; }
+  void DeleteDelegate() override { delete this; }
+  views::Widget* GetWidget() override { return contents_->GetWidget(); }
+  const views::Widget* GetWidget() const override {
+    return contents_->GetWidget();
+  }
+  views::View* GetContentsView() override {
+    // This will first be called by Widget::Init(), which passes the returned
+    // View* to SetContentsView(), which takes ownership.
+    if (!contents_)
+      contents_ = new TestView(harness_);
+    return contents_;
   }
 
  private:
   ViewEventTestBase* harness_;
-
-  DISALLOW_COPY_AND_ASSIGN(TestView);
+  views::View* contents_ = nullptr;
 };
 
-}  // namespace
-
 ViewEventTestBase::ViewEventTestBase() {
   // The TestingBrowserProcess must be created in the constructor because there
   // are tests that require it before SetUp() is called.
@@ -58,9 +85,8 @@
   mojo::core::Init();
 }
 
-void ViewEventTestBase::Done() {
-  drag_event_thread_.reset();
-  run_loop_.Quit();
+ViewEventTestBase::~ViewEventTestBase() {
+  TestingBrowserProcess::DeleteInstance();
 }
 
 void ViewEventTestBase::SetUpTestCase() {
@@ -72,9 +98,7 @@
   ui::InitializeInputMethodForTesting();
 
   // The ContextFactory must exist before any Compositors are created.
-  const bool enable_pixel_output = false;
-  context_factories_ =
-      std::make_unique<ui::TestContextFactories>(enable_pixel_output);
+  context_factories_ = std::make_unique<ui::TestContextFactories>(false);
 
 #if defined(OS_MACOSX)
   views_delegate_.set_context_factory(context_factories_->GetContextFactory());
@@ -83,8 +107,10 @@
 
   platform_part_.reset(ViewEventTestPlatformPart::Create(
       context_factories_->GetContextFactory()));
+
   window_ = views::Widget::CreateWindowWithContext(
-      this, platform_part_->GetContext());
+      new TestBaseWidgetDelegate(this),  // Owns itself.
+      platform_part_->GetContext());
   window_->Show();
 }
 
@@ -92,7 +118,6 @@
   if (window_) {
     window_->Close();
     base::RunLoop().RunUntilIdle();
-    window_ = NULL;
   }
 
   ui::Clipboard::DestroyClipboardForCurrentThread();
@@ -107,31 +132,9 @@
   return gfx::Size();
 }
 
-bool ViewEventTestBase::CanResize() const {
-  return true;
-}
-
-views::View* ViewEventTestBase::GetContentsView() {
-  if (!content_view_) {
-    // Wrap the real view (as returned by CreateContentsView) in a View so
-    // that we can customize the preferred size.
-    TestView* test_view = new TestView(this);
-    test_view->AddChildView(CreateContentsView());
-    content_view_ = test_view;
-  }
-  return content_view_;
-}
-
-const views::Widget* ViewEventTestBase::GetWidget() const {
-  return content_view_->GetWidget();
-}
-
-views::Widget* ViewEventTestBase::GetWidget() {
-  return content_view_->GetWidget();
-}
-
-ViewEventTestBase::~ViewEventTestBase() {
-  TestingBrowserProcess::DeleteInstance();
+void ViewEventTestBase::Done() {
+  drag_event_thread_.reset();
+  run_loop_.Quit();
 }
 
 void ViewEventTestBase::StartMessageLoopAndRunTest() {
diff --git a/chrome/browser/ui/views/test/view_event_test_base.h b/chrome/browser/ui/views/test/view_event_test_base.h
index d9fab1b..338195a 100644
--- a/chrome/browser/ui/views/test/view_event_test_base.h
+++ b/chrome/browser/ui/views/test/view_event_test_base.h
@@ -15,14 +15,12 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/compiler_specific.h"
-#include "base/macros.h"
 #include "base/run_loop.h"
 #include "base/threading/thread.h"
 #include "build/build_config.h"
 #include "chrome/test/views/chrome_test_views_delegate.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "ui/views/widget/widget_delegate.h"
 
 #if defined(OS_WIN)
 #include "ui/base/win/scoped_ole_initializer.h"
@@ -37,6 +35,7 @@
 }
 
 class ViewEventTestPlatformPart;
+class TestBaseWidgetDelegate;
 
 // Base class for Views based tests that dispatch events.
 //
@@ -70,39 +69,34 @@
 // driven from observer callbacks and posted on the task runner returned by
 // GetDragTaskRunner().
 
-class ViewEventTestBase : public views::WidgetDelegate, public testing::Test {
+class ViewEventTestBase : public testing::Test {
  public:
   ViewEventTestBase();
-
-  // Invoke when done either because of failure or success. Quits the message
-  // loop.
-  void Done();
+  ViewEventTestBase(const ViewEventTestBase&) = delete;
+  ViewEventTestBase& operator=(const ViewEventTestBase&) = delete;
+  ~ViewEventTestBase() override;
 
   static void SetUpTestCase();
 
-  // Creates a window.
+  // testing::Test:
   void SetUp() override;
-
-  // Destroys the window.
   void TearDown() override;
 
+  // Returns the view that is added to the window.
+  virtual views::View* CreateContentsView() = 0;
+
   // Returns an empty Size. Subclasses that want a preferred size other than
   // that of the View returned by CreateContentsView should override this
   // appropriately.
   virtual gfx::Size GetPreferredSizeForContents() const;
 
-  // views::WidgetDelegate:
-  bool CanResize() const override;
-  views::View* GetContentsView() override;
-  const views::Widget* GetWidget() const override;
-  views::Widget* GetWidget() override;
+  // Invoke when done either because of failure or success. Quits the message
+  // loop.
+  void Done();
+
+  views::Widget* window() { return window_; }
 
  protected:
-  ~ViewEventTestBase() override;
-
-  // Returns the view that is added to the window.
-  virtual views::View* CreateContentsView() = 0;
-
   // Called once the message loop is running.
   virtual void DoTestOnMessageLoop() = 0;
 
@@ -123,34 +117,25 @@
   // Returns a task runner to use for drag-related mouse events.
   scoped_refptr<base::SingleThreadTaskRunner> GetDragTaskRunner();
 
-  views::Widget* window_ = nullptr;
-
  private:
+  friend class TestBaseWidgetDelegate;
+
   // Callback from CreateEventTask. Runs the supplied task and if there are
   // failures invokes Done.
   void RunTestMethod(base::OnceClosure task);
 
-  // The content of the Window.
-  views::View* content_view_ = nullptr;
-
   // Thread for posting background drag events.
   std::unique_ptr<base::Thread> drag_event_thread_;
 
   content::BrowserTaskEnvironment task_environment_;
-
 #if defined(OS_WIN)
   ui::ScopedOleInitializer ole_initializer_;
 #endif
-
   std::unique_ptr<ui::TestContextFactories> context_factories_;
-
   std::unique_ptr<ViewEventTestPlatformPart> platform_part_;
-
   ChromeTestViewsDelegate<> views_delegate_;
-
   base::RunLoop run_loop_;
-
-  DISALLOW_COPY_AND_ASSIGN(ViewEventTestBase);
+  views::Widget* window_ = nullptr;
 };
 
 // Convenience macro for defining a ViewEventTestBase. See class description
diff --git a/chrome/browser/ui/views/touch_events_interactive_uitest_win.cc b/chrome/browser/ui/views/touch_events_interactive_uitest_win.cc
index 5561bf74..0eb6558 100644
--- a/chrome/browser/ui/views/touch_events_interactive_uitest_win.cc
+++ b/chrome/browser/ui/views/touch_events_interactive_uitest_win.cc
@@ -163,7 +163,7 @@
 
     const int touch_pointer_count = 3;
     TouchEventHandler touch_event_handler;
-    GetWidget()->GetNativeWindow()->GetHost()->window()->AddPreTargetHandler(
+    window()->GetNativeWindow()->GetHost()->window()->AddPreTargetHandler(
         &touch_event_handler);
     gfx::Point in_content(touch_view_->width() / 2, touch_view_->height() / 2);
     views::View::ConvertPointToScreen(touch_view_, &in_content);
@@ -181,7 +181,7 @@
     EXPECT_EQ(touch_pointer_count,
               gesture_recognizer_->num_touch_release_events());
 
-    GetWidget()->GetNativeWindow()->GetHost()->window()->RemovePreTargetHandler(
+    window()->GetNativeWindow()->GetHost()->window()->RemovePreTargetHandler(
         &touch_event_handler);
     Done();
   }
@@ -210,7 +210,7 @@
 
     const int touch_pointer_count = 1;
     TouchEventHandler touch_event_handler;
-    GetWidget()->GetNativeWindow()->GetHost()->window()->AddPreTargetHandler(
+    window()->GetNativeWindow()->GetHost()->window()->AddPreTargetHandler(
         &touch_event_handler);
     gfx::Point in_content(touch_view_->width() / 2, touch_view_->height() / 2);
     views::View::ConvertPointToScreen(touch_view_, &in_content);
@@ -224,7 +224,7 @@
     EXPECT_EQ(touch_pointer_count + 1, touch_event_handler.num_touch_presses());
     EXPECT_EQ(0, touch_event_handler.num_pointers_down());
     EXPECT_EQ(2, touch_event_handler.max_call_depth());
-    GetWidget()->GetNativeWindow()->GetHost()->window()->RemovePreTargetHandler(
+    window()->GetNativeWindow()->GetHost()->window()->RemovePreTargetHandler(
         &touch_event_handler);
     Done();
   }
diff --git a/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_dialog.cc b/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_dialog.cc
index 11110bd0..e5848706 100644
--- a/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_dialog.cc
+++ b/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_dialog.cc
@@ -46,11 +46,7 @@
       only_run_launch_closure_on_restart_(only_run_launch_closure_on_restart),
       launch_closure_{std::move(launch_closure)} {}
 
-CrostiniUpgraderDialog::~CrostiniUpgraderDialog() {
-  if (deletion_closure_for_testing_) {
-    std::move(deletion_closure_for_testing_).Run();
-  }
-}
+CrostiniUpgraderDialog::~CrostiniUpgraderDialog() = default;
 
 void CrostiniUpgraderDialog::GetDialogSize(gfx::Size* size) const {
   size->SetSize(::kDialogWidth, ::kDialogHeight);
@@ -69,19 +65,10 @@
   params->z_order = ui::ZOrderLevel::kNormal;
 }
 
-void CrostiniUpgraderDialog::SetDeletionClosureForTesting(
-    base::OnceClosure deletion_closure_for_testing) {
-  deletion_closure_for_testing_ = std::move(deletion_closure_for_testing);
-}
-
 bool CrostiniUpgraderDialog::CanCloseDialog() const {
   // TODO(929571): If other WebUI Dialogs also need to let the WebUI control
   // closing logic, we should find a more general solution.
 
-  if (deletion_closure_for_testing_) {
-    // Running in a test.
-    return true;
-  }
   // Disallow closing without WebUI consent.
   return upgrader_ui_ == nullptr || upgrader_ui_->can_close();
 }
diff --git a/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_dialog.h b/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_dialog.h
index 7f1a219..dc94be2 100644
--- a/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_dialog.h
+++ b/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_dialog.h
@@ -22,9 +22,6 @@
   static void Show(base::OnceClosure launch_closure,
                    bool only_run_launch_closure_on_restart = false);
 
-  void SetDeletionClosureForTesting(
-      base::OnceClosure deletion_closure_for_testing);
-
  private:
   explicit CrostiniUpgraderDialog(base::OnceClosure launch_closure,
                                   bool only_run_launch_closure_on_restart);
@@ -40,10 +37,10 @@
   void OnCloseContents(content::WebContents* source,
                        bool* out_close_dialog) override;
 
+ private:
   CrostiniUpgraderUI* upgrader_ui_ = nullptr;  // Not owned.
   const bool only_run_launch_closure_on_restart_;
   base::OnceClosure launch_closure_;
-  base::OnceClosure deletion_closure_for_testing_;
 };
 
 }  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_dialog_browsertest.cc b/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_dialog_browsertest.cc
deleted file mode 100644
index d106d06..0000000
--- a/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_dialog_browsertest.cc
+++ /dev/null
@@ -1,136 +0,0 @@
-// Copyright 2020 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/crostini_upgrader/crostini_upgrader_dialog.h"
-
-#include "base/feature_list.h"
-#include "base/metrics/histogram_base.h"
-#include "base/run_loop.h"
-#include "base/test/metrics/histogram_tester.h"
-#include "chrome/browser/chromeos/crostini/crostini_manager.h"
-#include "chrome/browser/chromeos/crostini/crostini_test_helper.h"
-#include "chrome/browser/chromeos/crostini/crostini_util.h"
-#include "chrome/browser/chromeos/guest_os/guest_os_registry_service.h"
-#include "chrome/browser/chromeos/guest_os/guest_os_registry_service_factory.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/views/crostini/crostini_browser_test_util.h"
-#include "chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader.mojom.h"
-#include "chrome/common/webui_url_constants.h"
-#include "chromeos/dbus/cicerone/cicerone_service.pb.h"
-#include "content/public/browser/web_ui.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-constexpr char kDesktopFileId[] = "test_app";
-constexpr int kDisplayId = 0;
-
-class CrostiniUpgraderDialogBrowserTest : public CrostiniDialogBrowserTest {
- public:
-  CrostiniUpgraderDialogBrowserTest()
-      : CrostiniDialogBrowserTest(true /*register_termina*/),
-        app_id_(crostini::CrostiniTestHelper::GenerateAppId(kDesktopFileId)) {}
-
-  // DialogBrowserTest:
-  void ShowUi(const std::string& name) override {
-    chromeos::CrostiniUpgraderDialog::Show(base::DoNothing(), false);
-  }
-
-  chromeos::CrostiniUpgraderDialog* GetCrostiniUpgraderDialog() {
-    auto url = GURL{chrome::kChromeUICrostiniUpgraderUrl};
-    return reinterpret_cast<chromeos::CrostiniUpgraderDialog*>(
-        chromeos::SystemWebDialogDelegate::FindInstance(url.spec()));
-  }
-
-  void SafelyCloseDialog() {
-    auto* upgrader_dialog = GetCrostiniUpgraderDialog();
-
-    // Make sure the WebUI has launches sufficiently. Closing immediately would
-    // miss breakages in the underlying plumbing.
-    auto* web_contents = upgrader_dialog->GetWebUIForTest()->GetWebContents();
-    WaitForLoadFinished(web_contents);
-
-    // Now there should be enough WebUI hooked up to close properly.
-    base::RunLoop run_loop;
-    upgrader_dialog->SetDeletionClosureForTesting(run_loop.QuitClosure());
-    upgrader_dialog->Close();
-    run_loop.Run();
-  }
-
-  void ExpectDialog() {
-    // A new Widget was created in ShowUi() or since the last VerifyUi().
-    EXPECT_TRUE(crostini_manager()->GetCrostiniDialogStatus(
-        crostini::DialogType::UPGRADER));
-
-    EXPECT_NE(nullptr, GetCrostiniUpgraderDialog());
-  }
-
-  void ExpectNoDialog() {
-    // No new Widget was created in ShowUi() or since the last VerifyUi().
-    EXPECT_FALSE(crostini_manager()->GetCrostiniDialogStatus(
-        crostini::DialogType::UPGRADER));
-    // Our dialog has really been deleted.
-    EXPECT_EQ(nullptr, GetCrostiniUpgraderDialog());
-  }
-
-  void RegisterApp() {
-    vm_tools::apps::ApplicationList app_list =
-        crostini::CrostiniTestHelper::BasicAppList(
-            kDesktopFileId, crostini::kCrostiniDefaultVmName,
-            crostini::kCrostiniDefaultContainerName);
-    guest_os::GuestOsRegistryServiceFactory::GetForProfile(browser()->profile())
-        ->UpdateApplicationList(app_list);
-  }
-
-  void DowngradeOSRelease() {
-    vm_tools::cicerone::OsRelease os_release;
-    os_release.set_id("debian");
-    os_release.set_version_id("9");
-    auto container_id = crostini::DefaultContainerId();
-    crostini_manager()->SetContainerOsRelease(
-        container_id.vm_name, container_id.container_name, os_release);
-  }
-
-  const std::string& app_id() const { return app_id_; }
-
-  crostini::CrostiniManager* crostini_manager() {
-    return crostini::CrostiniManager::GetForProfile(browser()->profile());
-  }
-
- private:
-  std::string app_id_;
-
-  DISALLOW_COPY_AND_ASSIGN(CrostiniUpgraderDialogBrowserTest);
-};
-
-IN_PROC_BROWSER_TEST_F(CrostiniUpgraderDialogBrowserTest,
-                       NoDialogOnNormalStartup) {
-  base::HistogramTester histogram_tester;
-  RegisterApp();
-
-  crostini::LaunchCrostiniApp(browser()->profile(), app_id(), kDisplayId);
-  ExpectNoDialog();
-}
-
-IN_PROC_BROWSER_TEST_F(CrostiniUpgraderDialogBrowserTest, ShowsOnAppLaunch) {
-  base::HistogramTester histogram_tester;
-
-  DowngradeOSRelease();
-  RegisterApp();
-
-  crostini::LaunchCrostiniApp(browser()->profile(), app_id(), kDisplayId);
-  ExpectDialog();
-
-  SafelyCloseDialog();
-  ExpectNoDialog();
-
-  // Once only - second time we launch an app, the dialog should not appear.
-  crostini::LaunchCrostiniApp(browser()->profile(), app_id(), kDisplayId);
-  ExpectNoDialog();
-
-  histogram_tester.ExpectUniqueSample(
-      crostini::kUpgradeDialogEventHistogram,
-      static_cast<base::HistogramBase::Sample>(
-          crostini::UpgradeDialogEvent::kDialogShown),
-      1);
-}
diff --git a/chrome/browser/ui/webui/print_preview/local_printer_handler_default.cc b/chrome/browser/ui/webui/print_preview/local_printer_handler_default.cc
index 876b308..146d3b6 100644
--- a/chrome/browser/ui/webui/print_preview/local_printer_handler_default.cc
+++ b/chrome/browser/ui/webui/print_preview/local_printer_handler_default.cc
@@ -11,45 +11,62 @@
 #include "base/logging.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/ref_counted_memory.h"
+#include "base/task/post_task.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
-#include "base/threading/scoped_blocking_call.h"
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/ui/webui/print_preview/print_preview_utils.h"
 #include "components/printing/browser/printer_capabilities.h"
+#include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
-#include "printing/backend/print_backend.h"
 
 #if defined(OS_MACOSX)
 #include "components/printing/browser/features.h"
 #include "components/printing/browser/printer_capabilities_mac.h"
 #endif
 
+#if defined(OS_WIN)
+#include "base/threading/thread_restrictions.h"
+#endif
+
 namespace printing {
 
 namespace {
 
 scoped_refptr<base::TaskRunner> CreatePrinterHandlerTaskRunner() {
   // USER_VISIBLE because the result is displayed in the print preview dialog.
+#if !defined(OS_WIN)
   static constexpr base::TaskTraits kTraits = {
       base::MayBlock(), base::TaskPriority::USER_VISIBLE};
+#endif
 
 #if defined(USE_CUPS)
   // CUPS is thread safe.
   return base::ThreadPool::CreateTaskRunner(kTraits);
 #elif defined(OS_WIN)
-  // Windows drivers are likely not thread-safe.
-  return base::ThreadPool::CreateSingleThreadTaskRunner(kTraits);
+  // Windows drivers are likely not thread-safe and need to be accessed on the
+  // UI thread.
+  return base::CreateSingleThreadTaskRunner({content::BrowserThread::UI,
+                                             base::MayBlock(),
+                                             base::TaskPriority::USER_VISIBLE});
 #else
   // Be conservative on unsupported platforms.
   return base::ThreadPool::CreateSingleThreadTaskRunner(kTraits);
 #endif
 }
 
-PrinterList EnumeratePrintersAsync(const std::string& locale) {
-  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
-                                                base::BlockingType::MAY_BLOCK);
+}  // namespace
+
+// static
+PrinterList LocalPrinterHandlerDefault::EnumeratePrintersAsync(
+    const std::string& locale) {
+#if defined(OS_WIN)
+  // Blocking is needed here because Windows printer drivers are oftentimes
+  // not thread-safe and have to be accessed on the UI thread.
+  base::ScopedAllowBlocking allow_blocking;
+#endif
+
   scoped_refptr<PrintBackend> print_backend(
       PrintBackend::CreateInstance(nullptr, locale));
 
@@ -58,16 +75,22 @@
   return printer_list;
 }
 
-base::Value FetchCapabilitiesAsync(const std::string& device_name,
-                                   const std::string& locale) {
+// static
+base::Value LocalPrinterHandlerDefault::FetchCapabilitiesAsync(
+    const std::string& device_name,
+    const std::string& locale) {
   PrinterSemanticCapsAndDefaults::Papers user_defined_papers;
 #if defined(OS_MACOSX)
   if (base::FeatureList::IsEnabled(features::kEnableCustomMacPaperSizes))
     user_defined_papers = GetMacCustomPaperSizes();
 #endif
 
-  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
-                                                base::BlockingType::MAY_BLOCK);
+#if defined(OS_WIN)
+  // Blocking is needed here because Windows printer drivers are oftentimes
+  // not thread-safe and have to be accessed on the UI thread.
+  base::ScopedAllowBlocking allow_blocking;
+#endif
+
   scoped_refptr<PrintBackend> print_backend(
       PrintBackend::CreateInstance(nullptr, locale));
 
@@ -84,9 +107,15 @@
       /*has_secure_protocol=*/false, print_backend);
 }
 
-std::string GetDefaultPrinterAsync(const std::string& locale) {
-  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
-                                                base::BlockingType::MAY_BLOCK);
+// static
+std::string LocalPrinterHandlerDefault::GetDefaultPrinterAsync(
+    const std::string& locale) {
+#if defined(OS_WIN)
+  // Blocking is needed here because Windows printer drivers are oftentimes
+  // not thread-safe and have to be accessed on the UI thread.
+  base::ScopedAllowBlocking allow_blocking;
+#endif
+
   scoped_refptr<PrintBackend> print_backend(
       PrintBackend::CreateInstance(nullptr, locale));
 
@@ -95,8 +124,6 @@
   return default_printer;
 }
 
-}  // namespace
-
 LocalPrinterHandlerDefault::LocalPrinterHandlerDefault(
     content::WebContents* preview_web_contents)
     : preview_web_contents_(preview_web_contents),
diff --git a/chrome/browser/ui/webui/print_preview/local_printer_handler_default.h b/chrome/browser/ui/webui/print_preview/local_printer_handler_default.h
index 1f6d48aa..cb4a577 100644
--- a/chrome/browser/ui/webui/print_preview/local_printer_handler_default.h
+++ b/chrome/browser/ui/webui/print_preview/local_printer_handler_default.h
@@ -14,6 +14,7 @@
 #include "base/strings/string16.h"
 #include "base/values.h"
 #include "chrome/browser/ui/webui/print_preview/printer_handler.h"
+#include "printing/backend/print_backend.h"
 
 namespace base {
 class TaskRunner;
@@ -44,6 +45,11 @@
                   PrintCallback callback) override;
 
  private:
+  static PrinterList EnumeratePrintersAsync(const std::string& locale);
+  static base::Value FetchCapabilitiesAsync(const std::string& device_name,
+                                            const std::string& locale);
+  static std::string GetDefaultPrinterAsync(const std::string& locale);
+
   content::WebContents* const preview_web_contents_;
 
   // TaskRunner for blocking tasks. Threading behavior is platform-specific.
diff --git a/chrome/browser/ui/webui/settings/chromeos/ambient_mode_handler.cc b/chrome/browser/ui/webui/settings/chromeos/ambient_mode_handler.cc
index e3c7e6d..55ae1bf 100644
--- a/chrome/browser/ui/webui/settings/chromeos/ambient_mode_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/ambient_mode_handler.cc
@@ -4,7 +4,9 @@
 
 #include "chrome/browser/ui/webui/settings/chromeos/ambient_mode_handler.h"
 
+#include "ash/public/cpp/ambient/photo_controller.h"
 #include "base/bind.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/values.h"
 
 namespace chromeos {
@@ -19,6 +21,16 @@
       "onAmbientModePageReady",
       base::BindRepeating(&AmbientModeHandler::HandleInitialized,
                           base::Unretained(this)));
+
+  web_ui()->RegisterMessageCallback(
+      "onTopicSourceSelectedChanged",
+      base::BindRepeating(&AmbientModeHandler::HandleTopicSourceSelectedChanged,
+                          base::Unretained(this)));
+}
+
+void AmbientModeHandler::OnJavascriptAllowed() {
+  if (topic_source_.has_value())
+    SendTopicSource();
 }
 
 void AmbientModeHandler::HandleInitialized(const base::ListValue* args) {
@@ -26,6 +38,59 @@
   CHECK(args->empty());
 
   AllowJavascript();
+  GetSettings();
+}
+
+void AmbientModeHandler::HandleTopicSourceSelectedChanged(
+    const base::ListValue* args) {
+  CHECK_EQ(args->GetSize(), 1U);
+  int topic_source;
+  CHECK(base::StringToInt(args->GetList()[0].GetString(), &topic_source));
+  // Check the |topic_source| has valid value.
+  // TODO: 0 and 1 are enum values for Google Photos and Art gallery. Will
+  // replace these values to enum after moving the enum definition to ash.
+  CHECK_GE(topic_source, 0);
+  CHECK_LE(topic_source, 1);
+
+  UpdateSettings(topic_source);
+}
+
+void AmbientModeHandler::GetSettings() {
+  ash::PhotoController::Get()->GetSettings(base::BindOnce(
+      &AmbientModeHandler::OnGetSettings, weak_factory_.GetWeakPtr()));
+}
+
+void AmbientModeHandler::OnGetSettings(
+    const base::Optional<int>& topic_source) {
+  if (!topic_source.has_value()) {
+    // TODO(b/152921891): Retry a small fixed number of times, then only retry
+    // when user confirms in the error message dialog.
+    return;
+  }
+
+  topic_source_ = topic_source;
+  if (!IsJavascriptAllowed())
+    return;
+
+  SendTopicSource();
+}
+
+void AmbientModeHandler::SendTopicSource() {
+  FireWebUIListener("topic-source-changed", base::Value(topic_source_.value()));
+}
+
+void AmbientModeHandler::UpdateSettings(int topic_source) {
+  ash::PhotoController::Get()->UpdateSettings(
+      topic_source, base::BindOnce(&AmbientModeHandler::OnUpdateSettings,
+                                   weak_factory_.GetWeakPtr(), topic_source));
+}
+
+void AmbientModeHandler::OnUpdateSettings(int topic_source, bool success) {
+  if (success)
+    return;
+
+  // TODO(b/152921891): Retry a small fixed number of times, then only retry
+  // when user confirms in the error message dialog.
 }
 
 }  // namespace settings
diff --git a/chrome/browser/ui/webui/settings/chromeos/ambient_mode_handler.h b/chrome/browser/ui/webui/settings/chromeos/ambient_mode_handler.h
index a52b109..c5afc74 100644
--- a/chrome/browser/ui/webui/settings/chromeos/ambient_mode_handler.h
+++ b/chrome/browser/ui/webui/settings/chromeos/ambient_mode_handler.h
@@ -5,6 +5,10 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_AMBIENT_MODE_HANDLER_H_
 #define CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_AMBIENT_MODE_HANDLER_H_
 
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
 #include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h"
 
 namespace base {
@@ -25,12 +29,37 @@
 
   // settings::SettingsPageUIHandler:
   void RegisterMessages() override;
-  void OnJavascriptAllowed() override {}
+  void OnJavascriptAllowed() override;
   void OnJavascriptDisallowed() override {}
 
  private:
   // WebUI call to signal js side is ready.
   void HandleInitialized(const base::ListValue* args);
+
+  // WebUI call to sync topic source with server.
+  void HandleTopicSourceSelectedChanged(const base::ListValue* args);
+
+  // Retrieve the initial settings from server.
+  void GetSettings();
+
+  // Called when the initial settings is retrieved.
+  void OnGetSettings(const base::Optional<int>& topic_source);
+
+  // Send the "topic-source-changed" WebUIListener event when the initial
+  // settings is retrieved.
+  void SendTopicSource();
+
+  // Update the selected topic source to server.
+  void UpdateSettings(int topic_source);
+
+  // Called when the settings is updated.
+  // |topic_source| is the value to retry if the update was failed.
+  void OnUpdateSettings(int topic_source, bool success);
+
+  // The topic source, i.e. from which category the photos will be displayed.
+  base::Optional<int> topic_source_;
+
+  base::WeakPtrFactory<AmbientModeHandler> weak_factory_{this};
 };
 
 }  // namespace settings
diff --git a/chrome/browser/ui/webui/settings/safety_check_handler.cc b/chrome/browser/ui/webui/settings/safety_check_handler.cc
index 0f10d6b..7e5487e 100644
--- a/chrome/browser/ui/webui/settings/safety_check_handler.cc
+++ b/chrome/browser/ui/webui/settings/safety_check_handler.cc
@@ -368,37 +368,39 @@
     Compromised compromised,
     Done done,
     Total total) {
+  const base::string16 short_product_name =
+      l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME);
   switch (status) {
     case PasswordsStatus::kChecking: {
       // Unable to get progress for some reason.
       if (total.value() == 0) {
         return l10n_util::GetStringUTF16(IDS_SETTINGS_SAFETY_CHECK_RUNNING);
       }
-      return l10n_util::GetStringFUTF16(
-          IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_PROGRESS,
-          base::FormatNumber(done.value()), base::FormatNumber(total.value()));
+      return l10n_util::GetStringFUTF16(IDS_SETTINGS_CHECK_PASSWORDS_PROGRESS,
+                                        base::FormatNumber(done.value()),
+                                        base::FormatNumber(total.value()));
     }
     case PasswordsStatus::kSafe:
-      return l10n_util::GetStringUTF16(
-          IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_SAFE);
+      return l10n_util::GetPluralStringFUTF16(
+          IDS_SETTINGS_COMPROMISED_PASSWORDS_COUNT, 0);
     case PasswordsStatus::kCompromisedExist:
       return l10n_util::GetPluralStringFUTF16(
-          IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_COMPROMISED, compromised.value());
+          IDS_SETTINGS_COMPROMISED_PASSWORDS_COUNT, compromised.value());
     case PasswordsStatus::kOffline:
-      return l10n_util::GetStringUTF16(
-          IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_OFFLINE);
+      return l10n_util::GetStringFUTF16(
+          IDS_SETTINGS_CHECK_PASSWORDS_ERROR_OFFLINE, short_product_name);
     case PasswordsStatus::kNoPasswords:
-      return l10n_util::GetStringUTF16(
-          IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_NO_PASSWORDS);
+      return l10n_util::GetStringFUTF16(
+          IDS_SETTINGS_CHECK_PASSWORDS_ERROR_NO_PASSWORDS, short_product_name);
     case PasswordsStatus::kSignedOut:
       return l10n_util::GetStringUTF16(
           IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_SIGNED_OUT);
     case PasswordsStatus::kQuotaLimit:
-      return l10n_util::GetStringUTF16(
-          IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_QUOTA_LIMIT);
+      return l10n_util::GetStringFUTF16(
+          IDS_SETTINGS_CHECK_PASSWORDS_ERROR_QUOTA_LIMIT, short_product_name);
     case PasswordsStatus::kError:
-      return l10n_util::GetStringUTF16(
-          IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_ERROR);
+      return l10n_util::GetStringFUTF16(
+          IDS_SETTINGS_CHECK_PASSWORDS_ERROR_GENERIC, short_product_name);
   }
 }
 
diff --git a/chrome/browser/ui/webui/settings/safety_check_handler_unittest.cc b/chrome/browser/ui/webui/settings/safety_check_handler_unittest.cc
index 585fdd9..af7dbf2 100644
--- a/chrome/browser/ui/webui/settings/safety_check_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/safety_check_handler_unittest.cc
@@ -615,9 +615,9 @@
           kPasswords,
           static_cast<int>(SafetyCheckHandler::PasswordsStatus::kSignedOut));
   ASSERT_TRUE(event3);
-  VerifyDisplayString(
-      event3,
-      "Browser can't check your passwords because you're not signed in");
+  VerifyDisplayString(event3,
+                      "Browser can't check your passwords because you're not "
+                      "signed in");
   histogram_tester_.ExpectBucketCount(
       "Settings.SafetyCheck.PasswordsResult",
       SafetyCheckHandler::PasswordsStatus::kSignedOut, 1);
@@ -730,8 +730,7 @@
           kPasswords,
           static_cast<int>(SafetyCheckHandler::PasswordsStatus::kError));
   ASSERT_TRUE(event);
-  VerifyDisplayString(event,
-                      "Browser can't check your passwords. Try again later.");
+  VerifyDisplayString(event, "Browser can't check your passwords.");
   histogram_tester_.ExpectBucketCount(
       "Settings.SafetyCheck.PasswordsResult",
       SafetyCheckHandler::PasswordsStatus::kError, 1);
@@ -767,7 +766,9 @@
           kPasswords,
           static_cast<int>(SafetyCheckHandler::PasswordsStatus::kNoPasswords));
   EXPECT_TRUE(event);
-  VerifyDisplayString(event, "No saved passwords");
+  VerifyDisplayString(event,
+                      "No saved passwords. Chrome can check your passwords "
+                      "when you save them.");
   histogram_tester_.ExpectBucketCount(
       "Settings.SafetyCheck.PasswordsResult",
       SafetyCheckHandler::PasswordsStatus::kNoPasswords, 1);
@@ -789,7 +790,7 @@
           kPasswords,
           static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking));
   EXPECT_TRUE(event);
-  VerifyDisplayString(event, base::UTF8ToUTF16("1/3 passwords checked…"));
+  VerifyDisplayString(event, base::UTF8ToUTF16("Checking passwords (1 of 3)…"));
 
   test_passwords_delegate_.SetProgress(2, 3);
   static_cast<password_manager::BulkLeakCheckService::Observer*>(
@@ -800,7 +801,8 @@
           kPasswords,
           static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking));
   EXPECT_TRUE(event2);
-  VerifyDisplayString(event2, base::UTF8ToUTF16("2/3 passwords checked…"));
+  VerifyDisplayString(event2,
+                      base::UTF8ToUTF16("Checking passwords (2 of 3)…"));
 
   // Final update comes after status change, so no new progress message should
   // be present.
@@ -816,7 +818,8 @@
           static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking));
   EXPECT_TRUE(event3);
   // Still 2/3 event.
-  VerifyDisplayString(event3, base::UTF8ToUTF16("2/3 passwords checked…"));
+  VerifyDisplayString(event3,
+                      base::UTF8ToUTF16("Checking passwords (2 of 3)…"));
 }
 
 TEST_F(SafetyCheckHandlerTest, CheckExtensions_NoExtensions) {
diff --git a/chrome/browser/util/android/java/src/org/chromium/chrome/browser/util/VoiceRecognitionUtilTest.java b/chrome/browser/util/android/java/src/org/chromium/chrome/browser/util/VoiceRecognitionUtilTest.java
index 973332e..c591ba2 100644
--- a/chrome/browser/util/android/java/src/org/chromium/chrome/browser/util/VoiceRecognitionUtilTest.java
+++ b/chrome/browser/util/android/java/src/org/chromium/chrome/browser/util/VoiceRecognitionUtilTest.java
@@ -42,7 +42,7 @@
 
     @After
     public void tearDown() {
-        AccountManagerFacadeProvider.resetAccountManagerFacadeForTests();
+        AccountManagerFacadeProvider.resetInstanceForTests();
     }
 
     private static class IntentTestPackageManager extends MockPackageManager {
diff --git a/chrome/browser/vr/webxr_permission_context.cc b/chrome/browser/vr/webxr_permission_context.cc
index 572cc65..293d0c4 100644
--- a/chrome/browser/vr/webxr_permission_context.cc
+++ b/chrome/browser/vr/webxr_permission_context.cc
@@ -9,6 +9,7 @@
 
 #if defined(OS_ANDROID)
 #include "chrome/browser/permissions/permission_update_infobar_delegate_android.h"
+#include "components/permissions/android/android_permission_util.h"
 #include "components/permissions/permission_request_id.h"
 #include "content/public/browser/web_contents.h"
 #endif
@@ -81,25 +82,27 @@
   // Otherwise, the user granted permission to use AR, so now we need to check
   // if we need to prompt for android system permissions.
   std::vector<ContentSettingsType> permission_type = {content_settings_type_};
-  ShowPermissionInfoBarState should_show =
-      PermissionUpdateInfoBarDelegate::ShouldShowPermissionInfoBar(
-          web_contents, permission_type);
-  switch (should_show) {
-    case ShowPermissionInfoBarState::NO_NEED_TO_SHOW_PERMISSION_INFOBAR:
+  permissions::PermissionRepromptState reprompt_state =
+      permissions::ShouldRepromptUserForPermissions(web_contents,
+                                                    permission_type);
+  switch (reprompt_state) {
+    case permissions::PermissionRepromptState::kNoNeed:
       // We have already returned if permission was denied by the user, and this
       // indicates that we have all the OS permissions we need.
       OnAndroidPermissionDecided(id, requesting_origin, embedding_origin,
                                  std::move(callback),
                                  true /*permission_granted*/);
       return;
-    case ShowPermissionInfoBarState::CANNOT_SHOW_PERMISSION_INFOBAR:
+
+    case permissions::PermissionRepromptState::kCannotShow:
       // If we cannot show the info bar, then we have to assume we don't have
       // the permissions we need.
       OnAndroidPermissionDecided(id, requesting_origin, embedding_origin,
                                  std::move(callback),
                                  false /*permission_granted*/);
       return;
-    case ShowPermissionInfoBarState::SHOW_PERMISSION_INFOBAR:
+
+    case permissions::PermissionRepromptState::kShow:
       // Otherwise, prompt the user that we need additional permissions.
       PermissionUpdateInfoBarDelegate::Create(
           web_contents, permission_type,
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 6f1d458..2b74861 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -258,11 +258,6 @@
 const base::Feature kDesktopPWAsSharedStoreService{
     "DesktopPWAsSharedStoreService", base::FEATURE_ENABLED_BY_DEFAULT};
 
-// Enables Chrome to query HTTPSSVC records from DNS over DoH. Returned HTTPSSVC
-// records may cause us to upgrade the URL to HTTPS and/or to attempt QUIC.
-const base::Feature kDnsHttpssvc{"DnsHttpssvc",
-                                 base::FEATURE_DISABLED_BY_DEFAULT};
-
 // Enable DNS over HTTPS (DoH).
 const base::Feature kDnsOverHttps{"DnsOverHttps",
                                   base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index e26dd84d..d8623c4 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -160,9 +160,6 @@
 extern const base::FeatureParam<std::string>
     kDnsOverHttpsDisabledProvidersParam;
 
-COMPONENT_EXPORT(CHROME_FEATURES)
-extern const base::Feature kDnsHttpssvc;
-
 #if defined(OS_ANDROID)
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kDownloadsLocationChange;
diff --git a/chrome/common/webui_url_constants.cc b/chrome/common/webui_url_constants.cc
index 77b97f0..a23ab9e4 100644
--- a/chrome/common/webui_url_constants.cc
+++ b/chrome/common/webui_url_constants.cc
@@ -430,6 +430,7 @@
 const char kNetworksSubPage[] = "networks";
 const char kOsAccessibilitySubPage[] = "osAccessibility";
 const char kOsPrintingSubPage[] = "osPrinting";
+const char kOsPrivacySubPage[] = "osPrivacy";
 const char kOsResetSubPage[] = "osReset";
 const char kOsSearchSubPage[] = "osSearch";
 const char kPluginVmDetailsSubPage[] = "pluginVm/details";
@@ -490,6 +491,7 @@
       kOsLanguagesDetailsSubPage,
       kOsLanguagesInputMethodsSubPage,
       kOsPrintingSubPage,
+      kOsPrivacySubPage,
       kOsResetSubPage,
       kOsSearchSubPage,
       kPointerOverlaySubPage,
diff --git a/chrome/common/webui_url_constants.h b/chrome/common/webui_url_constants.h
index a8d1d76..be10b4104 100644
--- a/chrome/common/webui_url_constants.h
+++ b/chrome/common/webui_url_constants.h
@@ -390,6 +390,7 @@
 extern const char kOsLanguagesDetailsSubPage[];
 extern const char kOsLanguagesInputMethodsSubPage[];
 extern const char kOsPrintingSubPage[];
+extern const char kOsPrivacySubPage[];
 extern const char kOsResetSubPage[];
 extern const char kOsSearchSubPage[];
 extern const char kPluginVmDetailsSubPage[];
diff --git a/chrome/credential_provider/gaiacp/associated_user_validator.cc b/chrome/credential_provider/gaiacp/associated_user_validator.cc
index 69f51a0..4ae597e 100644
--- a/chrome/credential_provider/gaiacp/associated_user_validator.cc
+++ b/chrome/credential_provider/gaiacp/associated_user_validator.cc
@@ -603,25 +603,6 @@
   return validity_it->second->is_valid;
 }
 
-bool AssociatedUserValidator::IsAuthEnforcedOnAssociatedUsers() {
-  std::map<base::string16, UserTokenHandleInfo> sids_to_handle_info;
-  HRESULT hr = GetUserTokenHandles(&sids_to_handle_info);
-  if (FAILED(hr)) {
-    LOGFN(ERROR) << "GetUserTokenHandles hr=" << putHR(hr);
-    return hr;
-  }
-
-  for (const auto& sid_to_association : sids_to_handle_info) {
-    const base::string16& sid = sid_to_association.first;
-    // Return true even if one of the associated user sid
-    // has an auth enforced.
-    if (IsAuthEnforcedForUser(sid)) {
-      return true;
-    }
-  }
-  return false;
-}
-
 void AssociatedUserValidator::BlockDenyAccessUpdate() {
   base::AutoLock locker(validator_lock_);
   ++block_deny_access_update_;
diff --git a/chrome/credential_provider/gaiacp/associated_user_validator_unittests.cc b/chrome/credential_provider/gaiacp/associated_user_validator_unittests.cc
index 53fe802..171836b 100644
--- a/chrome/credential_provider/gaiacp/associated_user_validator_unittests.cc
+++ b/chrome/credential_provider/gaiacp/associated_user_validator_unittests.cc
@@ -144,7 +144,6 @@
   EXPECT_FALSE(validator.IsAuthEnforcedForUser(OLE2CW(sid_bad)));
   EXPECT_FALSE(validator.IsAuthEnforcedForUser(OLE2CW(sid_no_gaia_id)));
   EXPECT_TRUE(validator.IsAuthEnforcedForUser(OLE2CW(sid_no_token_handle)));
-  EXPECT_TRUE(validator.IsAuthEnforcedOnAssociatedUsers());
 
   // Expect deleted user and user with no gaia id to be deleted.
   EXPECT_NE(ERROR_SUCCESS, key.OpenKey(OLE2CW(sid_bad), KEY_READ));
@@ -165,7 +164,6 @@
   // If there is no associated user then all token handles are valid.
   EXPECT_FALSE(
       validator.IsAuthEnforcedForUser(GetNewSidString(fake_os_user_manager())));
-  EXPECT_FALSE(validator.IsAuthEnforcedOnAssociatedUsers());
   EXPECT_EQ(0u, fake_http_url_fetcher_factory()->requests_created());
 }
 
@@ -187,7 +185,6 @@
   validator.StartRefreshingTokenHandleValidity();
 
   EXPECT_FALSE(validator.IsAuthEnforcedForUser(OLE2W(sid)));
-  EXPECT_FALSE(validator.IsAuthEnforcedOnAssociatedUsers());
   EXPECT_EQ(1u, fake_http_url_fetcher_factory()->requests_created());
 }
 
@@ -207,7 +204,6 @@
   validator.StartRefreshingTokenHandleValidity();
 
   EXPECT_TRUE(validator.IsAuthEnforcedForUser(OLE2W(sid)));
-  EXPECT_TRUE(validator.IsAuthEnforcedOnAssociatedUsers());
   EXPECT_EQ(1u, fake_http_url_fetcher_factory()->requests_created());
 }
 
@@ -223,7 +219,6 @@
 
   validator.StartRefreshingTokenHandleValidity();
   EXPECT_FALSE(validator.IsAuthEnforcedForUser(OLE2W(sid)));
-  EXPECT_FALSE(validator.IsAuthEnforcedOnAssociatedUsers());
   EXPECT_EQ(0u, fake_http_url_fetcher_factory()->requests_created());
 }
 
@@ -244,7 +239,6 @@
   validator.StartRefreshingTokenHandleValidity();
 
   EXPECT_FALSE(validator.IsAuthEnforcedForUser(OLE2W(sid)));
-  EXPECT_FALSE(validator.IsAuthEnforcedOnAssociatedUsers());
   EXPECT_EQ(1u, fake_http_url_fetcher_factory()->requests_created());
 
   http_fetcher_event.Signal();
@@ -269,7 +263,6 @@
 
   EXPECT_FALSE(validator.IsAuthEnforcedForUser(OLE2W(sid)));
   EXPECT_FALSE(validator.IsAuthEnforcedForUser(OLE2W(sid)));
-  EXPECT_FALSE(validator.IsAuthEnforcedOnAssociatedUsers());
   EXPECT_EQ(1u, fake_http_url_fetcher_factory()->requests_created());
 }
 
@@ -343,7 +336,6 @@
   EXPECT_TRUE(validator.IsAuthEnforcedForUser(OLE2W(sid)));
   EXPECT_TRUE(validator.DenySigninForUsersWithInvalidTokenHandles(CPUS_LOGON,
                                                                   reauth_sids));
-  EXPECT_TRUE(validator.IsAuthEnforcedOnAssociatedUsers());
 }
 
 // Donot deny user access even when the gaia handle is invalidated for a
@@ -372,7 +364,6 @@
   EXPECT_TRUE(validator.IsAuthEnforcedForUser(OLE2W(sid)));
   EXPECT_FALSE(validator.DenySigninForUsersWithInvalidTokenHandles(
       CPUS_LOGON, reauth_sids));
-  EXPECT_TRUE(validator.IsAuthEnforcedOnAssociatedUsers());
 }
 
 // Clear the UserProperty from registry for those sids which doesn't
@@ -564,7 +555,6 @@
   EXPECT_EQ(should_user_be_blocked,
             validator.IsUserAccessBlockedForTesting(OLE2W(sid)));
   EXPECT_EQ(is_get_auth_enforced, validator.IsAuthEnforcedForUser(OLE2W(sid)));
-  EXPECT_EQ(is_get_auth_enforced, validator.IsAuthEnforcedOnAssociatedUsers());
 
   // Unlock the user.
   validator.AllowSigninForUsersWithInvalidTokenHandles();
@@ -616,7 +606,6 @@
   validator.StartRefreshingTokenHandleValidity();
 
   EXPECT_FALSE(validator.IsAuthEnforcedForUser(OLE2W(sid)));
-  EXPECT_FALSE(validator.IsAuthEnforcedOnAssociatedUsers());
 
   // Make the next token fetch result invalid.
   fake_http_url_fetcher_factory()->SetFakeResponse(
@@ -626,7 +615,6 @@
   // If the lifetime of the validity has not expired, even if the token is
   // invalid, no new fetch will be performed yet.
   EXPECT_FALSE(validator.IsAuthEnforcedForUser(OLE2W(sid)));
-  EXPECT_FALSE(validator.IsAuthEnforcedOnAssociatedUsers());
   EXPECT_EQ(1u, fake_http_url_fetcher_factory()->requests_created());
 
   // Advance the time so that a new fetch will be done and retrieve the
@@ -635,7 +623,6 @@
       AssociatedUserValidator::kTokenHandleValidityLifetime +
       base::TimeDelta::FromMilliseconds(1);
   EXPECT_TRUE(validator.IsAuthEnforcedForUser(OLE2W(sid)));
-  EXPECT_TRUE(validator.IsAuthEnforcedOnAssociatedUsers());
   EXPECT_EQ(2u, fake_http_url_fetcher_factory()->requests_created());
 }
 
@@ -664,7 +651,6 @@
   validator.StartRefreshingTokenHandleValidity();
 
   EXPECT_TRUE(validator.IsAuthEnforcedForUser(OLE2W(sid)));
-  EXPECT_TRUE(validator.IsAuthEnforcedOnAssociatedUsers());
 }
 
 TEST_F(AssociatedUserValidatorTest, ValidTokenHandle_PresentPasswordLsaData) {
@@ -694,7 +680,6 @@
   validator.StartRefreshingTokenHandleValidity();
 
   EXPECT_FALSE(validator.IsAuthEnforcedForUser(OLE2W(sid)));
-  EXPECT_FALSE(validator.IsAuthEnforcedOnAssociatedUsers());
 }
 
 }  // namespace testing
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_provider.cc b/chrome/credential_provider/gaiacp/gaia_credential_provider.cc
index 7b564f4c..58ec0b7 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_provider.cc
+++ b/chrome/credential_provider/gaiacp/gaia_credential_provider.cc
@@ -120,25 +120,29 @@
 class BackgroundTokenHandleUpdater {
  public:
   explicit BackgroundTokenHandleUpdater(
-      ICredentialUpdateEventsHandler* event_handler);
+      ICredentialUpdateEventsHandler* event_handler,
+      const std::vector<base::string16>* reauth_sids);
   ~BackgroundTokenHandleUpdater();
 
  private:
   static unsigned __stdcall PeriodicTokenHandleUpdate(void* param);
+  bool IsAuthEnforcedOnAssociatedUsers();
 
   // Raw pointer to the interface on CGaiaCredentialProvider that is used
   // to notify that token handle validity has changed. Any instance of this
   // class should be owned by the CGaiaCredentialProvider to ensure that
   // this pointer outlives the updater.
   ICredentialUpdateEventsHandler* event_handler_;
+  const std::vector<base::string16>* reauth_sids_;
 
   base::win::ScopedHandle token_update_thread_;
   base::WaitableEvent token_update_quit_event_;
 };
 
 BackgroundTokenHandleUpdater::BackgroundTokenHandleUpdater(
-    ICredentialUpdateEventsHandler* event_handler)
-    : event_handler_(event_handler) {
+    ICredentialUpdateEventsHandler* event_handler,
+    const std::vector<base::string16>* reauth_sids)
+    : event_handler_(event_handler), reauth_sids_(reauth_sids) {
   unsigned wait_thread_id;
   uintptr_t wait_thread =
       _beginthreadex(nullptr, 0, PeriodicTokenHandleUpdate,
@@ -158,6 +162,31 @@
   }
 }
 
+bool BackgroundTokenHandleUpdater::IsAuthEnforcedOnAssociatedUsers() {
+  std::map<base::string16, UserTokenHandleInfo> sids_to_handle_info;
+  HRESULT hr = GetUserTokenHandles(&sids_to_handle_info);
+  if (FAILED(hr)) {
+    LOGFN(ERROR) << "GetUserTokenHandles hr=" << putHR(hr);
+    return hr;
+  }
+
+  for (const auto& sid_to_association : sids_to_handle_info) {
+    const base::string16& sid = sid_to_association.first;
+    // Checks if the login UI was already refreshed due to
+    // auth enforcements on this sid.
+    if (reauth_sids_ != nullptr &&
+        (std::find(reauth_sids_->begin(), reauth_sids_->end(), sid) !=
+         reauth_sids_->end()))
+      continue;
+
+    // Return true if the associated user sid has auth enforced.
+    if (AssociatedUserValidator::Get()->IsAuthEnforcedForUser(sid)) {
+      return true;
+    }
+  }
+  return false;
+}
+
 unsigned __stdcall BackgroundTokenHandleUpdater::PeriodicTokenHandleUpdate(
     void* param) {
   BackgroundTokenHandleUpdater* updater =
@@ -173,8 +202,7 @@
     if (hr != WAIT_TIMEOUT)
       break;
 
-    bool user_access_changed =
-        AssociatedUserValidator::Get()->IsAuthEnforcedOnAssociatedUsers();
+    bool user_access_changed = updater->IsAuthEnforcedOnAssociatedUsers();
     if (user_access_changed) {
       LOGFN(VERBOSE) << "A user token handle has been invalidated. Refreshing "
                         "credentials";
@@ -381,8 +409,8 @@
   }
 
   LOGFN(VERBOSE) << "count=" << count;
+  reauth_cred_sids_.clear();
 
-  std::vector<base::string16> reauth_cred_sids;
   for (DWORD i = 0; i < count; ++i) {
     Microsoft::WRL::ComPtr<ICredentialProviderUser> user;
     hr = users->GetAt(i, &user);
@@ -466,14 +494,14 @@
 
     // Add SID to the vector to keep track of all the users that have a reauth
     // credential created.
-    reauth_cred_sids.push_back(sid);
+    reauth_cred_sids_.push_back(sid);
 
     LOGFN(VERBOSE) << "Reauth SID : " << sid;
   }
 
   // Deny sign in access for users that have a reauth credential added to them.
   AssociatedUserValidator::Get()->DenySigninForUsersWithInvalidTokenHandles(
-      cpus_, reauth_cred_sids);
+      cpus_, reauth_cred_sids_);
 
   return S_OK;
 }
@@ -702,8 +730,8 @@
   advise_context_ = context;
 
   if (AssociatedUserValidator::Get()->IsUserAccessBlockingEnforced(cpus_)) {
-    token_handle_updater_ =
-        std::make_unique<BackgroundTokenHandleUpdater>(this);
+    token_handle_updater_ = std::make_unique<BackgroundTokenHandleUpdater>(
+        this, &reauth_cred_sids_);
   }
 
   return S_OK;
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_provider.h b/chrome/credential_provider/gaiacp/gaia_credential_provider.h
index e22dd396..3bf7b60 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_provider.h
+++ b/chrome/credential_provider/gaiacp/gaia_credential_provider.h
@@ -259,6 +259,7 @@
   CredentialCreatorFn anonymous_cred_creator_ = nullptr;
   CredentialCreatorFn other_user_cred_creator_ = nullptr;
   CredentialCreatorFn reauth_cred_creator_ = nullptr;
+  std::vector<base::string16> reauth_cred_sids_;
 };
 
 // OBJECT_ENTRY_AUTO() contains an extra semicolon.
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index cf539ef..88089d8 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -688,6 +688,7 @@
       "//components/sync:test_support_model",
       "//components/sync_device_info:test_support",
       "//components/translate/core/common",
+      "//components/webrtc",
       "//content/test:test_support",
       "//crypto:platform",
       "//crypto:test_support",
@@ -2425,7 +2426,6 @@
         "../browser/ui/web_applications/web_app_guest_session_browsertest_chromeos.cc",
         "../browser/ui/webui/chromeos/add_supervision/add_supervision_metrics_recorder_browsertest.cc",
         "../browser/ui/webui/chromeos/add_supervision/add_supervision_ui_browsertest.cc",
-        "../browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_dialog_browsertest.cc",
         "../browser/ui/webui/chromeos/login/discover/discover_browser_test.cc",
         "../browser/ui/webui/chromeos/login/discover/discover_browser_test.h",
         "../browser/ui/webui/chromeos/login/discover/modules/discover_module_launch_help_app_test.cc",
@@ -3158,6 +3158,7 @@
     "../browser/mac/keystone_glue_unittest.mm",
     "../browser/media/android/router/media_router_android_unittest.cc",
     "../browser/media/cast_mirroring_service_host_unittest.cc",
+    "../browser/media/feeds/media_feeds_converter_unittest.cc",
     "../browser/media/feeds/media_feeds_fetcher_unittest.cc",
     "../browser/media/feeds/media_feeds_service_unittest.cc",
     "../browser/media/history/media_history_keyed_service_unittest.cc",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/NewTabPageTestUtils.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/NewTabPageTestUtils.java
index 299a9fdc..7ce2fa7 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/NewTabPageTestUtils.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/NewTabPageTestUtils.java
@@ -19,12 +19,14 @@
 import org.chromium.chrome.browser.suggestions.tile.TileSource;
 import org.chromium.chrome.browser.suggestions.tile.TileTitleSource;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.AccountManagerFacadeProvider;
 import org.chromium.components.signin.AccountUtils;
 import org.chromium.components.signin.test.util.AccountHolder;
 import org.chromium.components.signin.test.util.FakeAccountManagerDelegate;
 import org.chromium.content_public.browser.test.util.Criteria;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.net.test.EmbeddedTestServer;
 
 import java.util.ArrayList;
@@ -97,7 +99,10 @@
     public static void setUpTestAccount() {
         FakeAccountManagerDelegate fakeAccountManager = new FakeAccountManagerDelegate(
                 FakeAccountManagerDelegate.ENABLE_PROFILE_DATA_SOURCE);
-        AccountManagerFacadeProvider.overrideAccountManagerFacadeForTests(fakeAccountManager);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            AccountManagerFacadeProvider.setInstanceForTests(
+                    new AccountManagerFacade(fakeAccountManager));
+        });
         Account account = AccountUtils.createAccountFromName("test@gmail.com");
         fakeAccountManager.addAccountHolderExplicitly(new AccountHolder.Builder(account).build());
         assertFalse(AccountManagerFacadeProvider.getInstance().isUpdatePending().get());
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/signin/SigninTestUtil.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/signin/SigninTestUtil.java
index b554fe7..907885e 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/signin/SigninTestUtil.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/signin/SigninTestUtil.java
@@ -18,6 +18,7 @@
 import org.chromium.chrome.browser.signin.SigninManager;
 import org.chromium.chrome.browser.signin.SigninPreferencesManager;
 import org.chromium.chrome.browser.sync.ProfileSyncService;
+import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.AccountManagerFacadeProvider;
 import org.chromium.components.signin.AccountTrackerService;
 import org.chromium.components.signin.AccountUtils;
@@ -53,7 +54,10 @@
     public static void setUpAuthForTest() {
         sAccountManager = new FakeAccountManagerDelegate(
                 FakeAccountManagerDelegate.DISABLE_PROFILE_DATA_SOURCE);
-        AccountManagerFacadeProvider.overrideAccountManagerFacadeForTests(sAccountManager);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            AccountManagerFacadeProvider.setInstanceForTests(
+                    new AccountManagerFacade(sAccountManager));
+        });
     }
 
     /**
diff --git a/chrome/test/base/testing_profile.cc b/chrome/test/base/testing_profile.cc
index c2586aa..07c36071 100644
--- a/chrome/test/base/testing_profile.cc
+++ b/chrome/test/base/testing_profile.cc
@@ -778,8 +778,12 @@
   DCHECK(otr_profile_id == OTRProfileID::PrimaryID());
   if (IsOffTheRecord())
     return this;
-  if (!incognito_profile_)
-    TestingProfile::Builder().BuildIncognito(this);
+  if (!incognito_profile_) {
+    TestingProfile::Builder builder;
+    if (IsGuestSession())
+      builder.SetGuestSession();
+    builder.BuildIncognito(this);
+  }
   return incognito_profile_.get();
 }
 
diff --git a/chrome/test/data/android/render_tests/NewTabPageTest.expandable_header_collapsed.Nexus_5X-23.png.sha1 b/chrome/test/data/android/render_tests/NewTabPageTest.expandable_header_collapsed.Nexus_5X-23.png.sha1
new file mode 100644
index 0000000..8b5de9b
--- /dev/null
+++ b/chrome/test/data/android/render_tests/NewTabPageTest.expandable_header_collapsed.Nexus_5X-23.png.sha1
@@ -0,0 +1 @@
+e56d2a4dbd8a8a78c5c6cb8ea5428588cffe1332
\ No newline at end of file
diff --git a/chrome/test/data/android/render_tests/NewTabPageTest.expandable_header_expanded.Nexus_5X-23.png.sha1 b/chrome/test/data/android/render_tests/NewTabPageTest.expandable_header_expanded.Nexus_5X-23.png.sha1
new file mode 100644
index 0000000..6575975
--- /dev/null
+++ b/chrome/test/data/android/render_tests/NewTabPageTest.expandable_header_expanded.Nexus_5X-23.png.sha1
@@ -0,0 +1 @@
+8ef5fc962c9a6447b5fe004c299e5c2b3e4d6112
\ No newline at end of file
diff --git a/chrome/test/data/devtools/extensions/chrome_scheme/devtools.html b/chrome/test/data/devtools/extensions/chrome_scheme/devtools.html
new file mode 100644
index 0000000..dc6e791f
--- /dev/null
+++ b/chrome/test/data/devtools/extensions/chrome_scheme/devtools.html
@@ -0,0 +1,5 @@
+<html>
+<head>
+<script src="devtools.js"></script>
+</head>
+</html>
diff --git a/chrome/test/data/devtools/extensions/chrome_scheme/devtools.js b/chrome/test/data/devtools/extensions/chrome_scheme/devtools.js
new file mode 100644
index 0000000..b914d28
--- /dev/null
+++ b/chrome/test/data/devtools/extensions/chrome_scheme/devtools.js
@@ -0,0 +1,42 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+function output(msg) {
+  top.postMessage({testOutput: msg}, '*');
+}
+
+async function test() {
+  const newPageURL = 'chrome://version/';
+  const inspectedTabId = chrome.devtools.inspectedWindow.tabId;
+  chrome.tabs.update(inspectedTabId, {url: newPageURL});
+  await new Promise(resolve => chrome.tabs.onUpdated.addListener(
+      (tabId, changedProps) => {
+        if (inspectedTabId !== tabId || changedProps.url !== newPageURL)
+          return;
+        resolve();
+      }
+  ));
+  // There may be a number of inherent races between the page and the DevTools
+  // during the navigation. Let's do a number of eval in a hope of catching some
+  // of them.
+  const evalCount = 1000;
+  let callbackCount = 0;
+  const evaluateCallback = (result, exception) => {
+    if (!exception || !exception.isError) {
+      output(`FAIL: ${result || exception.value}`);
+      return;
+    }
+    if (exception.code !== 'E_FAILED') {
+      output(`FAIL: ${exception.code}`);
+      return;
+    }
+    if (++callbackCount === evalCount)
+      output('PASS');
+  };
+  for (let i = 0; i < evalCount; ++i) {
+    chrome.devtools.inspectedWindow.eval('location.href', {}, evaluateCallback);
+  }
+}
+
+test();
diff --git a/chrome/test/data/devtools/extensions/chrome_scheme/manifest.json b/chrome/test/data/devtools/extensions/chrome_scheme/manifest.json
new file mode 100644
index 0000000..a19d595
--- /dev/null
+++ b/chrome/test/data/devtools/extensions/chrome_scheme/manifest.json
@@ -0,0 +1,8 @@
+{
+   "description": "Devtools Test extension",
+   "name": "Devtools Test extension",
+   "version": "0.1",
+   "manifest_version": 2,
+   "devtools_page": "devtools.html",
+   "permissions": ["tabs"]
+}
diff --git a/chrome/test/data/webui/settings/chromeos/ambient_mode_page_test.js b/chrome/test/data/webui/settings/chromeos/ambient_mode_page_test.js
index 31def6c..1288e263 100644
--- a/chrome/test/data/webui/settings/chromeos/ambient_mode_page_test.js
+++ b/chrome/test/data/webui/settings/chromeos/ambient_mode_page_test.js
@@ -9,6 +9,7 @@
   constructor() {
     super([
       'onAmbientModePageReady',
+      'onTopicSourceSelectedChanged',
     ]);
   }
 
@@ -16,6 +17,11 @@
   onAmbientModePageReady() {
     this.methodCalled('onAmbientModePageReady');
   }
+
+  /** @override */
+  onTopicSourceSelectedChanged(selected) {
+    this.methodCalled('onTopicSourceSelectedChanged', selected);
+  }
 }
 
 suite('AmbientModeHandler', function() {
@@ -72,24 +78,11 @@
     assertEquals(enabled, enabled_toggled_twice);
   });
 
-  test('chooseTopicSource', function() {
+  test('hasTopicSourceButtons', function() {
     const topicSourceRadioGroup = page.$$('#topicSourceRadioGroup');
 
-    // The radio group's state is set by the pref value.
-    let topicSourceValue =
-        page.getPref('settings.ambient_mode.topic_source.value');
-    assertEquals(topicSourceValue, parseFloat(topicSourceRadioGroup.selected));
-
     const radioButtons =
         topicSourceRadioGroup.querySelectorAll('cr-radio-button');
     assertEquals(2, radioButtons.length);
-
-    // Click on topic source radio button will set the pref value.
-    radioButtons.forEach(function(button, index) {
-      button.click();
-      topicSourceValue =
-          page.getPref('settings.ambient_mode.topic_source.value');
-      assertEquals(topicSourceValue, index);
-    });
   });
 });
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_page_test.js b/chrome/test/data/webui/settings/chromeos/os_settings_page_test.js
index 7ebe742..fc2e8e7 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_page_test.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_page_test.js
@@ -85,7 +85,7 @@
     await test_util.flushTasks();
 
     const sectionNames =
-        ['privacy', 'osLanguages', 'files', 'osReset', 'dateTime', 'a11y'];
+        ['osPrivacy', 'osLanguages', 'files', 'osReset', 'dateTime', 'a11y'];
 
     for (const name of sectionNames) {
       const section = settingsPage.shadowRoot.querySelector(
@@ -112,7 +112,7 @@
     }
 
     const visibleSections = [
-      'internet', 'bluetooth', 'device', 'osSearch', 'apps', 'privacy',
+      'internet', 'bluetooth', 'device', 'osSearch', 'apps', 'osPrivacy',
       'osLanguages', 'files', 'osReset', 'dateTime', 'a11y'
     ];
     for (const name of visibleSections) {
diff --git a/chrome/test/data/webui/settings/chromeos/os_sync_controls_test.js b/chrome/test/data/webui/settings/chromeos/os_sync_controls_test.js
index e31ad48..5a4308fc 100644
--- a/chrome/test/data/webui/settings/chromeos/os_sync_controls_test.js
+++ b/chrome/test/data/webui/settings/chromeos/os_sync_controls_test.js
@@ -60,6 +60,17 @@
   return getOsSyncPrefs(false);
 }
 
+// Returns a SyncStatus representing the default syncing state.
+function getDefaultSyncStatus() {
+  return {
+    disabled: false,
+    hasError: false,
+    hasUnrecoverableError: false,
+    signedIn: true,
+    statusAction: settings.StatusAction.NO_ACTION,
+  };
+}
+
 function setupWithFeatureEnabled() {
   cr.webUIListenerCallback(
       'os-sync-prefs-changed', /*featureEnabled=*/ true, getSyncAllPrefs());
@@ -74,8 +85,9 @@
 }
 
 suite('OsSyncControlsTest', function() {
-  let syncControls = null;
   let browserProxy = null;
+  let syncControls = null;
+  let syncIconContainer = null;
 
   setup(function() {
     browserProxy = new TestOsSyncBrowserProxy();
@@ -83,11 +95,14 @@
 
     PolymerTest.clearBody();
     syncControls = document.createElement('os-sync-controls');
-    syncControls.syncStatus = {hasError: false};
+    syncControls.syncStatus = getDefaultSyncStatus();
     syncControls.profileName = 'John Cena';
     syncControls.profileEmail = 'john.cena@gmail.com';
     syncControls.profileIconUrl = 'data:image/png;base64,abc123';
     document.body.appendChild(syncControls);
+
+    // Alias to help with line wrapping in test cases.
+    syncIconContainer = syncControls.$.syncIconContainer;
   });
 
   teardown(function() {
@@ -104,6 +119,47 @@
     assertEquals('data:image/png;base64,abc123', syncControls.$.avatarIcon.src);
   });
 
+  test('Status icon is visible with feature enabled', function() {
+    setupWithFeatureEnabled();
+    assertFalse(syncControls.$.syncIconContainer.hidden);
+  });
+
+  test('Status icon is hidden with feature disabled', function() {
+    setupWithFeatureDisabled();
+    assertTrue(syncControls.$.syncIconContainer.hidden);
+  });
+
+  test('Status icon with error', function() {
+    setupWithFeatureEnabled();
+    const status = getDefaultSyncStatus();
+    status.hasError = true;
+    syncControls.syncStatus = status;
+
+    assertTrue(syncIconContainer.classList.contains('sync-problem'));
+    assertTrue(!!syncControls.$$('[icon="settings:sync-problem"]'));
+  });
+
+  test('Status icon with sync paused for reauthentication', function() {
+    setupWithFeatureEnabled();
+    const status = getDefaultSyncStatus();
+    status.hasError = true;
+    status.statusAction = settings.StatusAction.REAUTHENTICATE;
+    syncControls.syncStatus = status;
+
+    assertTrue(syncIconContainer.classList.contains('sync-paused'));
+    assertTrue(!!syncControls.$$('[icon="settings:sync-disabled"]'));
+  });
+
+  test('Status icon with sync disabled', function() {
+    setupWithFeatureEnabled();
+    const status = getDefaultSyncStatus();
+    status.disabled = true;
+    syncControls.syncStatus = status;
+
+    assertTrue(syncIconContainer.classList.contains('sync-disabled'));
+    assertTrue(!!syncControls.$$('[icon="cr:sync"]'));
+  });
+
   test('Account name and email with feature enabled', function() {
     setupWithFeatureEnabled();
     assertEquals('John Cena', syncControls.$.accountTitle.textContent.trim());
diff --git a/chromecast/BUILD.gn b/chromecast/BUILD.gn
index fcfb14f..ae6c9570 100644
--- a/chromecast/BUILD.gn
+++ b/chromecast/BUILD.gn
@@ -128,7 +128,11 @@
       test_name = "cast_audio_backend_unittests"
 
       # --test-launcher-jobs=1 => so mixer service receiver can bind.
-      args = [ "--test-launcher-jobs=1" ]
+      args = [
+        "--test-launcher-jobs=1",
+        "--mixer-service-endpoint=/tmp/cast_audio_backend_unittest-mixer",
+        "--mixer-service-port=12855",
+      ]
     }
     filters += [ cast_audio_backend_unittests_filter ]
   }
diff --git a/chromecast/base/chromecast_switches.cc b/chromecast/base/chromecast_switches.cc
index 564499c..65ca06a4 100644
--- a/chromecast/base/chromecast_switches.cc
+++ b/chromecast/base/chromecast_switches.cc
@@ -185,11 +185,14 @@
 // Whether to enable detection and dispatch of a 'drag from the top' gesture.
 const char kEnableTopDragGesture[] = "enable-top-drag-gesture";
 
-// Endpoint that the mixer service listens on. On Linux/Android, this is a
-// path for a UNIX domain socket (default is /tmp/mixer-service). On other
-// platforms, this is a TCP port to listen on (on localhost) (default 12854).
+// Endpoint that the mixer service listens on. This is a path for a UNIX domain
+// socket (default is /tmp/mixer-service).
 const char kMixerServiceEndpoint[] = "mixer-service-endpoint";
 
+// TCP port that the mixer service listens on on non-Linux platforms.
+// (default 12854).
+const char kMixerServicePort[] = "mixer-service-port";
+
 extern const char kCastMemoryPressureCriticalFraction[] =
     "memory-pressure-critical-fraction";
 extern const char kCastMemoryPressureModerateFraction[] =
diff --git a/chromecast/base/chromecast_switches.h b/chromecast/base/chromecast_switches.h
index c2e4c61..7c0d34d2 100644
--- a/chromecast/base/chromecast_switches.h
+++ b/chromecast/base/chromecast_switches.h
@@ -89,6 +89,8 @@
 extern const char kCastAppBackgroundColor[];
 
 extern const char kMixerServiceEndpoint[];
+extern const char kMixerServicePort[];
+
 extern const char kCastMemoryPressureCriticalFraction[];
 extern const char kCastMemoryPressureModerateFraction[];
 
diff --git a/chromecast/media/audio/mixer_service/mixer_connection.cc b/chromecast/media/audio/mixer_service/mixer_connection.cc
index 4c11356..fc6441e 100644
--- a/chromecast/media/audio/mixer_service/mixer_connection.cc
+++ b/chromecast/media/audio/mixer_service/mixer_connection.cc
@@ -55,7 +55,7 @@
   if (path.empty()) {
     path = kDefaultUnixDomainSocketPath;
   }
-  int port = GetSwitchValueNonNegativeInt(switches::kMixerServiceEndpoint,
+  int port = GetSwitchValueNonNegativeInt(switches::kMixerServicePort,
                                           kDefaultTcpPort);
   connecting_socket_ = AudioSocketService::Connect(path, port);
 
diff --git a/chromecast/media/audio/mixer_service/receiver/receiver.cc b/chromecast/media/audio/mixer_service/receiver/receiver.cc
index c40f090..de8101d 100644
--- a/chromecast/media/audio/mixer_service/receiver/receiver.cc
+++ b/chromecast/media/audio/mixer_service/receiver/receiver.cc
@@ -128,7 +128,7 @@
     : task_runner_(base::SequencedTaskRunnerHandle::Get()),
       socket_service_(
           GetEndpoint(),
-          GetSwitchValueNonNegativeInt(switches::kMixerServiceEndpoint,
+          GetSwitchValueNonNegativeInt(switches::kMixerServicePort,
                                        mixer_service::kDefaultTcpPort),
           kMaxAcceptLoop,
           this),
diff --git a/chromeos/services/assistant/public/cpp/BUILD.gn b/chromeos/services/assistant/public/cpp/BUILD.gn
index a98a058..42051bb7 100644
--- a/chromeos/services/assistant/public/cpp/BUILD.gn
+++ b/chromeos/services/assistant/public/cpp/BUILD.gn
@@ -10,3 +10,15 @@
 
   deps = [ "//components/prefs" ]
 }
+
+source_set("interaction_subscriber") {
+  sources = [
+    "default_assistant_interaction_subscriber.cc",
+    "default_assistant_interaction_subscriber.h",
+  ]
+
+  deps = [
+    "//chromeos/services/assistant/public/mojom",
+    "//mojo/public/cpp/bindings",
+  ]
+}
diff --git a/chromeos/services/assistant/public/cpp/default_assistant_interaction_subscriber.cc b/chromeos/services/assistant/public/cpp/default_assistant_interaction_subscriber.cc
new file mode 100644
index 0000000..0470071
--- /dev/null
+++ b/chromeos/services/assistant/public/cpp/default_assistant_interaction_subscriber.cc
@@ -0,0 +1,21 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/services/assistant/public/cpp/default_assistant_interaction_subscriber.h"
+
+namespace chromeos {
+namespace assistant {
+
+DefaultAssistantInteractionSubscriber::DefaultAssistantInteractionSubscriber() =
+    default;
+DefaultAssistantInteractionSubscriber::
+    ~DefaultAssistantInteractionSubscriber() = default;
+
+mojo::PendingRemote<mojom::AssistantInteractionSubscriber>
+DefaultAssistantInteractionSubscriber::BindNewPipeAndPassRemote() {
+  return receiver_.BindNewPipeAndPassRemote();
+}
+
+}  // namespace assistant
+}  // namespace chromeos
diff --git a/chromeos/services/assistant/public/cpp/default_assistant_interaction_subscriber.h b/chromeos/services/assistant/public/cpp/default_assistant_interaction_subscriber.h
new file mode 100644
index 0000000..633f4ca
--- /dev/null
+++ b/chromeos/services/assistant/public/cpp/default_assistant_interaction_subscriber.h
@@ -0,0 +1,62 @@
+// Copyright 2020 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 CHROMEOS_SERVICES_ASSISTANT_PUBLIC_CPP_DEFAULT_ASSISTANT_INTERACTION_SUBSCRIBER_H_
+#define CHROMEOS_SERVICES_ASSISTANT_PUBLIC_CPP_DEFAULT_ASSISTANT_INTERACTION_SUBSCRIBER_H_
+
+#include "chromeos/services/assistant/public/mojom/assistant.mojom.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+
+namespace chromeos {
+namespace assistant {
+
+// Implementation of |AssistantInteractionSubscriber| that has a default, empty
+// implementation for each virtual method.
+// This reduces the clutter if a derived class only cares about a few of the
+// methods.
+// It also contains a |mojo::Receiver| as every derived class needs this anyway.
+class DefaultAssistantInteractionSubscriber
+    : public mojom::AssistantInteractionSubscriber {
+ public:
+  DefaultAssistantInteractionSubscriber();
+  ~DefaultAssistantInteractionSubscriber() override;
+
+  mojo::PendingRemote<AssistantInteractionSubscriber>
+  BindNewPipeAndPassRemote();
+
+  // AssistantInteractionSubscriber implementation:
+  void OnInteractionStarted(
+      chromeos::assistant::mojom::AssistantInteractionMetadataPtr) override {}
+  void OnInteractionFinished(
+      chromeos::assistant::mojom::AssistantInteractionResolution) override {}
+  void OnHtmlResponse(const std::string& response,
+                      const std::string& fallback) override {}
+  void OnSuggestionsResponse(
+      std::vector<chromeos::assistant::mojom::AssistantSuggestionPtr> response)
+      override {}
+  void OnTextResponse(const std::string& response) override {}
+  void OnTimersResponse(const std::vector<std::string>& timer_ids) override {}
+  void OnOpenUrlResponse(const ::GURL& url, bool in_background) override {}
+  void OnOpenAppResponse(chromeos::assistant::mojom::AndroidAppInfoPtr app_info,
+                         OnOpenAppResponseCallback callback) override {}
+  void OnSpeechRecognitionStarted() override {}
+  void OnSpeechRecognitionIntermediateResult(
+      const std::string& high_confidence_text,
+      const std::string& low_confidence_text) override {}
+  void OnSpeechRecognitionEndOfUtterance() override {}
+  void OnSpeechRecognitionFinalResult(
+      const std::string& final_result) override {}
+  void OnSpeechLevelUpdated(float speech_level) override {}
+  void OnTtsStarted(bool due_to_error) override {}
+  void OnWaitStarted() override {}
+
+ private:
+  mojo::Receiver<AssistantInteractionSubscriber> receiver_{this};
+};
+
+}  // namespace assistant
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_ASSISTANT_PUBLIC_CPP_DEFAULT_ASSISTANT_INTERACTION_SUBSCRIBER_H_
diff --git a/components/arc/mojom/intent_helper.mojom b/components/arc/mojom/intent_helper.mojom
index 2916146..c874b59f 100644
--- a/components/arc/mojom/intent_helper.mojom
+++ b/components/arc/mojom/intent_helper.mojom
@@ -179,6 +179,7 @@
   OSLANGUAGESINPUTMETHODS,
   OSPRINTING,
   PRINTING,
+  OSPRIVACY,
   OSSEARCH,
   OSRESET,
 
diff --git a/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProviderImpl.java b/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProviderImpl.java
index a010f18..ba7b2b5a 100644
--- a/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProviderImpl.java
+++ b/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProviderImpl.java
@@ -85,6 +85,12 @@
                     child.setAutofillHints(field.mAutocompleteAttr.split(" +"));
                 }
                 child.setHint(field.mPlaceholder);
+
+                RectF bounds = field.getBoundsInContainerViewCoordinates();
+                // Field has no scroll.
+                child.setDimens((int) bounds.left, (int) bounds.top, 0 /* scrollX*/,
+                        0 /* scrollY */, (int) bounds.width(), (int) bounds.height());
+
                 ViewStructure.HtmlInfo.Builder builder =
                         child.newHtmlInfoBuilder("input")
                                 .addAttribute("name", field.mName)
@@ -308,6 +314,7 @@
         mAutofillManager.notifyNewSessionStarted();
         Rect absBound = transformToWindowBounds(new RectF(x, y, x + width, y + height));
         if (mRequest != null) notifyViewExitBeforeDestoryRequest();
+        transformFormFieldToContainViewCoordinates(formData);
         mRequest = new AutofillRequest(formData, new FocusField((short) focus, absBound));
         int virtualId = mRequest.getVirtualId((short) focus);
         mAutofillManager.notifyVirtualViewEntered(mContainerView, virtualId, absBound);
@@ -491,4 +498,25 @@
         return new Rect(
                 (int) bounds.left, (int) bounds.top, (int) bounds.right, (int) bounds.bottom);
     }
+
+    /**
+     * Transform FormFieldData's bounds to ContainView's coordinates and update the bounds with the
+     * transformed one.
+     *
+     * @param formData the form need to be transformed.
+     */
+    private void transformFormFieldToContainViewCoordinates(FormData formData) {
+        WindowAndroid windowAndroid = mWebContents.getTopLevelNativeWindow();
+        DisplayAndroid displayAndroid = windowAndroid.getDisplay();
+        float dipScale = displayAndroid.getDipScale();
+        Matrix matrix = new Matrix();
+        matrix.setScale(dipScale, dipScale);
+        matrix.postTranslate(mContainerView.getScrollX(), mContainerView.getScrollY());
+
+        for (FormFieldData field : formData.mFields) {
+            RectF bounds = new RectF();
+            matrix.mapRect(bounds, field.getBounds());
+            field.setBoundsInContainerViewCoordinates(bounds);
+        }
+    }
 }
diff --git a/components/feed/feed_feature_list.cc b/components/feed/feed_feature_list.cc
index b8f44049..cce49e83 100644
--- a/components/feed/feed_feature_list.cc
+++ b/components/feed/feed_feature_list.cc
@@ -30,4 +30,7 @@
 const base::Feature kReportFeedUserActions{"ReportFeedUserActions",
                                            base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kInterestFeedV2{"InterestFeedV2",
+                                    base::FEATURE_DISABLED_BY_DEFAULT};
+
 }  // namespace feed
diff --git a/components/feed/feed_feature_list.h b/components/feed/feed_feature_list.h
index fe5e114..0122f85 100644
--- a/components/feed/feed_feature_list.h
+++ b/components/feed/feed_feature_list.h
@@ -28,6 +28,8 @@
 // for personalization.
 extern const base::Feature kReportFeedUserActions;
 
+extern const base::Feature kInterestFeedV2;
+
 }  // namespace feed
 
 #endif  // COMPONENTS_FEED_FEED_FEATURE_LIST_H_
diff --git a/components/neterror/resources/neterror.js b/components/neterror/resources/neterror.js
index fb7fd6d..f94a9fc 100644
--- a/components/neterror/resources/neterror.js
+++ b/components/neterror/resources/neterror.js
@@ -72,8 +72,9 @@
 
 // Subframes use a different layout but the same html file.  This is to make it
 // easier to support platforms that load the error page via different
-// mechanisms (Currently just iOS).
-if (window.top.location !== window.location) {
+// mechanisms (Currently just iOS). We also use the subframe style for portals
+// as they are embedded like subframes and can't be interacted with by the user.
+if (window.top.location !== window.location || window.portalHost) {
   document.documentElement.setAttribute('subframe', '');
 }
 
diff --git a/components/omnibox/browser/BUILD.gn b/components/omnibox/browser/BUILD.gn
index 4757f16e..22ec4ffc 100644
--- a/components/omnibox/browser/BUILD.gn
+++ b/components/omnibox/browser/BUILD.gn
@@ -304,6 +304,7 @@
     sources = [
       "android/java/src/org/chromium/components/omnibox/AutocompleteSchemeClassifier.java",
       "android/java/src/org/chromium/components/omnibox/OmniboxUrlEmphasizer.java",
+      "android/java/src/org/chromium/components/omnibox/SecurityButtonAnimationDelegate.java",
       "android/java/src/org/chromium/components/omnibox/SecurityStatusIcon.java",
       "android/java/src/org/chromium/components/omnibox/SuggestionAnswer.java",
     ]
@@ -312,10 +313,12 @@
       ":java_resources",
       "//base:base_java",
       "//base:jni_java",
+      "//components/browser_ui/widget/android:java",
       "//components/embedder_support/android:util_java",
       "//components/security_state/core:security_state_enums_java",
       "//content/public/android:content_java",
       "//third_party/android_deps:androidx_core_core_java",
+      "//ui/android:ui_full_java",
     ]
 
     srcjar_deps = [ ":browser_java_enums_srcjar" ]
diff --git a/components/omnibox/browser/DEPS b/components/omnibox/browser/DEPS
index 5a88c3cf..b4a6af0 100644
--- a/components/omnibox/browser/DEPS
+++ b/components/omnibox/browser/DEPS
@@ -1,6 +1,7 @@
 include_rules = [
   "+components/bookmarks/browser",
   "+components/bookmarks/test",
+  "+components/browser_ui/widget/android/java",
   "+components/component_updater",
   "+components/dom_distiller/core",
   "+components/embedder_support/android/java/src/org/chromium/components/embedder_support/util",
@@ -42,6 +43,7 @@
   "+third_party/protobuf/src/google",
   "+third_party/re2",
   "+third_party/skia",
+  "+ui/android/java",
   "+ui/base",
   "+ui/gfx",
   "+url",
diff --git a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/SecurityButtonAnimationDelegate.java b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/SecurityButtonAnimationDelegate.java
new file mode 100644
index 0000000..2c14b14
--- /dev/null
+++ b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/SecurityButtonAnimationDelegate.java
@@ -0,0 +1,112 @@
+// Copyright 2020 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.components.omnibox;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.view.View;
+import android.widget.ImageButton;
+
+import androidx.annotation.DimenRes;
+
+import org.chromium.components.browser_ui.widget.animation.CancelAwareAnimatorListener;
+import org.chromium.ui.interpolators.BakedBezierInterpolator;
+
+/**
+ * Helper class to animate the security status icon.
+ */
+public class SecurityButtonAnimationDelegate {
+    public static final int SLIDE_DURATION_MS = 200;
+    public static final int FADE_DURATION_MS = 150;
+
+    private final ImageButton mSecurityButton;
+    private final View mTitleUrlContainer;
+    private final AnimatorSet mSecurityButtonShowAnimator;
+    private final AnimatorSet mSecurityButtonHideAnimator;
+    private final int mSecurityButtonWidth;
+
+    public SecurityButtonAnimationDelegate(
+            ImageButton securityButton, View urlTextView, @DimenRes int securityButtonIconSize) {
+        mSecurityButton = securityButton;
+        mTitleUrlContainer = urlTextView;
+        mSecurityButtonWidth =
+                mSecurityButton.getResources().getDimensionPixelSize(securityButtonIconSize);
+
+        mSecurityButtonShowAnimator = new AnimatorSet();
+        Animator translateRight = ObjectAnimator.ofFloat(mTitleUrlContainer, View.TRANSLATION_X, 0);
+        translateRight.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE);
+        translateRight.setDuration(SLIDE_DURATION_MS);
+
+        Animator fadeIn = ObjectAnimator.ofFloat(mSecurityButton, View.ALPHA, 1);
+        fadeIn.setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE);
+        fadeIn.setDuration(FADE_DURATION_MS);
+        fadeIn.addListener(new CancelAwareAnimatorListener() {
+            @Override
+            public void onStart(Animator animation) {
+                mSecurityButton.setVisibility(View.VISIBLE);
+            }
+        });
+        mSecurityButtonShowAnimator.playSequentially(translateRight, fadeIn);
+
+        mSecurityButtonHideAnimator = new AnimatorSet();
+        Animator fadeOut = ObjectAnimator.ofFloat(mSecurityButton, View.ALPHA, 0);
+        fadeOut.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE);
+        fadeOut.setDuration(FADE_DURATION_MS);
+        fadeOut.addListener(new CancelAwareAnimatorListener() {
+            @Override
+            public void onEnd(Animator animation) {
+                mSecurityButton.setVisibility(View.INVISIBLE);
+            }
+        });
+
+        Animator translateLeft = ObjectAnimator.ofFloat(
+                mTitleUrlContainer, View.TRANSLATION_X, -mSecurityButtonWidth);
+        translateLeft.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE);
+        translateLeft.setDuration(SLIDE_DURATION_MS);
+        mSecurityButtonHideAnimator.playSequentially(fadeOut, translateLeft);
+    }
+
+    /**
+     * Based on |securityIconResource|, animates the security status icon in or out.
+     * @param securityIconResource The updated resource to be assigned to the security status icon.
+     * When this is null, the icon is animated to the left and faded out.
+     */
+    public void updateSecurityButton(int securityIconResource) {
+        if (securityIconResource == 0) {
+            // No icon to display.
+            mSecurityButton.setImageDrawable(null);
+            hideSecurityButton();
+        } else {
+            // ImageView#setImageResource is no-op if given resource is the current one.
+            mSecurityButton.setImageResource(securityIconResource);
+            showSecurityButton();
+        }
+    }
+
+    /**
+     * Starts the animation to show the security button.
+     */
+    private void showSecurityButton() {
+        if (mSecurityButtonHideAnimator.isStarted()) mSecurityButtonHideAnimator.cancel();
+        if (mSecurityButtonShowAnimator.isStarted()
+                || mSecurityButton.getVisibility() == View.VISIBLE) {
+            return;
+        }
+        mSecurityButtonShowAnimator.start();
+    }
+
+    /**
+     * Starts the animation to hide the security button.
+     */
+    private void hideSecurityButton() {
+        if (mSecurityButtonShowAnimator.isStarted()) mSecurityButtonShowAnimator.cancel();
+        if (mSecurityButtonHideAnimator.isStarted()
+                || mTitleUrlContainer.getTranslationX() == -mSecurityButtonWidth) {
+            return;
+        }
+        mSecurityButtonHideAnimator.start();
+    }
+}
diff --git a/components/paint_preview/player/android/javatests/src/org/chromium/components/paintpreview/player/PaintPreviewTestRule.java b/components/paint_preview/player/android/javatests/src/org/chromium/components/paintpreview/player/PaintPreviewTestRule.java
index a52cbfc..79d4147 100644
--- a/components/paint_preview/player/android/javatests/src/org/chromium/components/paintpreview/player/PaintPreviewTestRule.java
+++ b/components/paint_preview/player/android/javatests/src/org/chromium/components/paintpreview/player/PaintPreviewTestRule.java
@@ -11,6 +11,7 @@
 import org.chromium.components.signin.AccountManagerFacadeProvider;
 import org.chromium.components.signin.test.util.FakeAccountManagerDelegate;
 import org.chromium.content_public.browser.test.NativeLibraryTestRule;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 /**
  * Loads native and initializes the browser process for Paint Preview instrumentation tests.
@@ -25,7 +26,10 @@
     private void setUp() {
         mAccountManager = new FakeAccountManagerDelegate(
                 FakeAccountManagerDelegate.DISABLE_PROFILE_DATA_SOURCE);
-        AccountManagerFacadeProvider.overrideAccountManagerFacadeForTests(mAccountManager);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            AccountManagerFacadeProvider.setInstanceForTests(
+                    new AccountManagerFacade(mAccountManager));
+        });
         loadNativeLibraryAndInitBrowserProcess();
     }
 
diff --git a/components/password_manager/core/browser/password_save_manager_impl.cc b/components/password_manager/core/browser/password_save_manager_impl.cc
index 4417c7e3..ca5d8b5 100644
--- a/components/password_manager/core/browser/password_save_manager_impl.cc
+++ b/components/password_manager/core/browser/password_save_manager_impl.cc
@@ -157,8 +157,6 @@
     const FormData& submitted_form,
     bool is_http_auth,
     bool is_credential_api_save) {
-  DCHECK(votes_uploader_);
-
   const PasswordForm* similar_saved_form;
   std::tie(similar_saved_form, pending_credentials_state_) =
       FindSimilarSavedFormAndComputeState(parsed_submitted_form);
@@ -172,6 +170,12 @@
       submitted_form, generated_password, is_http_auth, is_credential_api_save,
       similar_saved_form);
 
+  SetVotesAndRecordMetricsForPendingCredentials(parsed_submitted_form);
+}
+
+void PasswordSaveManagerImpl::SetVotesAndRecordMetricsForPendingCredentials(
+    const PasswordForm& parsed_submitted_form) {
+  DCHECK(votes_uploader_);
   votes_uploader_->set_password_overridden(false);
   switch (pending_credentials_state_) {
     case PendingCredentialsState::NEW_LOGIN: {
@@ -217,22 +221,12 @@
   }
 
   if (IsNewLogin()) {
-    metrics_util::LogNewlySavedPasswordIsGenerated(
-        pending_credentials_.type == PasswordForm::Type::kGenerated);
     SanitizePossibleUsernames(&pending_credentials_);
     pending_credentials_.date_created = base::Time::Now();
-    votes_uploader_->SendVotesOnSave(observed_form, parsed_submitted_form,
-                                     form_fetcher_->GetBestMatches(),
-                                     &pending_credentials_);
-    SavePendingToStore(parsed_submitted_form, false /*update*/);
-  } else {
-    // It sounds wrong that we still update even if the state is NONE. We
-    // should double check if this actually necessary. Currently some tests
-    // depend on this behavior.
-    ProcessUpdate(observed_form, parsed_submitted_form);
-    SavePendingToStore(parsed_submitted_form, true /*update*/);
   }
 
+  SavePendingToStore(observed_form, parsed_submitted_form);
+
   if (pending_credentials_.times_used == 1 &&
       pending_credentials_.type == PasswordForm::Type::kGenerated) {
     // This also includes PSL matched credentials.
@@ -254,8 +248,7 @@
 
   pending_credentials_state_ = PendingCredentialsState::UPDATE;
 
-  ProcessUpdate(observed_form, parsed_submitted_form);
-  SavePendingToStore(parsed_submitted_form, true /*update*/);
+  SavePendingToStore(observed_form, parsed_submitted_form);
 }
 
 void PasswordSaveManagerImpl::PermanentlyBlacklist(
@@ -451,14 +444,16 @@
 }
 
 void PasswordSaveManagerImpl::SavePendingToStore(
-    const PasswordForm& parsed_submitted_form,
-    bool update) {
+    const FormData& observed_form,
+    const PasswordForm& parsed_submitted_form) {
+  UploadVotesAndMetrics(observed_form, parsed_submitted_form);
+
+  bool update = !IsNewLogin();
   const PasswordForm* similar_saved_form =
       FindSimilarSavedFormAndComputeState(parsed_submitted_form).first;
-  if ((update || IsPasswordUpdate()) &&
-      !pending_credentials_.IsFederatedCredential()) {
+  if (update && !pending_credentials_.IsFederatedCredential())
     DCHECK(similar_saved_form);
-  }
+
   base::string16 old_password = similar_saved_form
                                     ? similar_saved_form->password_value
                                     : base::string16();
@@ -467,15 +462,27 @@
         pending_credentials_, form_fetcher_->GetAllRelevantMatches(),
         old_password, GetFormSaverForGeneration());
   } else if (update) {
+    // It sounds wrong that we still update even if the state is NONE. We
+    // should double check if this actually necessary. Currently some tests
+    // depend on this behavior.
     UpdateInternal(form_fetcher_->GetAllRelevantMatches(), old_password);
   } else {
     SaveInternal(form_fetcher_->GetAllRelevantMatches(), old_password);
   }
 }
 
-void PasswordSaveManagerImpl::ProcessUpdate(
+void PasswordSaveManagerImpl::UploadVotesAndMetrics(
     const FormData& observed_form,
     const PasswordForm& parsed_submitted_form) {
+  if (IsNewLogin()) {
+    metrics_util::LogNewlySavedPasswordIsGenerated(
+        pending_credentials_.type == PasswordForm::Type::kGenerated);
+    votes_uploader_->SendVotesOnSave(observed_form, parsed_submitted_form,
+                                     form_fetcher_->GetBestMatches(),
+                                     &pending_credentials_);
+    return;
+  }
+
   DCHECK_EQ(FormFetcher::State::NOT_WAITING, form_fetcher_->GetState());
   DCHECK(form_fetcher_->GetPreferredMatch() ||
          pending_credentials_.IsFederatedCredential());
diff --git a/components/password_manager/core/browser/password_save_manager_impl.h b/components/password_manager/core/browser/password_save_manager_impl.h
index 328d13e..783fdbd0 100644
--- a/components/password_manager/core/browser/password_save_manager_impl.h
+++ b/components/password_manager/core/browser/password_save_manager_impl.h
@@ -136,15 +136,18 @@
   const FormFetcher* form_fetcher_;
 
  private:
-  // Save/update |pending_credentials_| to the password store.
-  void SavePendingToStore(const autofill::PasswordForm& parsed_submitted_form,
-                          bool update);
+  void SetVotesAndRecordMetricsForPendingCredentials(
+      const autofill::PasswordForm& parsed_submitted_form);
 
-  // Helper for Save in the case there is at least one match for the pending
-  // credentials. This sends needed signals to the autofill server, and also
-  // triggers some UMA reporting.
-  void ProcessUpdate(const autofill::FormData& observed_form,
-                     const autofill::PasswordForm& parsed_submitted_form);
+  // Save/update |pending_credentials_| to the password store.
+  void SavePendingToStore(const autofill::FormData& observed_form,
+                          const autofill::PasswordForm& parsed_submitted_form);
+
+  // This sends needed signals to the autofill server, and also triggers some
+  // UMA reporting.
+  void UploadVotesAndMetrics(
+      const autofill::FormData& observed_form,
+      const autofill::PasswordForm& parsed_submitted_form);
 
   // Handles the user flows related to the generation.
   std::unique_ptr<PasswordGenerationManager> generation_manager_;
diff --git a/components/payments/content/BUILD.gn b/components/payments/content/BUILD.gn
index ae4e32d..cd3fc20 100644
--- a/components/payments/content/BUILD.gn
+++ b/components/payments/content/BUILD.gn
@@ -6,8 +6,18 @@
 
 jumbo_static_library("content") {
   sources = [
+    "autofill_payment_app_factory.cc",
+    "autofill_payment_app_factory.h",
     "can_make_payment_query_factory.cc",
     "can_make_payment_query_factory.h",
+    "initialization_task.cc",
+    "initialization_task.h",
+    "payment_app_factory.cc",
+    "payment_app_factory.h",
+    "payment_app_service.cc",
+    "payment_app_service.h",
+    "payment_app_service_factory.cc",
+    "payment_app_service_factory.h",
     "payment_details_converter.cc",
     "payment_details_converter.h",
     "payment_event_response_util.cc",
@@ -16,6 +26,12 @@
     "payment_handler_host.h",
     "payment_request_converter.cc",
     "payment_request_converter.h",
+    "payment_request_spec.cc",
+    "payment_request_spec.h",
+    "service_worker_payment_app.cc",
+    "service_worker_payment_app.h",
+    "service_worker_payment_app_factory.cc",
+    "service_worker_payment_app_factory.h",
   ]
 
   deps = [
@@ -26,6 +42,7 @@
     "//components/payments/content/utility",
     "//components/payments/core",
     "//components/payments/core:error_strings",
+    "//components/payments/core:method_strings",
     "//components/payments/mojom",
     "//components/prefs",
     "//components/strings:components_strings_grit",
@@ -38,37 +55,19 @@
 
   if (!is_android) {
     sources += [
-      "autofill_payment_app_factory.cc",
-      "autofill_payment_app_factory.h",
       "content_payment_request_delegate.h",
-      "initialization_task.cc",
-      "initialization_task.h",
-      "payment_app_factory.cc",
-      "payment_app_factory.h",
-      "payment_app_service.cc",
-      "payment_app_service.h",
-      "payment_app_service_factory.cc",
-      "payment_app_service_factory.h",
       "payment_request.cc",
       "payment_request.h",
       "payment_request_dialog.h",
       "payment_request_display_manager.cc",
       "payment_request_display_manager.h",
-      "payment_request_spec.cc",
-      "payment_request_spec.h",
       "payment_request_state.cc",
       "payment_request_state.h",
       "payment_request_web_contents_manager.cc",
       "payment_request_web_contents_manager.h",
       "payment_response_helper.cc",
       "payment_response_helper.h",
-      "service_worker_payment_app.cc",
-      "service_worker_payment_app.h",
-      "service_worker_payment_app_factory.cc",
-      "service_worker_payment_app_factory.h",
     ]
-
-    deps += [ "//components/payments/core:method_strings" ]
   }
 }
 
diff --git a/components/payments/core/BUILD.gn b/components/payments/core/BUILD.gn
index 084d14c4..5daea95 100644
--- a/components/payments/core/BUILD.gn
+++ b/components/payments/core/BUILD.gn
@@ -6,6 +6,12 @@
 
 jumbo_static_library("core") {
   sources = [
+    "autofill_card_validation.cc",
+    "autofill_card_validation.h",
+    "autofill_payment_app.cc",
+    "autofill_payment_app.h",
+    "basic_card_response.cc",
+    "basic_card_response.h",
     "can_make_payment_query.cc",
     "can_make_payment_query.h",
     "currency_formatter.cc",
@@ -18,6 +24,8 @@
     "features.h",
     "journey_logger.cc",
     "journey_logger.h",
+    "payer_data.cc",
+    "payer_data.h",
     "payment_address.cc",
     "payment_address.h",
     "payment_app.cc",
@@ -38,6 +46,8 @@
     "payment_method_data.h",
     "payment_prefs.cc",
     "payment_prefs.h",
+    "payment_request_data_util.cc",
+    "payment_request_data_util.h",
     "payment_shipping_option.cc",
     "payment_shipping_option.h",
     "payments_experimental_features.cc",
@@ -50,20 +60,10 @@
 
   if (!is_android) {
     sources += [
-      "autofill_card_validation.cc",
-      "autofill_card_validation.h",
-      "autofill_payment_app.cc",
-      "autofill_payment_app.h",
-      "basic_card_response.cc",
-      "basic_card_response.h",
-      "payer_data.cc",
-      "payer_data.h",
       "payment_options.cc",
       "payment_options.h",
       "payment_options_provider.h",
       "payment_request_base_delegate.h",
-      "payment_request_data_util.cc",
-      "payment_request_data_util.h",
       "payment_request_delegate.h",
       "payment_response.cc",
       "payment_response.h",
diff --git a/components/permissions/BUILD.gn b/components/permissions/BUILD.gn
index 2a4eb7e..d7a2876 100644
--- a/components/permissions/BUILD.gn
+++ b/components/permissions/BUILD.gn
@@ -57,6 +57,8 @@
   ]
   if (is_android) {
     sources += [
+      "android/android_permission_util.cc",
+      "android/android_permission_util.h",
       "android/permission_dialog_delegate.cc",
       "android/permission_dialog_delegate.h",
       "android/permission_prompt_android.cc",
diff --git a/components/permissions/android/android_permission_util.cc b/components/permissions/android/android_permission_util.cc
new file mode 100644
index 0000000..959ed69
--- /dev/null
+++ b/components/permissions/android/android_permission_util.cc
@@ -0,0 +1,55 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/permissions/android/android_permission_util.h"
+
+#include "base/android/jni_array.h"
+#include "components/permissions/android/jni_headers/PermissionUtil_jni.h"
+#include "components/permissions/permission_uma_util.h"
+#include "content/public/browser/web_contents.h"
+#include "ui/android/window_android.h"
+
+namespace permissions {
+
+void GetAndroidPermissionsForContentSetting(
+    ContentSettingsType content_settings_type,
+    std::vector<std::string>* out) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  base::android::AppendJavaStringArrayToStringVector(
+      env,
+      Java_PermissionUtil_getAndroidPermissionsForContentSetting(
+          env, static_cast<int>(content_settings_type)),
+      out);
+}
+
+PermissionRepromptState ShouldRepromptUserForPermissions(
+    content::WebContents* web_contents,
+    const std::vector<ContentSettingsType>& content_settings_types) {
+  if (!web_contents)
+    return PermissionRepromptState::kCannotShow;
+
+  auto* window_android = web_contents->GetNativeView()->GetWindowAndroid();
+  if (!window_android)
+    return PermissionRepromptState::kCannotShow;
+
+  for (ContentSettingsType content_settings_type : content_settings_types) {
+    std::vector<std::string> android_permissions;
+    GetAndroidPermissionsForContentSetting(content_settings_type,
+                                           &android_permissions);
+
+    for (const auto& android_permission : android_permissions) {
+      if (!window_android->HasPermission(android_permission)) {
+        PermissionUmaUtil::RecordMissingPermissionInfobarShouldShow(
+            true, content_settings_types);
+        return PermissionRepromptState::kShow;
+      }
+    }
+  }
+
+  permissions::PermissionUmaUtil::RecordMissingPermissionInfobarShouldShow(
+      false, content_settings_types);
+  return PermissionRepromptState::kNoNeed;
+}
+
+}  // namespace permissions
diff --git a/components/permissions/android/android_permission_util.h b/components/permissions/android/android_permission_util.h
new file mode 100644
index 0000000..c636707
--- /dev/null
+++ b/components/permissions/android/android_permission_util.h
@@ -0,0 +1,42 @@
+// Copyright 2020 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 COMPONENTS_PERMISSIONS_ANDROID_ANDROID_PERMISSION_UTIL_H_
+#define COMPONENTS_PERMISSIONS_ANDROID_ANDROID_PERMISSION_UTIL_H_
+
+#include <string>
+#include <vector>
+
+#include "components/content_settings/core/common/content_settings_types.h"
+
+namespace content {
+class WebContents;
+}
+
+namespace permissions {
+
+// Populate the list of corresponding Android permissions associated with the
+// ContentSettingsType specified.
+void GetAndroidPermissionsForContentSetting(ContentSettingsType content_type,
+                                            std::vector<std::string>* out);
+
+// The states that indicate if the user should/can be re-nudged to accept
+// permissions. In Chrome this correlates to the PermissionUpdateInfoBar.
+enum class PermissionRepromptState {
+  // No need to show the infobar as the permissions have been already granted.
+  kNoNeed = 0,
+  // Show the the permission infobar.
+  kShow,
+  // Can't show the permission infobar due to an internal state issue like
+  // the WebContents or the AndroidWindow are not available.
+  kCannotShow,
+};
+
+PermissionRepromptState ShouldRepromptUserForPermissions(
+    content::WebContents* web_contents,
+    const std::vector<ContentSettingsType>& content_settings_types);
+
+}  // namespace permissions
+
+#endif  // COMPONENTS_PERMISSIONS_ANDROID_ANDROID_PERMISSION_UTIL_H_
diff --git a/components/permissions/contexts/geolocation_permission_context.h b/components/permissions/contexts/geolocation_permission_context.h
index 7a45795..3b4b392 100644
--- a/components/permissions/contexts/geolocation_permission_context.h
+++ b/components/permissions/contexts/geolocation_permission_context.h
@@ -46,18 +46,6 @@
                                   bool allowed) = 0;
 
 #if defined(OS_ANDROID)
-    // Returns whether or not the Android location permission should be
-    // requested.
-    virtual bool ShouldRequestAndroidLocationPermission(
-        content::WebContents* web_contents) = 0;
-
-    using PermissionUpdatedCallback = base::OnceCallback<void(bool)>;
-    // Requests Android location permission, and calls |callback| with the
-    // reslt.
-    virtual void RequestAndroidPermission(
-        content::WebContents* web_contents,
-        PermissionUpdatedCallback callback) = 0;
-
     // Returns whether or not this |web_contents| is interactable.
     virtual bool IsInteractable(content::WebContents* web_contents) = 0;
 
diff --git a/components/permissions/contexts/geolocation_permission_context_android.cc b/components/permissions/contexts/geolocation_permission_context_android.cc
index 7fe86d2..b517320 100644
--- a/components/permissions/contexts/geolocation_permission_context_android.cc
+++ b/components/permissions/contexts/geolocation_permission_context_android.cc
@@ -12,8 +12,10 @@
 #include "base/metrics/histogram_functions.h"
 #include "components/location/android/location_settings.h"
 #include "components/location/android/location_settings_impl.h"
+#include "components/permissions/android/android_permission_util.h"
 #include "components/permissions/permission_request_id.h"
 #include "components/permissions/permission_uma_util.h"
+#include "components/permissions/permissions_client.h"
 #include "components/permissions/pref_names.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
@@ -112,9 +114,11 @@
           embedding_origin)
           .content_setting;
   if (content_setting == CONTENT_SETTING_ALLOW &&
-      delegate_->ShouldRequestAndroidLocationPermission(web_contents)) {
-    delegate_->RequestAndroidPermission(
-        web_contents,
+      ShouldRepromptUserForPermissions(web_contents,
+                                       {ContentSettingsType::GEOLOCATION}) ==
+          PermissionRepromptState::kShow) {
+    PermissionsClient::Get()->RepromptForAndroidPermissions(
+        web_contents, {ContentSettingsType::GEOLOCATION},
         base::BindOnce(&GeolocationPermissionContextAndroid::
                            HandleUpdateAndroidPermissions,
                        weak_factory_.GetWeakPtr(), id, requesting_frame_origin,
diff --git a/components/permissions/contexts/geolocation_permission_context_unittest.cc b/components/permissions/contexts/geolocation_permission_context_unittest.cc
index 773af2d..f7821b1c 100644
--- a/components/permissions/contexts/geolocation_permission_context_unittest.cc
+++ b/components/permissions/contexts/geolocation_permission_context_unittest.cc
@@ -93,14 +93,6 @@
   }
 
 #if defined(OS_ANDROID)
-  bool ShouldRequestAndroidLocationPermission(
-      content::WebContents* web_contents) override {
-    return false;
-  }
-
-  void RequestAndroidPermission(content::WebContents* web_contents,
-                                PermissionUpdatedCallback callback) override {}
-
   bool IsInteractable(content::WebContents* web_contents) override {
     return true;
   }
diff --git a/components/permissions/permission_util.cc b/components/permissions/permission_util.cc
index ec59a99..df3ea21 100644
--- a/components/permissions/permission_util.cc
+++ b/components/permissions/permission_util.cc
@@ -8,11 +8,6 @@
 #include "build/build_config.h"
 #include "content/public/browser/permission_type.h"
 
-#if defined(OS_ANDROID)
-#include "base/android/jni_array.h"
-#include "components/permissions/android/jni_headers/PermissionUtil_jni.h"
-#endif
-
 using content::PermissionType;
 
 namespace permissions {
@@ -203,18 +198,4 @@
   }
 }
 
-#if defined(OS_ANDROID)
-// static
-void PermissionUtil::GetAndroidPermissionsForContentSetting(
-    ContentSettingsType content_settings_type,
-    std::vector<std::string>* out) {
-  JNIEnv* env = base::android::AttachCurrentThread();
-  base::android::AppendJavaStringArrayToStringVector(
-      env,
-      Java_PermissionUtil_getAndroidPermissionsForContentSetting(
-          env, static_cast<int>(content_settings_type)),
-      out);
-}
-#endif
-
 }  // namespace permissions
diff --git a/components/permissions/permission_util.h b/components/permissions/permission_util.h
index 89be65f..2cb93a4b 100644
--- a/components/permissions/permission_util.h
+++ b/components/permissions/permission_util.h
@@ -6,7 +6,6 @@
 #define COMPONENTS_PERMISSIONS_PERMISSION_UTIL_H_
 
 #include <string>
-#include <vector>
 
 #include "base/macros.h"
 #include "build/build_config.h"
@@ -58,14 +57,6 @@
   // PermissionManager.
   static bool IsPermission(ContentSettingsType type);
 
-#if defined(OS_ANDROID)
-  // Populate the list of corresponding Android permissions associated with the
-  // ContentSettingsType specified.
-  static void GetAndroidPermissionsForContentSetting(
-      ContentSettingsType content_type,
-      std::vector<std::string>* out);
-#endif
-
  private:
   DISALLOW_IMPLICIT_CONSTRUCTORS(PermissionUtil);
 };
diff --git a/components/permissions/permissions_client.cc b/components/permissions/permissions_client.cc
index 941f6fa..67430fcc 100644
--- a/components/permissions/permissions_client.cc
+++ b/components/permissions/permissions_client.cc
@@ -118,6 +118,13 @@
   return nullptr;
 }
 
+void PermissionsClient::RepromptForAndroidPermissions(
+    content::WebContents* web_contents,
+    const std::vector<ContentSettingsType>& content_settings_types,
+    PermissionsUpdatedCallback callback) {
+  std::move(callback).Run(false);
+}
+
 base::android::ScopedJavaLocalRef<jobject> PermissionsClient::GetJavaObject() {
   return Java_PermissionsClient_get(base::android::AttachCurrentThread());
 }
diff --git a/components/permissions/permissions_client.h b/components/permissions/permissions_client.h
index 0a7eaa4..fba25661 100644
--- a/components/permissions/permissions_client.h
+++ b/components/permissions/permissions_client.h
@@ -163,6 +163,16 @@
       ContentSettingsType type,
       base::WeakPtr<PermissionPromptAndroid> prompt);
 
+  using PermissionsUpdatedCallback = base::OnceCallback<void(bool)>;
+
+  // Prompts the user to accept system permissions for |content_settings_types|,
+  // after they've already been denied. In Chrome, this shows an infobar.
+  // |callback| will be run with |true| for success and |false| otherwise.
+  virtual void RepromptForAndroidPermissions(
+      content::WebContents* web_contents,
+      const std::vector<ContentSettingsType>& content_settings_types,
+      PermissionsUpdatedCallback callback);
+
   // Returns a handle to the Java counterpart of this class.
   virtual base::android::ScopedJavaLocalRef<jobject> GetJavaObject();
 
diff --git a/components/policy/proto/device_management_backend.proto b/components/policy/proto/device_management_backend.proto
index 2e0a147..30e7ad0b 100644
--- a/components/policy/proto/device_management_backend.proto
+++ b/components/policy/proto/device_management_backend.proto
@@ -1079,6 +1079,12 @@
   optional uint32 brightness = 3;
 }
 
+// Information about the device's fan.
+message FanInfo {
+  // Fan speed in RPM.
+  optional uint32 speed_rpm = 1;
+}
+
 // Report device level status.
 message DeviceStatusReportRequest {
   reserved 4, 7, 13, 20;
@@ -1188,6 +1194,9 @@
 
   // Information about the device's backlights.
   repeated BacklightInfo backlight_info = 37;
+
+  // Information about the device's fans.
+  repeated FanInfo fan_info = 38;
 }
 
 message OsUpdateStatus {
diff --git a/components/schema_org/generate_schema_org_code.py b/components/schema_org/generate_schema_org_code.py
index 28ba1a66..20d8ab4c 100644
--- a/components/schema_org/generate_schema_org_code.py
+++ b/components/schema_org/generate_schema_org_code.py
@@ -133,9 +133,47 @@
         schema['@graph'].append(thing)
 
 
+def lookup_parents(thing, schema, lookup_table):
+    """Recursively looks up all the parents of thing in the schema.
+
+  Returns the parents and populates them in lookup_table. The parents list may
+  contain duplicates if thing has multiple inheritance trees.
+  """
+    obj_name = object_name_from_id(thing['@id'])
+    if obj_name in lookup_table:
+        return lookup_table[obj_name]
+    lookup_table[obj_name] = []
+
+    if 'rdfs:subClassOf' in thing:
+        parent_classes = thing['rdfs:subClassOf']
+        if not isinstance(parent_classes, list):
+            parent_classes = [parent_classes]
+        parent_classes = [
+            get_schema_obj(parent['@id'], schema) for parent in parent_classes
+        ]
+        parent_classes = [
+            parent for parent in parent_classes if parent is not None
+        ]
+        found_parents = [
+            lookup_parents(parent_thing, schema, lookup_table)
+            for parent_thing in parent_classes
+        ]
+        # flatten the list
+        found_parents = [item for sublist in found_parents for item in sublist]
+        lookup_table[obj_name].extend(found_parents)
+
+    lookup_table[obj_name].append(obj_name)
+    return lookup_table[obj_name]
+
+
 def get_template_vars(schema_file_path, overrides_file_path):
     """Read the needed template variables from the schema file."""
-    template_vars = {'entities': [], 'properties': [], 'enums': []}
+    template_vars = {
+        'entities': [],
+        'properties': [],
+        'enums': [],
+        'entity_parent_lookup': {}
+    }
 
     with open(schema_file_path) as schema_file:
         schema = json.loads(schema_file.read())
@@ -149,6 +187,8 @@
     for thing in schema['@graph']:
         if thing['@type'] == 'rdfs:Class':
             template_vars['entities'].append(object_name_from_id(thing['@id']))
+            lookup_parents(thing, schema,
+                           template_vars['entity_parent_lookup'])
             if is_enum_type(thing):
                 template_vars['enums'].append({
                     'name':
diff --git a/components/schema_org/generate_schema_org_code_unittest.cc b/components/schema_org/generate_schema_org_code_unittest.cc
index 3de5e7a9..7a08b9f 100644
--- a/components/schema_org/generate_schema_org_code_unittest.cc
+++ b/components/schema_org/generate_schema_org_code_unittest.cc
@@ -92,4 +92,17 @@
       testing::UnorderedElementsAre("http://schema.org/PropertyValue"));
 }
 
+TEST(GenerateSchemaOrgCodeTest, IsDescendedFromSucceeds) {
+  EXPECT_TRUE(entity::IsDescendedFrom(entity::kThing, entity::kVideoObject));
+}
+
+TEST(GenerateSchemaOrgCodeTest, IsDescendedFromSucceedsMultipleInheritance) {
+  EXPECT_TRUE(
+      entity::IsDescendedFrom(entity::kPlace, entity::kCafeOrCoffeeShop));
+}
+
+TEST(GenerateSchemaOrgCodeTest, IsDescendedFromFails) {
+  EXPECT_FALSE(entity::IsDescendedFrom(entity::kVideoObject, entity::kThing));
+}
+
 }  // namespace schema_org
diff --git a/components/schema_org/generate_schema_org_code_unittest.py b/components/schema_org/generate_schema_org_code_unittest.py
index fb2f6d23..4cdf698 100644
--- a/components/schema_org/generate_schema_org_code_unittest.py
+++ b/components/schema_org/generate_schema_org_code_unittest.py
@@ -48,8 +48,27 @@
                         'enum_types': []
                     }],
                     'enums': [],
+                    'entity_parent_lookup': {
+                        'MediaObject': ['MediaObject']
+                    }
                 })
 
+    def test_lookup_parents(self):
+        thing = {'@id': schema_org_id('Thing')}
+        intangible = {
+            '@id': schema_org_id('Intangible'),
+            'rdfs:subClassOf': thing
+        }
+        structured_value = {
+            '@id': schema_org_id('StructuredValue'),
+            'rdfs:subClassOf': intangible
+        }
+        brand = {'@id': schema_org_id('Brand'), 'rdfs:subClassOf': intangible}
+        schema = {'@graph': [thing, intangible, structured_value, brand]}
+        self.assertListEqual(
+            generate_schema_org_code.lookup_parents(brand, schema, {}),
+            ['Thing', 'Intangible', 'Brand'])
+
     def test_get_root_type_thing(self):
         thing = {'@id': schema_org_id('Thing')}
         intangible = {
diff --git a/components/schema_org/templates/schema_org_entity_names.cc.tmpl b/components/schema_org/templates/schema_org_entity_names.cc.tmpl
index c9aae1d..95b5f14 100644
--- a/components/schema_org/templates/schema_org_entity_names.cc.tmpl
+++ b/components/schema_org/templates/schema_org_entity_names.cc.tmpl
@@ -27,5 +27,26 @@
   return kValidEntityNames->find(entity_name) != kValidEntityNames->end();
 }
 
+bool IsDescendedFrom(const std::string& possible_parent,
+                     const std::string& possible_child) {
+  static const base::NoDestructor<std::map<std::string, std::vector<std::string>>>
+    kParentEntities(std::map<std::string, std::vector<std::string>>({
+      {%for key in entity_parent_lookup %}
+      { "{{key}}", {
+        {% for parent in entity_parent_lookup[key] %}
+          "{{parent}}",
+        {% endfor %}
+        }
+      },
+      {% endfor %}
+    }));
+  auto parents = kParentEntities->find(possible_child);
+  if (parents == kParentEntities->end())
+    return false;
+  auto it = std::find_if(parents->second.begin(), parents->second.end(), [&possible_parent](const std::string& parent) { return parent == possible_parent; });
+  return it != parents->second.end();
+}
+
+
 }  // entity
 }  // schema_org
diff --git a/components/schema_org/templates/schema_org_entity_names.h.tmpl b/components/schema_org/templates/schema_org_entity_names.h.tmpl
index 134ea42..afdd033 100644
--- a/components/schema_org/templates/schema_org_entity_names.h.tmpl
+++ b/components/schema_org/templates/schema_org_entity_names.h.tmpl
@@ -19,6 +19,11 @@
 
 bool IsValidEntityName(const std::string& entity_name);
 
+// Returns true if the entity with name possible_child inherits from
+// possible_parent.
+bool IsDescendedFrom(const std::string& possible_parent,
+                     const std::string& possible_child);
+
 }  // namespace entity
 }  // namespace schema_org
 
diff --git a/components/schema_org/validator.cc b/components/schema_org/validator.cc
index dd96578..afe0b664 100644
--- a/components/schema_org/validator.cc
+++ b/components/schema_org/validator.cc
@@ -17,6 +17,24 @@
 using improved::mojom::Entity;
 using improved::mojom::EntityPtr;
 
+base::Optional<std::string> ObjectNameFromId(const std::string& id) {
+  GURL id_url = GURL(id);
+  if (!id_url.SchemeIsHTTPOrHTTPS() || id_url.host() != "schema.org")
+    return base::nullopt;
+  return id_url.path().substr(1);
+}
+
+bool EntityPropertyIsValidType(const property::PropertyConfiguration& config,
+                               const std::string& type) {
+  for (const auto& thing_type_id : config.thing_types) {
+    auto thing_type_name = ObjectNameFromId(thing_type_id);
+    DCHECK(thing_type_name.has_value());
+    if (entity::IsDescendedFrom(thing_type_name.value(), type))
+      return true;
+  }
+  return false;
+}
+
 // static
 bool ValidateEntity(Entity* entity) {
   if (!entity::IsValidEntityName(entity->type)) {
@@ -49,7 +67,8 @@
         auto nested_it = (*it)->values->entity_values.begin();
         while (nested_it != (*it)->values->entity_values.end()) {
           auto& nested_entity = *nested_it;
-          if (!ValidateEntity(nested_entity.get())) {
+          if (!ValidateEntity(nested_entity.get()) ||
+              !EntityPropertyIsValidType(config, nested_entity->type)) {
             nested_it = (*it)->values->entity_values.erase(nested_it);
           } else {
             has_valid_entities = true;
diff --git a/components/schema_org/validator_unittest.cc b/components/schema_org/validator_unittest.cc
index b9ed23e..f376ab2 100644
--- a/components/schema_org/validator_unittest.cc
+++ b/components/schema_org/validator_unittest.cc
@@ -204,6 +204,27 @@
   EXPECT_TRUE(entity->properties.empty());
 }
 
+TEST_F(SchemaOrgValidatorTest, ValidEntityPropertyValueDescendedType) {
+  EntityPtr entity = Entity::New();
+  entity->type = entity::kVideoObject;
+
+  PropertyPtr property = Property::New();
+  property->name = property::kThumbnail;
+  property->values = Values::New();
+
+  // Barcode is descended from ImageObject, an acceptable type for the thumbnail
+  // property. So barcode should also be accepted.
+  EntityPtr value = Entity::New();
+  value->type = entity::kBarcode;
+  property->values->entity_values.push_back(std::move(value));
+
+  entity->properties.push_back(std::move(property));
+
+  bool validated_entity = ValidateEntity(entity.get());
+  EXPECT_TRUE(validated_entity);
+  EXPECT_EQ(1u, entity->properties.size());
+}
+
 TEST_F(SchemaOrgValidatorTest, ValidRepeatedEntityPropertyValue) {
   EntityPtr entity = Entity::New();
   entity->type = entity::kRestaurant;
diff --git a/components/security_interstitials/content/ssl_error_navigation_throttle.cc b/components/security_interstitials/content/ssl_error_navigation_throttle.cc
index d1f031c..d848010 100644
--- a/components/security_interstitials/content/ssl_error_navigation_throttle.cc
+++ b/components/security_interstitials/content/ssl_error_navigation_throttle.cc
@@ -41,7 +41,7 @@
 
   // Do not set special error page HTML for subframes; those are handled as
   // normal network errors.
-  if (!handle->IsInMainFrame()) {
+  if (!handle->IsInMainFrame() || handle->GetWebContents()->IsPortal()) {
     return content::NavigationThrottle::PROCEED;
   }
 
@@ -67,7 +67,7 @@
 
   // Do not set special error page HTML for subframes; those are handled as
   // normal network errors.
-  if (!handle->IsInMainFrame()) {
+  if (!handle->IsInMainFrame() || handle->GetWebContents()->IsPortal()) {
     return content::NavigationThrottle::PROCEED;
   }
 
diff --git a/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountManagerFacade.java b/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountManagerFacade.java
index cb781dc..2d8b9e0e 100644
--- a/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountManagerFacade.java
+++ b/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountManagerFacade.java
@@ -84,7 +84,8 @@
     /**
      * @param delegate the AccountManagerDelegate to use as a backend
      */
-    AccountManagerFacade(AccountManagerDelegate delegate) {
+    @VisibleForTesting
+    public AccountManagerFacade(AccountManagerDelegate delegate) {
         ThreadUtils.assertOnUiThread();
         mDelegate = delegate;
         mDelegate.registerObservers();
diff --git a/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountManagerFacadeProvider.java b/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountManagerFacadeProvider.java
index a9224de..3bdf9c0 100644
--- a/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountManagerFacadeProvider.java
+++ b/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountManagerFacadeProvider.java
@@ -26,7 +26,7 @@
 
     /**
      * Initializes AccountManagerFacade singleton instance. Can only be called once.
-     * Tests can override the instance with {@link #overrideAccountManagerFacadeForTests}.
+     * Tests can override the instance with {@link #setInstanceForTests}.
      *
      * @param delegate the AccountManagerDelegate to use
      */
@@ -42,27 +42,23 @@
     }
 
     /**
-     * Overrides AccountManagerFacade singleton instance for tests. Only for use in Tests.
-     * Overrides any previous or future calls to {@link #initializeAccountManagerFacade}.
-     *
-     * @param delegate the AccountManagerDelegate to use
+     * Sets the test instance.
      */
     @VisibleForTesting
     @AnyThread
-    public static void overrideAccountManagerFacadeForTests(AccountManagerDelegate delegate) {
+    public static void setInstanceForTests(AccountManagerFacade accountManagerFacade) {
         ThreadUtils.runOnUiThreadBlocking(() -> {
-            sTestingInstance = new AccountManagerFacade(delegate);
+            sTestingInstance = accountManagerFacade;
             sAtomicInstance.set(sTestingInstance);
         });
     }
 
     /**
-     * Resets custom AccountManagerFacade set with {@link #overrideAccountManagerFacadeForTests}.
-     * Only for use in Tests.
+     * Resets the test instance set with {@link #setInstanceForTests}.
      */
     @VisibleForTesting
     @AnyThread
-    public static void resetAccountManagerFacadeForTests() {
+    public static void resetInstanceForTests() {
         ThreadUtils.runOnUiThreadBlocking(() -> {
             sTestingInstance = null;
             sAtomicInstance.set(sInstance);
@@ -71,7 +67,7 @@
 
     /**
      * Singleton instance getter. Singleton must be initialized before calling this by
-     * {@link #initializeAccountManagerFacade} or {@link #overrideAccountManagerFacadeForTests}.
+     * {@link #initializeAccountManagerFacade} or {@link #setInstanceForTests}.
      *
      * @return a singleton instance
      */
diff --git a/components/signin/core/browser/android/java/src/org/chromium/components/signin/ChildAccountInfoFetcher.java b/components/signin/core/browser/android/java/src/org/chromium/components/signin/ChildAccountInfoFetcher.java
index 730e5a8..3daf09a2 100644
--- a/components/signin/core/browser/android/java/src/org/chromium/components/signin/ChildAccountInfoFetcher.java
+++ b/components/signin/core/browser/android/java/src/org/chromium/components/signin/ChildAccountInfoFetcher.java
@@ -10,6 +10,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 
+import androidx.annotation.VisibleForTesting;
+
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 import org.chromium.base.ThreadUtils;
@@ -86,10 +88,11 @@
                 mNativeAccountFetcherService, mAccountId, isChildAccount);
     }
 
+    @VisibleForTesting
     @CalledByNative
     private static void initializeForTests() {
         AccountManagerDelegate delegate = new SystemAccountManagerDelegate();
-        AccountManagerFacadeProvider.overrideAccountManagerFacadeForTests(delegate);
+        AccountManagerFacadeProvider.setInstanceForTests(new AccountManagerFacade(delegate));
     }
 
     @NativeMethods
diff --git a/components/signin/core/browser/android/javatests/src/org/chromium/components/signin/test/AccountManagerFacadeTest.java b/components/signin/core/browser/android/javatests/src/org/chromium/components/signin/test/AccountManagerFacadeTest.java
index f926bcd..76090627 100644
--- a/components/signin/core/browser/android/javatests/src/org/chromium/components/signin/test/AccountManagerFacadeTest.java
+++ b/components/signin/core/browser/android/javatests/src/org/chromium/components/signin/test/AccountManagerFacadeTest.java
@@ -36,7 +36,9 @@
 
     @Before
     public void setUp() {
-        AccountManagerFacadeProvider.overrideAccountManagerFacadeForTests(mDelegate);
+        ThreadUtils.runOnUiThreadBlocking(() -> {
+            AccountManagerFacadeProvider.setInstanceForTests(new AccountManagerFacade(mDelegate));
+        });
     }
 
     @Test
diff --git a/components/signin/core/browser/android/javatests/src/org/chromium/components/signin/test/util/AccountManagerTestRule.java b/components/signin/core/browser/android/javatests/src/org/chromium/components/signin/test/util/AccountManagerTestRule.java
index 2b1df5e..18395734 100644
--- a/components/signin/core/browser/android/javatests/src/org/chromium/components/signin/test/util/AccountManagerTestRule.java
+++ b/components/signin/core/browser/android/javatests/src/org/chromium/components/signin/test/util/AccountManagerTestRule.java
@@ -53,7 +53,10 @@
                         : FakeAccountManagerDelegate.ENABLE_BLOCK_GET_ACCOUNTS;
                 mDelegate = new FakeAccountManagerDelegate(
                         mProfileDataSourceFlag, blockGetAccountsFlag);
-                AccountManagerFacadeProvider.overrideAccountManagerFacadeForTests(mDelegate);
+                ThreadUtils.runOnUiThreadBlocking(() -> {
+                    AccountManagerFacadeProvider.setInstanceForTests(
+                            new AccountManagerFacade(mDelegate));
+                });
                 statement.evaluate();
             }
         };
diff --git a/components/signin/core/browser/android/junit/src/org/chromium/components/signin/test/AccountManagerFacadeRobolectricTest.java b/components/signin/core/browser/android/junit/src/org/chromium/components/signin/test/AccountManagerFacadeRobolectricTest.java
index 8c474e3..a8637e00 100644
--- a/components/signin/core/browser/android/junit/src/org/chromium/components/signin/test/AccountManagerFacadeRobolectricTest.java
+++ b/components/signin/core/browser/android/junit/src/org/chromium/components/signin/test/AccountManagerFacadeRobolectricTest.java
@@ -63,7 +63,8 @@
         mDelegate = new FakeAccountManagerDelegate(
                 FakeAccountManagerDelegate.ENABLE_PROFILE_DATA_SOURCE);
         Assert.assertFalse(mDelegate.isRegisterObserversCalled());
-        AccountManagerFacadeProvider.overrideAccountManagerFacadeForTests(mDelegate);
+        // TODO(https://crbug.com/1067633): Remove AccountManagerFacadeProvider in this test
+        AccountManagerFacadeProvider.setInstanceForTests(new AccountManagerFacade(mDelegate));
         Assert.assertTrue(mDelegate.isRegisterObserversCalled());
         mFacade = AccountManagerFacadeProvider.getInstance();
     }
diff --git a/components/sync/android/javatests/src/org/chromium/components/sync/AndroidSyncSettingsTest.java b/components/sync/android/javatests/src/org/chromium/components/sync/AndroidSyncSettingsTest.java
index 6741359..88150a9 100644
--- a/components/sync/android/javatests/src/org/chromium/components/sync/AndroidSyncSettingsTest.java
+++ b/components/sync/android/javatests/src/org/chromium/components/sync/AndroidSyncSettingsTest.java
@@ -20,6 +20,7 @@
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.Feature;
+import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.AccountManagerFacadeProvider;
 import org.chromium.components.signin.AccountUtils;
 import org.chromium.components.signin.ChromeSigninController;
@@ -140,7 +141,10 @@
     private void setupTestAccounts() {
         mAccountManager = new FakeAccountManagerDelegate(
                 FakeAccountManagerDelegate.DISABLE_PROFILE_DATA_SOURCE);
-        AccountManagerFacadeProvider.overrideAccountManagerFacadeForTests(mAccountManager);
+        ThreadUtils.runOnUiThreadBlocking(() -> {
+            AccountManagerFacadeProvider.setInstanceForTests(
+                    new AccountManagerFacade(mAccountManager));
+        });
         mAccount = addTestAccount("account@example.com");
         mAlternateAccount = addTestAccount("alternate@example.com");
     }
diff --git a/components/webrtc/BUILD.gn b/components/webrtc/BUILD.gn
new file mode 100644
index 0000000..a095af23
--- /dev/null
+++ b/components/webrtc/BUILD.gn
@@ -0,0 +1,24 @@
+# Copyright 2020 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.
+
+source_set("webrtc") {
+  sources = [
+    "media_stream_device_enumerator.h",
+    "media_stream_device_enumerator_impl.cc",
+    "media_stream_device_enumerator_impl.h",
+    "media_stream_devices_controller.cc",
+    "media_stream_devices_controller.h",
+  ]
+
+  deps = [
+    "//base",
+    "//components/content_settings/core/common",
+    "//components/permissions",
+    "//content/public/browser",
+    "//content/public/common",
+  ]
+  if (is_android) {
+    deps += [ "//ui/android" ]
+  }
+}
diff --git a/components/webrtc/DEPS b/components/webrtc/DEPS
new file mode 100644
index 0000000..93ba970e
--- /dev/null
+++ b/components/webrtc/DEPS
@@ -0,0 +1,9 @@
+include_rules = [
+  "+components/content_settings",
+  "+components/permissions",
+  "+content/public/browser",
+  "+content/public/common",
+  "+third_party/blink/public/common",
+  "+third_party/blink/public/mojom",
+  "+ui/android",
+]
diff --git a/components/webrtc/OWNERS b/components/webrtc/OWNERS
new file mode 100644
index 0000000..48b19446
--- /dev/null
+++ b/components/webrtc/OWNERS
@@ -0,0 +1,3 @@
+file://third_party/blink/public/platform/modules/webrtc/OWNERS
+
+# COMPONENT: Blink>WebRTC
diff --git a/components/webrtc/media_stream_device_enumerator.h b/components/webrtc/media_stream_device_enumerator.h
new file mode 100644
index 0000000..f148f44
--- /dev/null
+++ b/components/webrtc/media_stream_device_enumerator.h
@@ -0,0 +1,52 @@
+// Copyright 2020 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 COMPONENTS_WEBRTC_MEDIA_STREAM_DEVICE_ENUMERATOR_H_
+#define COMPONENTS_WEBRTC_MEDIA_STREAM_DEVICE_ENUMERATOR_H_
+
+#include <string>
+
+#include "third_party/blink/public/common/mediastream/media_stream_request.h"
+#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace webrtc {
+
+// Enumerates system devices for MediaStreamDevicesController.
+class MediaStreamDeviceEnumerator {
+ public:
+  // Helper to get the default devices which can be used by the media request.
+  // Uses the first available devices if the default devices are not available.
+  // If the return list is empty, it means there is no available device on the
+  // OS.
+  virtual const blink::MediaStreamDevices& GetAudioCaptureDevices() const = 0;
+  virtual const blink::MediaStreamDevices& GetVideoCaptureDevices() const = 0;
+
+  // Helper to get the default devices which can be used by the media request.
+  // Uses the first available devices if the default devices are not available.
+  // If the return list is empty, it means there is no available device on the
+  // OS.
+  virtual void GetDefaultDevicesForBrowserContext(
+      content::BrowserContext* context,
+      bool audio,
+      bool video,
+      blink::MediaStreamDevices* devices) = 0;
+
+  // Helpers for picking particular requested devices, identified by raw id.
+  // If the device requested is not available it will return nullptr.
+  virtual const blink::MediaStreamDevice* GetRequestedAudioDevice(
+      const std::string& requested_audio_device_id) = 0;
+  virtual const blink::MediaStreamDevice* GetRequestedVideoDevice(
+      const std::string& requested_video_device_id) = 0;
+
+ protected:
+  virtual ~MediaStreamDeviceEnumerator() = default;
+};
+
+}  // namespace webrtc
+
+#endif  // COMPONENTS_WEBRTC_MEDIA_STREAM_DEVICE_ENUMERATOR_H_
diff --git a/components/webrtc/media_stream_device_enumerator_impl.cc b/components/webrtc/media_stream_device_enumerator_impl.cc
new file mode 100644
index 0000000..e11d49a
--- /dev/null
+++ b/components/webrtc/media_stream_device_enumerator_impl.cc
@@ -0,0 +1,81 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/webrtc/media_stream_device_enumerator_impl.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/media_capture_devices.h"
+
+using blink::MediaStreamDevices;
+using content::BrowserThread;
+using content::MediaCaptureDevices;
+
+namespace webrtc {
+
+namespace {
+
+// Finds a device in |devices| that has |device_id|, or nullptr if not found.
+const blink::MediaStreamDevice* FindDeviceWithId(
+    const MediaStreamDevices& devices,
+    const std::string& device_id) {
+  auto iter = devices.begin();
+  for (; iter != devices.end(); ++iter) {
+    if (iter->id == device_id)
+      return &(*iter);
+  }
+  return nullptr;
+}
+
+}  // namespace
+
+const MediaStreamDevices&
+MediaStreamDeviceEnumeratorImpl::GetAudioCaptureDevices() const {
+  return MediaCaptureDevices::GetInstance()->GetAudioCaptureDevices();
+}
+
+const MediaStreamDevices&
+MediaStreamDeviceEnumeratorImpl::GetVideoCaptureDevices() const {
+  return MediaCaptureDevices::GetInstance()->GetVideoCaptureDevices();
+}
+
+void MediaStreamDeviceEnumeratorImpl::GetDefaultDevicesForBrowserContext(
+    content::BrowserContext* context,
+    bool audio,
+    bool video,
+    MediaStreamDevices* devices) {
+  std::string default_device;
+  if (audio) {
+    const MediaStreamDevices& audio_devices = GetAudioCaptureDevices();
+    if (!audio_devices.empty())
+      devices->push_back(audio_devices.front());
+  }
+
+  if (video) {
+    const MediaStreamDevices& video_devices = GetVideoCaptureDevices();
+    if (!video_devices.empty())
+      devices->push_back(video_devices.front());
+  }
+}
+
+const blink::MediaStreamDevice*
+MediaStreamDeviceEnumeratorImpl::GetRequestedAudioDevice(
+    const std::string& requested_audio_device_id) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  return FindDeviceWithId(GetAudioCaptureDevices(), requested_audio_device_id);
+}
+
+const blink::MediaStreamDevice*
+MediaStreamDeviceEnumeratorImpl::GetRequestedVideoDevice(
+    const std::string& requested_video_device_id) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  return FindDeviceWithId(GetVideoCaptureDevices(), requested_video_device_id);
+}
+
+}  // namespace webrtc
diff --git a/components/webrtc/media_stream_device_enumerator_impl.h b/components/webrtc/media_stream_device_enumerator_impl.h
new file mode 100644
index 0000000..0f1f0f27
--- /dev/null
+++ b/components/webrtc/media_stream_device_enumerator_impl.h
@@ -0,0 +1,39 @@
+// Copyright 2020 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 COMPONENTS_WEBRTC_MEDIA_STREAM_DEVICE_ENUMERATOR_IMPL_H_
+#define COMPONENTS_WEBRTC_MEDIA_STREAM_DEVICE_ENUMERATOR_IMPL_H_
+
+#include "components/webrtc/media_stream_device_enumerator.h"
+
+namespace webrtc {
+
+// A default MediaStreamDeviceEnumerator that passes through to
+// content::MediaCaptureDevices.
+class MediaStreamDeviceEnumeratorImpl : public MediaStreamDeviceEnumerator {
+ public:
+  MediaStreamDeviceEnumeratorImpl() = default;
+  MediaStreamDeviceEnumeratorImpl(const MediaStreamDeviceEnumeratorImpl&) =
+      delete;
+  MediaStreamDeviceEnumeratorImpl& operator=(MediaStreamDeviceEnumeratorImpl&) =
+      delete;
+  ~MediaStreamDeviceEnumeratorImpl() override = default;
+
+  // MediaStreamDeviceEnumerator:
+  const blink::MediaStreamDevices& GetAudioCaptureDevices() const override;
+  const blink::MediaStreamDevices& GetVideoCaptureDevices() const override;
+  void GetDefaultDevicesForBrowserContext(
+      content::BrowserContext* context,
+      bool audio,
+      bool video,
+      blink::MediaStreamDevices* devices) override;
+  const blink::MediaStreamDevice* GetRequestedAudioDevice(
+      const std::string& requested_audio_device_id) override;
+  const blink::MediaStreamDevice* GetRequestedVideoDevice(
+      const std::string& requested_video_device_id) override;
+};
+
+}  // namespace webrtc
+
+#endif  // COMPONENTS_WEBRTC_MEDIA_STREAM_DEVICE_ENUMERATOR_IMPL_H_
diff --git a/chrome/browser/media/webrtc/media_stream_devices_controller.cc b/components/webrtc/media_stream_devices_controller.cc
similarity index 68%
rename from chrome/browser/media/webrtc/media_stream_devices_controller.cc
rename to components/webrtc/media_stream_devices_controller.cc
index 76908e0..7d5da98 100644
--- a/chrome/browser/media/webrtc/media_stream_devices_controller.cc
+++ b/components/webrtc/media_stream_devices_controller.cc
@@ -2,50 +2,32 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/media/webrtc/media_stream_devices_controller.h"
+#include "components/webrtc/media_stream_devices_controller.h"
 
-#include <algorithm>
 #include <memory>
 #include <utility>
 #include <vector>
 
 #include "base/bind.h"
-#include "base/callback_helpers.h"
-#include "base/metrics/histogram_macros.h"
-#include "base/strings/utf_string_conversions.h"
-#include "base/values.h"
-#include "chrome/browser/content_settings/tab_specific_content_settings.h"
-#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
-#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
-#include "chrome/browser/media/webrtc/media_stream_device_permissions.h"
-#include "chrome/browser/permissions/permission_manager_factory.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/common/pref_names.h"
-#include "components/content_settings/core/common/content_settings_pattern.h"
 #include "components/permissions/permission_manager.h"
 #include "components/permissions/permission_result.h"
-#include "components/permissions/permission_uma_util.h"
-#include "components/permissions/permission_util.h"
-#include "components/pref_registry/pref_registry_syncable.h"
-#include "components/prefs/scoped_user_pref_update.h"
-#include "components/url_formatter/elide_url.h"
-#include "content/public/browser/browser_thread.h"
+#include "components/permissions/permissions_client.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/web_contents.h"
 #include "content/public/common/origin_util.h"
-#include "extensions/common/constants.h"
 #include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom.h"
 
 #if defined(OS_ANDROID)
-#include "chrome/browser/android/android_theme_resources.h"
-#include "chrome/browser/permissions/permission_update_infobar_delegate_android.h"
+#include "components/permissions/android/android_permission_util.h"
 #include "ui/android/window_android.h"
-#else  // !defined(OS_ANDROID)
-#include "components/vector_icons/vector_icons.h"
 #endif
 
-using content::BrowserThread;
+using blink::MediaStreamDevices;
+
+namespace webrtc {
 
 namespace {
 
@@ -64,70 +46,34 @@
   return false;
 }
 
-bool HasAvailableDevices(ContentSettingsType content_type,
-                         const std::string& device_id) {
-  const blink::MediaStreamDevices* devices = nullptr;
-  if (content_type == ContentSettingsType::MEDIASTREAM_MIC) {
-    devices =
-        &MediaCaptureDevicesDispatcher::GetInstance()->GetAudioCaptureDevices();
-  } else if (content_type == ContentSettingsType::MEDIASTREAM_CAMERA) {
-    devices =
-        &MediaCaptureDevicesDispatcher::GetInstance()->GetVideoCaptureDevices();
-  } else {
-    NOTREACHED();
-  }
-
-  // TODO(tommi): It's kind of strange to have this here since if we fail this
-  // test, there'll be a UI shown that indicates to the user that access to
-  // non-existing audio/video devices has been denied.  The user won't have
-  // any way to change that but there will be a UI shown which indicates that
-  // access is blocked.
-  if (devices->empty())
-    return false;
-
-  // Note: we check device_id before dereferencing devices. If the requested
-  // device id is non-empty, then the corresponding device list must not be
-  // NULL.
-  if (!device_id.empty()) {
-    auto it = std::find_if(devices->begin(), devices->end(),
-                           [device_id](const blink::MediaStreamDevice& device) {
-                             return device.id == device_id;
-                           });
-    if (it == devices->end())
-      return false;
-  }
-
-  return true;
-}
-
 }  // namespace
 
 // static
 void MediaStreamDevicesController::RequestPermissions(
     const content::MediaStreamRequest& request,
-    content::MediaResponseCallback callback) {
+    MediaStreamDeviceEnumerator* enumerator,
+    ResultCallback callback) {
   content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
       request.render_process_id, request.render_frame_id);
   // The RFH may have been destroyed by the time the request is processed.
   if (!rfh) {
     std::move(callback).Run(
-        blink::MediaStreamDevices(),
-        blink::mojom::MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN,
-        std::unique_ptr<content::MediaStreamUI>());
+        MediaStreamDevices(),
+        blink::mojom::MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN, false,
+        {}, {});
     return;
   }
   content::WebContents* web_contents =
       content::WebContents::FromRenderFrameHost(rfh);
   std::unique_ptr<MediaStreamDevicesController> controller(
-      new MediaStreamDevicesController(web_contents, request,
+      new MediaStreamDevicesController(web_contents, enumerator, request,
                                        std::move(callback)));
 
-  Profile* profile =
-      Profile::FromBrowserContext(web_contents->GetBrowserContext());
   std::vector<ContentSettingsType> content_settings_types;
 
   permissions::PermissionManager* permission_manager =
-      PermissionManagerFactory::GetForProfile(profile);
+      permissions::PermissionsClient::Get()->GetPermissionManager(
+          web_contents->GetBrowserContext());
   bool will_prompt_for_audio = false;
   bool will_prompt_for_video = false;
 
@@ -176,6 +122,37 @@
           will_prompt_for_video));
 }
 
+MediaStreamDevicesController::~MediaStreamDevicesController() {
+  if (!callback_.is_null()) {
+    std::move(callback_).Run(
+        MediaStreamDevices(),
+        blink::mojom::MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN, false,
+        {}, {});
+  }
+}
+
+MediaStreamDevicesController::MediaStreamDevicesController(
+    content::WebContents* web_contents,
+    MediaStreamDeviceEnumerator* enumerator,
+    const content::MediaStreamRequest& request,
+    ResultCallback callback)
+    : web_contents_(web_contents),
+      enumerator_(enumerator),
+      request_(request),
+      callback_(std::move(callback)) {
+  DCHECK(content::IsOriginSecure(request_.security_origin) ||
+         request_.request_type == blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY);
+
+  if (!enumerator_)
+    enumerator_ = &owned_enumerator_;
+
+  denial_reason_ = blink::mojom::MediaStreamRequestResult::OK;
+  audio_setting_ = GetContentSetting(ContentSettingsType::MEDIASTREAM_MIC,
+                                     request, &denial_reason_);
+  video_setting_ = GetContentSetting(ContentSettingsType::MEDIASTREAM_CAMERA,
+                                     request, &denial_reason_);
+}
+
 void MediaStreamDevicesController::RequestAndroidPermissionsIfNeeded(
     content::WebContents* web_contents,
     std::unique_ptr<MediaStreamDevicesController> controller,
@@ -191,16 +168,21 @@
   // requested.
   // If the user was already prompted for mic (|did_prompt_for_audio| flag), we
   // would have requested Android permission at that point.
-  if (!did_prompt_for_audio &&
-      controller->ShouldRequestAudio() &&
+  if (!did_prompt_for_audio && controller->ShouldRequestAudio() &&
       responses.front() == CONTENT_SETTING_ALLOW) {
     content_settings_types.push_back(ContentSettingsType::MEDIASTREAM_MIC);
   }
 
   // If the user was already prompted for camera (|did_prompt_for_video| flag),
   // we would have requested Android permission at that point.
-  if (!did_prompt_for_video &&
-      controller->ShouldRequestVideo() &&
+  if (!did_prompt_for_video && controller->ShouldRequestVideo() &&
+      responses.back() == CONTENT_SETTING_ALLOW) {
+    content_settings_types.push_back(ContentSettingsType::MEDIASTREAM_CAMERA);
+  }
+
+  // If the user was already prompted for camera (|did_prompt_for_video| flag),
+  // we would have requested Android permission at that point.
+  if (!did_prompt_for_video && controller->ShouldRequestVideo() &&
       responses.back() == CONTENT_SETTING_ALLOW) {
     content_settings_types.push_back(ContentSettingsType::MEDIASTREAM_CAMERA);
   }
@@ -209,20 +191,22 @@
     return;
   }
 
-  ShowPermissionInfoBarState show_permission_infobar_state =
-      PermissionUpdateInfoBarDelegate::ShouldShowPermissionInfoBar(
-          web_contents, content_settings_types);
-  switch (show_permission_infobar_state) {
-    case ShowPermissionInfoBarState::NO_NEED_TO_SHOW_PERMISSION_INFOBAR:
+  permissions::PermissionRepromptState reprompt_state =
+      permissions::ShouldRepromptUserForPermissions(web_contents,
+                                                    content_settings_types);
+  switch (reprompt_state) {
+    case permissions::PermissionRepromptState::kNoNeed:
       controller->PromptAnsweredGroupedRequest(responses);
       return;
-    case ShowPermissionInfoBarState::SHOW_PERMISSION_INFOBAR:
-      PermissionUpdateInfoBarDelegate::Create(
+
+    case permissions::PermissionRepromptState::kShow:
+      permissions::PermissionsClient::Get()->RepromptForAndroidPermissions(
           web_contents, content_settings_types,
           base::BindOnce(&MediaStreamDevicesController::AndroidOSPromptAnswered,
                          std::move(controller), responses));
       return;
-    case ShowPermissionInfoBarState::CANNOT_SHOW_PERMISSION_INFOBAR: {
+
+    case permissions::PermissionRepromptState::kCannotShow: {
       std::vector<ContentSetting> blocked_responses(responses.size(),
                                                     CONTENT_SETTING_BLOCK);
       controller->PromptAnsweredGroupedRequest(blocked_responses);
@@ -256,79 +240,6 @@
 }
 #endif  // defined(OS_ANDROID)
 
-// static
-void MediaStreamDevicesController::RegisterProfilePrefs(
-    user_prefs::PrefRegistrySyncable* prefs) {
-  prefs->RegisterBooleanPref(prefs::kVideoCaptureAllowed, true);
-  prefs->RegisterBooleanPref(prefs::kAudioCaptureAllowed, true);
-  prefs->RegisterListPref(prefs::kVideoCaptureAllowedUrls);
-  prefs->RegisterListPref(prefs::kAudioCaptureAllowedUrls);
-}
-
-MediaStreamDevicesController::~MediaStreamDevicesController() {
-  if (!callback_.is_null()) {
-    std::move(callback_).Run(
-        blink::MediaStreamDevices(),
-        blink::mojom::MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN,
-        std::unique_ptr<content::MediaStreamUI>());
-  }
-}
-
-void MediaStreamDevicesController::PromptAnsweredGroupedRequest(
-    const std::vector<ContentSetting>& responses) {
-  // The audio setting will always be the first one in the vector, if it was
-  // requested.
-  bool blocked_by_feature_policy = ShouldRequestAudio() || ShouldRequestVideo();
-  if (ShouldRequestAudio()) {
-    audio_setting_ = responses.front();
-    blocked_by_feature_policy &=
-        audio_setting_ == CONTENT_SETTING_BLOCK &&
-        PermissionIsBlockedForReason(
-            ContentSettingsType::MEDIASTREAM_MIC,
-            permissions::PermissionStatusSource::FEATURE_POLICY);
-  }
-
-  if (ShouldRequestVideo()) {
-    video_setting_ = responses.back();
-    blocked_by_feature_policy &=
-        video_setting_ == CONTENT_SETTING_BLOCK &&
-        PermissionIsBlockedForReason(
-            ContentSettingsType::MEDIASTREAM_CAMERA,
-            permissions::PermissionStatusSource::FEATURE_POLICY);
-  }
-
-  for (ContentSetting response : responses) {
-    if (response == CONTENT_SETTING_BLOCK)
-      denial_reason_ =
-          blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
-    else if (response == CONTENT_SETTING_ASK)
-      denial_reason_ =
-          blink::mojom::MediaStreamRequestResult::PERMISSION_DISMISSED;
-  }
-
-  RunCallback(blocked_by_feature_policy);
-}
-
-MediaStreamDevicesController::MediaStreamDevicesController(
-    content::WebContents* web_contents,
-    const content::MediaStreamRequest& request,
-    content::MediaResponseCallback callback)
-    : web_contents_(web_contents),
-      request_(request),
-      callback_(std::move(callback)) {
-  DCHECK(content::IsOriginSecure(request_.security_origin) ||
-         request_.request_type == blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY);
-
-  profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());
-  content_settings_ = TabSpecificContentSettings::FromWebContents(web_contents);
-
-  denial_reason_ = blink::mojom::MediaStreamRequestResult::OK;
-  audio_setting_ = GetContentSetting(ContentSettingsType::MEDIASTREAM_MIC,
-                                     request, &denial_reason_);
-  video_setting_ = GetContentSetting(ContentSettingsType::MEDIASTREAM_CAMERA,
-                                     request, &denial_reason_);
-}
-
 bool MediaStreamDevicesController::ShouldRequestAudio() const {
   return audio_setting_ == CONTENT_SETTING_ASK;
 }
@@ -337,19 +248,19 @@
   return video_setting_ == CONTENT_SETTING_ASK;
 }
 
-blink::MediaStreamDevices MediaStreamDevicesController::GetDevices(
+MediaStreamDevices MediaStreamDevicesController::GetDevices(
     ContentSetting audio_setting,
     ContentSetting video_setting) {
   bool audio_allowed = audio_setting == CONTENT_SETTING_ALLOW;
   bool video_allowed = video_setting == CONTENT_SETTING_ALLOW;
 
   if (!audio_allowed && !video_allowed)
-    return blink::MediaStreamDevices();
+    return MediaStreamDevices();
 
-  blink::MediaStreamDevices devices;
+  MediaStreamDevices devices;
   switch (request_.request_type) {
     case blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY: {
-      const blink::MediaStreamDevice* device = NULL;
+      const blink::MediaStreamDevice* device = nullptr;
       // For open device request, when requested device_id is empty, pick
       // the first available of the given type. If requested device_id is
       // not empty, return the desired device if it's available. Otherwise,
@@ -360,12 +271,13 @@
         DCHECK_EQ(blink::mojom::MediaStreamType::NO_SERVICE,
                   request_.video_type);
         if (!request_.requested_audio_device_id.empty()) {
-          device =
-              MediaCaptureDevicesDispatcher::GetInstance()
-                  ->GetRequestedAudioDevice(request_.requested_audio_device_id);
+          device = enumerator_->GetRequestedAudioDevice(
+              request_.requested_audio_device_id);
         } else {
-          device = MediaCaptureDevicesDispatcher::GetInstance()
-                       ->GetFirstAvailableAudioDevice();
+          const blink::MediaStreamDevices& audio_devices =
+              enumerator_->GetAudioCaptureDevices();
+          if (!audio_devices.empty())
+            device = &audio_devices.front();
         }
       } else if (video_allowed &&
                  request_.video_type ==
@@ -374,12 +286,13 @@
                   request_.audio_type);
         // Pepper API opens only one device at a time.
         if (!request_.requested_video_device_id.empty()) {
-          device =
-              MediaCaptureDevicesDispatcher::GetInstance()
-                  ->GetRequestedVideoDevice(request_.requested_video_device_id);
+          device = enumerator_->GetRequestedVideoDevice(
+              request_.requested_video_device_id);
         } else {
-          device = MediaCaptureDevicesDispatcher::GetInstance()
-                       ->GetFirstAvailableVideoDevice();
+          const blink::MediaStreamDevices& video_devices =
+              enumerator_->GetVideoCaptureDevices();
+          if (!video_devices.empty())
+            device = &video_devices.front();
         }
       }
       if (device)
@@ -393,8 +306,8 @@
       // Get the exact audio or video device if an id is specified.
       if (audio_allowed && !request_.requested_audio_device_id.empty()) {
         const blink::MediaStreamDevice* audio_device =
-            MediaCaptureDevicesDispatcher::GetInstance()
-                ->GetRequestedAudioDevice(request_.requested_audio_device_id);
+            enumerator_->GetRequestedAudioDevice(
+                request_.requested_audio_device_id);
         if (audio_device) {
           devices.push_back(*audio_device);
           get_default_audio_device = false;
@@ -402,8 +315,8 @@
       }
       if (video_allowed && !request_.requested_video_device_id.empty()) {
         const blink::MediaStreamDevice* video_device =
-            MediaCaptureDevicesDispatcher::GetInstance()
-                ->GetRequestedVideoDevice(request_.requested_video_device_id);
+            enumerator_->GetRequestedVideoDevice(
+                request_.requested_video_device_id);
         if (video_device) {
           devices.push_back(*video_device);
           get_default_video_device = false;
@@ -413,16 +326,17 @@
       // If either or both audio and video devices were requested but not
       // specified by id, get the default devices.
       if (get_default_audio_device || get_default_video_device) {
-        MediaCaptureDevicesDispatcher::GetInstance()
-            ->GetDefaultDevicesForProfile(profile_, get_default_audio_device,
-                                          get_default_video_device, &devices);
+        enumerator_->GetDefaultDevicesForBrowserContext(
+            web_contents_->GetBrowserContext(), get_default_audio_device,
+            get_default_video_device, &devices);
       }
       break;
     }
     case blink::MEDIA_DEVICE_ACCESS: {
       // Get the default devices for the request.
-      MediaCaptureDevicesDispatcher::GetInstance()->GetDefaultDevicesForProfile(
-          profile_, audio_allowed, video_allowed, &devices);
+      enumerator_->GetDefaultDevicesForBrowserContext(
+          web_contents_->GetBrowserContext(), audio_allowed, video_allowed,
+          &devices);
       break;
     }
     case blink::MEDIA_DEVICE_UPDATE: {
@@ -435,18 +349,9 @@
 }
 
 void MediaStreamDevicesController::RunCallback(bool blocked_by_feature_policy) {
-  CHECK(!callback_.is_null());
+  CHECK(callback_);
 
-  // If the kill switch is, or the request was blocked because of feature
-  // policy we don't update the tab context.
-  if (denial_reason_ !=
-          blink::mojom::MediaStreamRequestResult::KILL_SWITCH_ON &&
-      !blocked_by_feature_policy) {
-    UpdateTabSpecificContentSettings(audio_setting_, video_setting_);
-  }
-
-  blink::MediaStreamDevices devices;
-
+  MediaStreamDevices devices;
   // If all requested permissions are allowed then the callback should report
   // success, otherwise we report |denial_reason_|.
   blink::mojom::MediaStreamRequestResult request_result =
@@ -466,62 +371,8 @@
     request_result = denial_reason_;
   }
 
-  std::unique_ptr<content::MediaStreamUI> ui;
-  if (!devices.empty()) {
-    ui = MediaCaptureDevicesDispatcher::GetInstance()
-             ->GetMediaStreamCaptureIndicator()
-             ->RegisterMediaStream(web_contents_, devices);
-  }
-  std::move(callback_).Run(devices, request_result, std::move(ui));
-}
-
-void MediaStreamDevicesController::UpdateTabSpecificContentSettings(
-    ContentSetting audio_setting,
-    ContentSetting video_setting) const {
-  if (!content_settings_)
-    return;
-
-  TabSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
-      TabSpecificContentSettings::MICROPHONE_CAMERA_NOT_ACCESSED;
-  std::string selected_audio_device;
-  std::string selected_video_device;
-  std::string requested_audio_device = request_.requested_audio_device_id;
-  std::string requested_video_device = request_.requested_video_device_id;
-
-  // TODO(raymes): Why do we use the defaults here for the selected devices?
-  // Shouldn't we just use the devices that were actually selected?
-  PrefService* prefs = Profile::FromBrowserContext(
-                           web_contents_->GetBrowserContext())->GetPrefs();
-  if (audio_setting != CONTENT_SETTING_DEFAULT) {
-    selected_audio_device =
-        requested_audio_device.empty()
-            ? prefs->GetString(prefs::kDefaultAudioCaptureDevice)
-            : requested_audio_device;
-    microphone_camera_state |=
-        TabSpecificContentSettings::MICROPHONE_ACCESSED |
-        (audio_setting == CONTENT_SETTING_ALLOW
-             ? 0
-             : TabSpecificContentSettings::MICROPHONE_BLOCKED);
-  }
-
-  if (video_setting != CONTENT_SETTING_DEFAULT) {
-    selected_video_device =
-        requested_video_device.empty()
-            ? prefs->GetString(prefs::kDefaultVideoCaptureDevice)
-            : requested_video_device;
-    microphone_camera_state |=
-        TabSpecificContentSettings::CAMERA_ACCESSED |
-        (video_setting == CONTENT_SETTING_ALLOW
-             ? 0
-             : TabSpecificContentSettings::CAMERA_BLOCKED);
-  }
-
-  content_settings_->OnMediaStreamPermissionSet(
-      PermissionManagerFactory::GetForProfile(profile_)->GetCanonicalOrigin(
-          ContentSettingsType::MEDIASTREAM_CAMERA, request_.security_origin,
-          web_contents_->GetLastCommittedURL()),
-      microphone_camera_state, selected_audio_device, selected_video_device,
-      requested_audio_device, requested_video_device);
+  std::move(callback_).Run(devices, request_result, blocked_by_feature_policy,
+                           audio_setting_, video_setting_);
 }
 
 ContentSetting MediaStreamDevicesController::GetContentSetting(
@@ -572,8 +423,8 @@
     return false;
 
   std::vector<std::string> android_permissions;
-  permissions::PermissionUtil::GetAndroidPermissionsForContentSetting(
-      content_type, &android_permissions);
+  permissions::GetAndroidPermissionsForContentSetting(content_type,
+                                                      &android_permissions);
   for (const auto& android_permission : android_permissions) {
     if (!window_android->HasPermission(android_permission) &&
         !window_android->CanRequestPermission(android_permission)) {
@@ -597,7 +448,8 @@
   content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
       request_.render_process_id, request_.render_frame_id);
   permissions::PermissionResult result =
-      PermissionManagerFactory::GetForProfile(profile_)
+      permissions::PermissionsClient::Get()
+          ->GetPermissionManager(web_contents_->GetBrowserContext())
           ->GetPermissionStatusForFrame(content_type, rfh,
                                         request_.security_origin);
   if (result.source == reason) {
@@ -606,3 +458,75 @@
   }
   return false;
 }
+
+void MediaStreamDevicesController::PromptAnsweredGroupedRequest(
+    const std::vector<ContentSetting>& responses) {
+  // The audio setting will always be the first one in the vector, if it was
+  // requested.
+  bool blocked_by_feature_policy = ShouldRequestAudio() || ShouldRequestVideo();
+  if (ShouldRequestAudio()) {
+    audio_setting_ = responses.front();
+    blocked_by_feature_policy &=
+        audio_setting_ == CONTENT_SETTING_BLOCK &&
+        PermissionIsBlockedForReason(
+            ContentSettingsType::MEDIASTREAM_MIC,
+            permissions::PermissionStatusSource::FEATURE_POLICY);
+  }
+
+  if (ShouldRequestVideo()) {
+    video_setting_ = responses.back();
+    blocked_by_feature_policy &=
+        video_setting_ == CONTENT_SETTING_BLOCK &&
+        PermissionIsBlockedForReason(
+            ContentSettingsType::MEDIASTREAM_CAMERA,
+            permissions::PermissionStatusSource::FEATURE_POLICY);
+  }
+
+  for (ContentSetting response : responses) {
+    if (response == CONTENT_SETTING_BLOCK)
+      denial_reason_ =
+          blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
+    else if (response == CONTENT_SETTING_ASK)
+      denial_reason_ =
+          blink::mojom::MediaStreamRequestResult::PERMISSION_DISMISSED;
+  }
+
+  RunCallback(blocked_by_feature_policy);
+}
+
+bool MediaStreamDevicesController::HasAvailableDevices(
+    ContentSettingsType content_type,
+    const std::string& device_id) const {
+  const MediaStreamDevices* devices = nullptr;
+  if (content_type == ContentSettingsType::MEDIASTREAM_MIC) {
+    devices = &enumerator_->GetAudioCaptureDevices();
+  } else if (content_type == ContentSettingsType::MEDIASTREAM_CAMERA) {
+    devices = &enumerator_->GetVideoCaptureDevices();
+  } else {
+    NOTREACHED();
+  }
+
+  // TODO(tommi): It's kind of strange to have this here since if we fail this
+  // test, there'll be a UI shown that indicates to the user that access to
+  // non-existing audio/video devices has been denied.  The user won't have
+  // any way to change that but there will be a UI shown which indicates that
+  // access is blocked.
+  if (devices->empty())
+    return false;
+
+  // Note: we check device_id before dereferencing devices. If the requested
+  // device id is non-empty, then the corresponding device list must not be
+  // nullptr.
+  if (!device_id.empty()) {
+    auto it = std::find_if(devices->begin(), devices->end(),
+                           [device_id](const blink::MediaStreamDevice& device) {
+                             return device.id == device_id;
+                           });
+    if (it == devices->end())
+      return false;
+  }
+
+  return true;
+}
+
+}  // namespace webrtc
diff --git a/chrome/browser/media/webrtc/media_stream_devices_controller.h b/components/webrtc/media_stream_devices_controller.h
similarity index 65%
rename from chrome/browser/media/webrtc/media_stream_devices_controller.h
rename to components/webrtc/media_stream_devices_controller.h
index 07ef0cb..0c6cc67f 100644
--- a/chrome/browser/media/webrtc/media_stream_devices_controller.h
+++ b/components/webrtc/media_stream_devices_controller.h
@@ -2,24 +2,20 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_DEVICES_CONTROLLER_H_
-#define CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_DEVICES_CONTROLLER_H_
+#ifndef COMPONENTS_WEBRTC_MEDIA_STREAM_DEVICES_CONTROLLER_H_
+#define COMPONENTS_WEBRTC_MEDIA_STREAM_DEVICES_CONTROLLER_H_
 
 #include <map>
 #include <string>
 
 #include "base/callback.h"
-#include "base/macros.h"
 #include "build/build_config.h"
 #include "components/content_settings/core/common/content_settings.h"
+#include "components/webrtc/media_stream_device_enumerator_impl.h"
 #include "content/public/browser/media_stream_request.h"
 #include "third_party/blink/public/common/mediastream/media_stream_request.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
 
-class MediaStreamDevicesController;
-class Profile;
-class TabSpecificContentSettings;
-
 namespace permissions {
 enum class PermissionStatusSource;
 }
@@ -28,22 +24,38 @@
 class WebContents;
 }
 
-namespace user_prefs {
-class PrefRegistrySyncable;
-}
+namespace webrtc {
 
-namespace policy {
-class MediaStreamDevicesControllerBrowserTest;
-}
+class MediaStreamDeviceEnumerator;
 
-namespace test {
-class MediaStreamDevicesControllerTestApi;
-}
-
+// A class that provides logic for microphone/camera requests originating in the
+// renderer.
 class MediaStreamDevicesController {
  public:
+  typedef base::OnceCallback<void(const blink::MediaStreamDevices& devices,
+                                  blink::mojom::MediaStreamRequestResult result,
+                                  bool blocked_by_feature_policy,
+                                  ContentSetting audio_setting,
+                                  ContentSetting video_setting)>
+      ResultCallback;
+
+  // Requests the mic/camera permissions described in |request|, using
+  // |enumerator| to list the system's devices. The result of the request is
+  // synchronously or asynchronously returned via |callback|.
   static void RequestPermissions(const content::MediaStreamRequest& request,
-                                 content::MediaResponseCallback callback);
+                                 MediaStreamDeviceEnumerator* enumerator,
+                                 ResultCallback callback);
+
+  ~MediaStreamDevicesController();
+
+ private:
+  MediaStreamDevicesController(content::WebContents* web_contents,
+                               MediaStreamDeviceEnumerator* enumerator,
+                               const content::MediaStreamRequest& request,
+                               ResultCallback callback);
+  MediaStreamDevicesController(const MediaStreamDevicesController&) = delete;
+  MediaStreamDevicesController& operator=(MediaStreamDevicesController&) =
+      delete;
 
   static void RequestAndroidPermissionsIfNeeded(
       content::WebContents* web_contents,
@@ -60,24 +72,6 @@
       bool android_prompt_granted);
 #endif  // defined(OS_ANDROID)
 
-  // Registers the prefs backing the audio and video policies.
-  static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
-
-  ~MediaStreamDevicesController();
-
-  // Called when a permission prompt is answered through the PermissionManager.
-  void PromptAnsweredGroupedRequest(
-      const std::vector<ContentSetting>& responses);
-
- private:
-  friend class MediaStreamDevicesControllerTest;
-  friend class test::MediaStreamDevicesControllerTestApi;
-  friend class policy::MediaStreamDevicesControllerBrowserTest;
-
-  MediaStreamDevicesController(content::WebContents* web_contents,
-                               const content::MediaStreamRequest& request,
-                               content::MediaResponseCallback callback);
-
   // Returns true if audio/video should be requested through the
   // PermissionManager. We won't try to request permission if the request is
   // already blocked for some other reason, e.g. there are no devices available.
@@ -111,6 +105,13 @@
       ContentSettingsType content_type,
       permissions::PermissionStatusSource reason) const;
 
+  // Called when a permission prompt is answered through the PermissionManager.
+  void PromptAnsweredGroupedRequest(
+      const std::vector<ContentSetting>& responses);
+
+  bool HasAvailableDevices(ContentSettingsType content_type,
+                           const std::string& device_id) const;
+
   // The current state of the audio/video content settings which may be updated
   // through the lifetime of the request.
   ContentSetting audio_setting_;
@@ -119,24 +120,21 @@
 
   content::WebContents* web_contents_;
 
-  // The owner of this class needs to make sure it does not outlive the profile.
-  Profile* profile_;
+  // The object which lists available devices.
+  MediaStreamDeviceEnumerator* enumerator_;
 
-  // Weak pointer to the tab specific content settings of the tab for which the
-  // MediaStreamDevicesController was created. The tab specific content
-  // settings are associated with a the web contents of the tab. The
-  // MediaStreamDeviceController must not outlive the web contents for which it
-  // was created.
-  TabSpecificContentSettings* content_settings_;
+  // This enumerator is used as |enumerator_| when the instance passed into the
+  // constructor is null.
+  MediaStreamDeviceEnumeratorImpl owned_enumerator_;
 
   // The original request for access to devices.
   const content::MediaStreamRequest request_;
 
-  // The callback that needs to be Run to notify WebRTC of whether access to
+  // The callback that needs to be run to notify WebRTC of whether access to
   // audio/video devices was granted or not.
-  content::MediaResponseCallback callback_;
-
-  DISALLOW_COPY_AND_ASSIGN(MediaStreamDevicesController);
+  ResultCallback callback_;
 };
 
-#endif  // CHROME_BROWSER_MEDIA_WEBRTC_MEDIA_STREAM_DEVICES_CONTROLLER_H_
+}  // namespace webrtc
+
+#endif  // COMPONENTS_WEBRTC_MEDIA_STREAM_DEVICES_CONTROLLER_H_
diff --git a/content/browser/child_process_security_policy_impl.cc b/content/browser/child_process_security_policy_impl.cc
index bcd42e7..d2e2947 100644
--- a/content/browser/child_process_security_policy_impl.cc
+++ b/content/browser/child_process_security_policy_impl.cc
@@ -1891,12 +1891,12 @@
 bool ChildProcessSecurityPolicyImpl::DoesOriginRequestOptInIsolation(
     const IsolationContext& isolation_context,
     const url::Origin& origin) {
-  // IsolationOptIn is only available when OriginPolicy is enabled.
-  // We only isolate HTTPS, so early-out if we see other schemes.
-  if (!base::FeatureList::IsEnabled(features::kOriginPolicy) ||
-      !origin.GetURL().SchemeIs(url::kHttpsScheme)) {
+  if (!IsOptInOriginIsolationEnabled())
     return false;
-  }
+
+  // We only isolate HTTPS, so early-out if we see other schemes.
+  if (!origin.GetURL().SchemeIs(url::kHttpsScheme))
+    return false;
 
   base::AutoLock origins_isolation_opt_in_lock(origins_isolation_opt_in_lock_);
   // See if the same origin exists in the BrowsingInstance already, and if so
@@ -1922,6 +1922,12 @@
   return origin_isolation_opt_ins_.contains(origin);
 }
 
+// static
+bool ChildProcessSecurityPolicyImpl::IsOptInOriginIsolationEnabled() {
+  return base::FeatureList::IsEnabled(features::kOriginPolicy) ||
+         base::FeatureList::IsEnabled(features::kOriginIsolationHeader);
+}
+
 void ChildProcessSecurityPolicyImpl::
     RemoveOptInIsolatedOriginsForBrowsingInstance(
         const IsolationContext& isolation_context) {
diff --git a/content/browser/child_process_security_policy_impl.h b/content/browser/child_process_security_policy_impl.h
index 1969a69..4df6457 100644
--- a/content/browser/child_process_security_policy_impl.h
+++ b/content/browser/child_process_security_policy_impl.h
@@ -255,6 +255,11 @@
       const IsolationContext& isolation_context,
       const url::Origin& origin);
 
+  // Returns true if web-exposed mechanisms for opting in to isolated origins
+  // are enabled (namely, either via origin policy or via the Origin-Isolation
+  // header).
+  static bool IsOptInOriginIsolationEnabled();
+
   // This function manages updates to the master list of origins requesting
   // isolation, e.g. via an OriginPolicy.
   void UpdateOriginIsolationOptInListIfNecessary(const url::Origin& origin,
diff --git a/content/browser/frame_host/navigation_request.cc b/content/browser/frame_host/navigation_request.cc
index f0b9f96..a878b8a 100644
--- a/content/browser/frame_host/navigation_request.cc
+++ b/content/browser/frame_host/navigation_request.cc
@@ -1692,30 +1692,35 @@
   WillRedirectRequest(common_params_->referrer->url, expected_process);
 }
 
-void NavigationRequest::CheckForOriginPolicyIsolationOptIn(
+void NavigationRequest::CheckForIsolationOptIn(
     const GURL& url,
     const network::mojom::URLResponseHead* response) {
-  // IsolationOptIn is only available when OriginPolicy is enabled.
-  if (!base::FeatureList::IsEnabled(features::kOriginPolicy))
+  if (!response)
     return;
 
-  bool requests_origin_isolation = false;
-  if (response && response->origin_policy) {
-    const network::OriginPolicy& origin_policy =
-        response->origin_policy.value();
-    // For now, we'll take the presence of any isolation_optin_hints value
-    // as an indication to opt-in.
-    requests_origin_isolation =
-        origin_policy.state == network::OriginPolicyState::kLoaded &&
-        origin_policy.contents->isolation_optin_hints.has_value();
-  }
+  // For now we only check for the presence of hints; we do not yet act on the
+  // specific hints.
+  const bool requests_via_origin_policy =
+      base::FeatureList::IsEnabled(features::kOriginPolicy) &&
+      response->origin_policy &&
+      response->origin_policy->state == network::OriginPolicyState::kLoaded &&
+      response->origin_policy->contents->isolation_optin_hints.has_value();
+
+  // TODO(https://crbug.com/1066930): For now we just check the presence of the
+  // header; we do not parse/validate it. When we do, that will have to be
+  // outside the browser process.
+  const bool requests_via_header =
+      base::FeatureList::IsEnabled(features::kOriginIsolationHeader) &&
+      response->headers && response->headers->HasHeader("origin-isolation");
+
+  const bool requests_origin_isolation =
+      requests_via_origin_policy || requests_via_header;
+
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
   url::Origin origin = url::Origin::Create(url);
   // We need to update the master list even if |requests_origin_isolation| is
-  // false, since we need to maintain the master opt-in list according to the
-  // most recently seen OriginPolicy.
-  // TODO(wjmaclean): when we start to support versioning for OriginPolicies,
-  // will we need to key the master list accordingly?
+  // false, since we need to maintain the opt-in list according to the most
+  // recently seen opt-in.
   policy->UpdateOriginIsolationOptInListIfNecessary(origin,
                                                     requests_origin_isolation);
 }
@@ -1902,10 +1907,10 @@
     }
   }
 
-  // The navigation may have encountered an OriginPolicy that requests isolation
-  // for the url's origin. Before we pick the renderer, make sure we update the
-  // master list for origin-isolation opt-ins.
-  CheckForOriginPolicyIsolationOptIn(common_params().url, response());
+  // The navigation may have encountered an origin policy or Origin-Isolation
+  // header that requests isolation for the url's origin. Before we pick the
+  // renderer, make sure we update the origin-isolation opt-ins appropriately.
+  CheckForIsolationOptIn(common_params().url, response());
 
   // Select an appropriate renderer to commit the navigation.
   if (IsServedFromBackForwardCache()) {
@@ -2777,8 +2782,7 @@
 
 void NavigationRequest::RenderProcessExited(
     RenderProcessHost* host,
-    const ChildProcessTerminationInfo& info) {
-}
+    const ChildProcessTerminationInfo& info) {}
 
 void NavigationRequest::UpdateSiteURL(
     RenderProcessHost* post_redirect_process) {
diff --git a/content/browser/frame_host/navigation_request.h b/content/browser/frame_host/navigation_request.h
index 5e47c91..9f41feb 100644
--- a/content/browser/frame_host/navigation_request.h
+++ b/content/browser/frame_host/navigation_request.h
@@ -580,12 +580,11 @@
           navigation_initiator,
       RenderFrameHostImpl* rfh_restored_from_back_forward_cache);
 
-  // Checks if the OriginPolicy in a NavigationRequest's response contains a
-  // request to isolate the url's origin, and if so registers it with the global
-  // origin isolation map.
-  void CheckForOriginPolicyIsolationOptIn(
-      const GURL& url,
-      const network::mojom::URLResponseHead* response);
+  // Checks if the response requests an isolated origin (using either origin
+  // policy or the Origin-Isolation header), and if so opts in the origin to be
+  // isolated.
+  void CheckForIsolationOptIn(const GURL& url,
+                              const network::mojom::URLResponseHead* response);
 
   // NavigationURLLoaderDelegate implementation.
   void OnRequestRedirected(
diff --git a/content/browser/isolated_origin_browsertest.cc b/content/browser/isolated_origin_browsertest.cc
index 923f1e90..0ecdf245 100644
--- a/content/browser/isolated_origin_browsertest.cc
+++ b/content/browser/isolated_origin_browsertest.cc
@@ -111,11 +111,13 @@
   DISALLOW_COPY_AND_ASSIGN(IsolatedOriginTest);
 };
 
-class OriginIsolationOptInTest : public IsolatedOriginTestBase {
+// Base class used for server-based origin isolation opt-in tests to handle the
+// server responses and other common infrastructure.
+class OriginIsolationOptInServerTest : public IsolatedOriginTestBase {
  public:
-  OriginIsolationOptInTest()
+  OriginIsolationOptInServerTest()
       : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
-  ~OriginIsolationOptInTest() override = default;
+  ~OriginIsolationOptInServerTest() override = default;
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
     IsolatedOriginTestBase::SetUpCommandLine(command_line);
@@ -125,14 +127,15 @@
     //  --site-per-process isn't the default, such as Android.
     IsolateAllSitesForTesting(command_line);
     command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
-    feature_list_.InitAndEnableFeature(features::kOriginPolicy);
+    feature_list_.InitAndEnableFeature(feature_switch());
   }
 
   void SetUpOnMainThread() override {
     IsolatedOriginTestBase::SetUpOnMainThread();
     https_server()->AddDefaultHandlers(GetTestDataFilePath());
-    https_server()->RegisterRequestHandler(base::BindRepeating(
-        &OriginIsolationOptInTest::HandleResponse, base::Unretained(this)));
+    https_server()->RegisterRequestHandler(
+        base::BindRepeating(&OriginIsolationOptInServerTest::HandleResponse,
+                            base::Unretained(this)));
     ASSERT_TRUE(https_server()->Start());
     host_resolver()->AddRule("*", "127.0.0.1");
     embedded_test_server()->StartAcceptingConnections();
@@ -143,22 +146,56 @@
     IsolatedOriginTestBase::TearDownOnMainThread();
   }
 
-  void SetOriginPolicyManifest(const std::string& manifest) {
-    origin_policy_manifest_ = manifest;
-  }
-
   // Need an https server because
   // OriginPolicyThrottle::ShouldRequestOriginPolicy() will return false
   // otherwise.
   net::EmbeddedTestServer* https_server() { return &https_server_; }
 
- private:
-  std::unique_ptr<net::test_server::HttpResponse> HandleResponse(
-      const net::test_server::HttpRequest& request) {
-    std::unique_ptr<net::test_server::BasicHttpResponse> response =
-        std::make_unique<net::test_server::BasicHttpResponse>();
+  bool DoesOriginRequestOptInIsolation(const url::Origin& origin) {
+    auto* site_instance = static_cast<SiteInstanceImpl*>(
+        shell()->web_contents()->GetMainFrame()->GetSiteInstance());
 
-    // TODO(wjmaclean): document how this handler works.
+    return ChildProcessSecurityPolicyImpl::GetInstance()
+        ->DoesOriginRequestOptInIsolation(site_instance->GetIsolationContext(),
+                                          origin);
+  }
+
+ protected:
+  virtual const base::Feature& feature_switch() = 0;
+  virtual std::unique_ptr<net::test_server::HttpResponse> HandleResponse(
+      const net::test_server::HttpRequest& request) = 0;
+
+ private:
+  net::EmbeddedTestServer https_server_;
+  base::test::ScopedFeatureList feature_list_;
+
+  DISALLOW_COPY_AND_ASSIGN(OriginIsolationOptInServerTest);
+};
+
+// Tests of opt-in origin isolation which use origin policy as the opt-in
+// mechanism. Most tests for the overall feature are in this class, but see also
+// OriginIsolationOptInHeaderTest for tests that verify headers can be used as
+// an opt-in mechanism as well.
+class OriginIsolationOptInOriginPolicyTest
+    : public OriginIsolationOptInServerTest {
+ public:
+  OriginIsolationOptInOriginPolicyTest() = default;
+
+  void SetOriginPolicyManifest(const std::string& manifest) {
+    origin_policy_manifest_ = manifest;
+  }
+
+ protected:
+  const base::Feature& feature_switch() override {
+    return features::kOriginPolicy;
+  }
+
+  std::unique_ptr<net::test_server::HttpResponse> HandleResponse(
+      const net::test_server::HttpRequest& request) override {
+    auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+
+    // Ensures requests to /isolate_me request that the origin policy be
+    // applied.
     if (request.relative_url == "/isolate_me") {
       response->set_code(net::HTTP_OK);
       response->set_content_type("text/html");
@@ -178,19 +215,89 @@
 
     // If we return nullptr, then the server will go ahead and actually serve
     // the file.
-    return std::unique_ptr<net::test_server::HttpResponse>();
+    return nullptr;
   }
 
-  net::EmbeddedTestServer https_server_;
   std::string origin_policy_manifest_;
-  base::test::ScopedFeatureList feature_list_;
 
-  DISALLOW_COPY_AND_ASSIGN(OriginIsolationOptInTest);
+  DISALLOW_COPY_AND_ASSIGN(OriginIsolationOptInOriginPolicyTest);
 };
 
+// Tests that verify headers can be used to opt-in to origin isolation.  See
+// OriginIsolationOptInOriginPolicyTest for most tests of the feature.
+class OriginIsolationOptInHeaderTest : public OriginIsolationOptInServerTest {
+ public:
+  OriginIsolationOptInHeaderTest() = default;
+
+  void SetHeaderValue(const std::string& header_value) {
+    header_ = header_value;
+  }
+
+ protected:
+  const base::Feature& feature_switch() override {
+    return features::kOriginIsolationHeader;
+  }
+
+  std::unique_ptr<net::test_server::HttpResponse> HandleResponse(
+      const net::test_server::HttpRequest& request) override {
+    if (request.relative_url == "/isolate_me") {
+      auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+      response->set_code(net::HTTP_OK);
+      response->set_content_type("text/html");
+
+      if (header_) {
+        response->AddCustomHeader("Origin-Isolation", *header_);
+      }
+
+      response->set_content("isolate me!");
+      return std::move(response);
+    }
+
+    // If we return nullptr, then the server will go ahead and actually serve
+    // the file.
+    return nullptr;
+  }
+
+  base::Optional<std::string> header_;
+
+  DISALLOW_COPY_AND_ASSIGN(OriginIsolationOptInHeaderTest);
+};
+
+// This tests that origin policy opt-in causes the origin to end up in the
+// isolated origins list.
+IN_PROC_BROWSER_TEST_F(OriginIsolationOptInOriginPolicyTest, Basic) {
+  SetOriginPolicyManifest(R"({ "ids": ["my-policy"], "isolation": true })");
+
+  GURL url(https_server()->GetURL("isolated.foo.com", "/isolate_me"));
+  url::Origin origin(url::Origin::Create(url));
+
+  EXPECT_FALSE(DoesOriginRequestOptInIsolation(origin));
+  EXPECT_TRUE(NavigateToURL(shell(), url));
+  EXPECT_TRUE(DoesOriginRequestOptInIsolation(origin));
+}
+
+// This tests that header-based opt-in causes the origin to end up in the
+// isolated origins list.
+IN_PROC_BROWSER_TEST_F(OriginIsolationOptInHeaderTest, Basic) {
+  SetHeaderValue("?1");
+
+  GURL url(https_server()->GetURL("isolated.foo.com", "/isolate_me"));
+  url::Origin origin(url::Origin::Create(url));
+
+  EXPECT_FALSE(DoesOriginRequestOptInIsolation(origin));
+  EXPECT_TRUE(NavigateToURL(shell(), url));
+  EXPECT_TRUE(DoesOriginRequestOptInIsolation(origin));
+}
+
+// Further tests deep-dive into various scenarios for the isolation opt-ins.
+// They use the origin policy mechanism, under the assumption that it will be
+// the same for the header mechanism since they both trigger the same behavior
+// in ChildProcessSecurityPolicyImpl.
+
 // In this test the sub-origin is isolated because the origin policy requests
 // "isolation". It will have a different site instance than the main frame.
-IN_PROC_BROWSER_TEST_F(OriginIsolationOptInTest, SimpleSubOriginIsolationTest) {
+IN_PROC_BROWSER_TEST_F(OriginIsolationOptInOriginPolicyTest,
+                       SimpleSubOriginIsolationTest) {
   SetOriginPolicyManifest(R"({ "ids": ["my-policy"], "isolation": true })");
   // Start off with an a(a) page, then navigate the subframe to an isolated sub
   // origin.
@@ -222,7 +329,7 @@
 
 // In this test the sub-origin isn't isolated because the origin policy doesn't
 // request "isolation". It will have the same site instance as the main frame.
-IN_PROC_BROWSER_TEST_F(OriginIsolationOptInTest,
+IN_PROC_BROWSER_TEST_F(OriginIsolationOptInOriginPolicyTest,
                        SimpleSubOriginNonIsolationTest) {
   SetOriginPolicyManifest(R"({ "ids": ["my-policy"] })");
   // Start off with an a(a) page, then navigate the subframe to an isolated sub
@@ -244,7 +351,8 @@
 
 // This test verifies that renderer-initiated navigations to/from isolated
 // sub-origins works as expected.
-IN_PROC_BROWSER_TEST_F(OriginIsolationOptInTest, RendererInitiatedNavigations) {
+IN_PROC_BROWSER_TEST_F(OriginIsolationOptInOriginPolicyTest,
+                       RendererInitiatedNavigations) {
   SetOriginPolicyManifest(R"({ "ids": ["my-policy"], "isolation": true })");
   GURL test_url(https_server()->GetURL("foo.com",
                                        "/cross_site_iframe_factory.html?"
@@ -286,7 +394,8 @@
 // both for renderer-initiated and browser-initiated navigations.
 // Note: this test is essentially identical to
 // IsolatedOriginTest.MainFrameNavigation.
-IN_PROC_BROWSER_TEST_F(OriginIsolationOptInTest, MainFrameNavigation) {
+IN_PROC_BROWSER_TEST_F(OriginIsolationOptInOriginPolicyTest,
+                       MainFrameNavigation) {
   SetOriginPolicyManifest(R"({ "ids": ["my-policy"], "isolation": true })");
   GURL unisolated_url(https_server()->GetURL("www.foo.com", "/title1.html"));
   GURL isolated_url(https_server()->GetURL("isolated.foo.com", "/isolate_me"));
@@ -359,7 +468,7 @@
 // This test ensures that if an origin starts off being isolated in a
 // BrowsingInstance, it continues that way within the BrowsingInstance, even
 // if a new policy is received that removes the opt-in request.
-IN_PROC_BROWSER_TEST_F(OriginIsolationOptInTest,
+IN_PROC_BROWSER_TEST_F(OriginIsolationOptInOriginPolicyTest,
                        OriginIsolationStateRetainedForBrowsingInstance) {
   SetOriginPolicyManifest(R"({ "ids": ["my-policy"], "isolation": true })");
   // Start off with an a(a,a) page, then navigate the subframe to an isolated
@@ -396,6 +505,11 @@
   EXPECT_FALSE(policy->DoesOriginRequestOptInIsolation(
       IsolationContext(shell()->web_contents()->GetBrowserContext()),
       url::Origin::Create(isolated_sub_origin)));
+
+  // TODO: replace the above with the following when we have
+  // per-BrowsingInstance tracking implemented correctly.
+  // EXPECT_FALSE(DoesOriginRequestOptInIsolation(
+  //     url::Origin::Create(isolated_sub_origin)));
 }
 
 // This test ensures that if an origin starts off not being isolated in a
@@ -404,7 +518,7 @@
 // TODO(wjmaclean): Re-enable this once we support tracking non-opted-in
 // origins.
 IN_PROC_BROWSER_TEST_F(
-    OriginIsolationOptInTest,
+    OriginIsolationOptInOriginPolicyTest,
     DISABLED_OriginNonIsolationStateRetainedForBrowsingInstance) {
   SetOriginPolicyManifest(R"({ "ids": ["my-policy"] })");
   // Start off with an a(a,a) page, then navigate the subframe to an isolated
@@ -433,9 +547,7 @@
             child_frame_node1->current_frame_host()->GetSiteInstance());
 
   // Make sure the master opt-in list has the origin listed.
-  auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
-  EXPECT_TRUE(policy->DoesOriginRequestOptInIsolation(
-      IsolationContext(shell()->web_contents()->GetBrowserContext()),
+  EXPECT_TRUE(DoesOriginRequestOptInIsolation(
       url::Origin::Create(isolated_sub_origin)));
 }
 
@@ -445,7 +557,8 @@
 // TODO(wjmaclean): Modify this to verify that the sub-origin is placed into the
 // site-keyed SiteInstance corresponding to the base-origin, and not the
 // origin-keyed SiteInstance the base origin is assigned to.
-IN_PROC_BROWSER_TEST_F(OriginIsolationOptInTest, IsolatedBaseOrigin) {
+IN_PROC_BROWSER_TEST_F(OriginIsolationOptInOriginPolicyTest,
+                       IsolatedBaseOrigin) {
   SetOriginPolicyManifest(R"({ "ids": ["my-policy"], "isolation": true })");
   // Start off with an isolated base-origin in an a(a) configuration, then
   // navigate the subframe to a sub-origin no requesting isolation.
@@ -463,12 +576,8 @@
             child_frame_node->current_frame_host()->GetSiteInstance());
   // Make sure the master opt-in list has both the base origin and the sub
   // origin both isolated.
-  auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
-  EXPECT_TRUE(policy->DoesOriginRequestOptInIsolation(
-      IsolationContext(shell()->web_contents()->GetBrowserContext()),
-      url::Origin::Create(test_url)));
-  EXPECT_FALSE(policy->DoesOriginRequestOptInIsolation(
-      IsolationContext(shell()->web_contents()->GetBrowserContext()),
+  EXPECT_TRUE(DoesOriginRequestOptInIsolation(url::Origin::Create(test_url)));
+  EXPECT_FALSE(DoesOriginRequestOptInIsolation(
       url::Origin::Create(non_isolated_sub_origin)));
 }
 
diff --git a/content/browser/renderer_host/input/event_latency_aura_browsertest.cc b/content/browser/renderer_host/input/event_latency_aura_browsertest.cc
index f0fecd8..8a2ed432 100644
--- a/content/browser/renderer_host/input/event_latency_aura_browsertest.cc
+++ b/content/browser/renderer_host/input/event_latency_aura_browsertest.cc
@@ -83,6 +83,16 @@
   FetchHistogramsFromChildProcesses();
 
   base::HistogramTester::CountsMap expected_counts = {
+      {"EventLatency.KeyReleased.BrowserToRendererCompositor", 1},
+      {"EventLatency.KeyReleased.BeginImplFrameToSendBeginMainFrame", 1},
+      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit", 1},
+      {"EventLatency.KeyReleased.Commit", 1},
+      {"EventLatency.KeyReleased.EndCommitToActivation", 1},
+      {"EventLatency.KeyReleased.Activation", 1},
+      {"EventLatency.KeyReleased.EndActivateToSubmitCompositorFrame", 1},
+      {"EventLatency.KeyReleased."
+       "SubmitCompositorFrameToPresentationCompositorFrame",
+       1},
       {"EventLatency.KeyReleased.TotalLatency", 1},
   };
   EXPECT_THAT(histogram_tester.GetTotalCountsForPrefix("EventLatency."),
@@ -114,6 +124,16 @@
   FetchHistogramsFromChildProcesses();
 
   base::HistogramTester::CountsMap expected_counts = {
+      {"EventLatency.KeyReleased.BrowserToRendererCompositor", 1},
+      {"EventLatency.KeyReleased.BeginImplFrameToSendBeginMainFrame", 1},
+      {"EventLatency.KeyReleased.SendBeginMainFrameToCommit", 1},
+      {"EventLatency.KeyReleased.Commit", 1},
+      {"EventLatency.KeyReleased.EndCommitToActivation", 1},
+      {"EventLatency.KeyReleased.Activation", 1},
+      {"EventLatency.KeyReleased.EndActivateToSubmitCompositorFrame", 1},
+      {"EventLatency.KeyReleased."
+       "SubmitCompositorFrameToPresentationCompositorFrame",
+       1},
       {"EventLatency.KeyReleased.TotalLatency", 1},
   };
   EXPECT_THAT(histogram_tester.GetTotalCountsForPrefix("EventLatency."),
diff --git a/content/browser/renderer_host/render_view_host_delegate.cc b/content/browser/renderer_host/render_view_host_delegate.cc
index b34eb600..1964096 100644
--- a/content/browser/renderer_host/render_view_host_delegate.cc
+++ b/content/browser/renderer_host/render_view_host_delegate.cc
@@ -69,7 +69,7 @@
   return nullptr;
 }
 
-bool RenderViewHostDelegate::IsPortal() const {
+bool RenderViewHostDelegate::IsPortal() {
   return false;
 }
 
diff --git a/content/browser/renderer_host/render_view_host_delegate.h b/content/browser/renderer_host/render_view_host_delegate.h
index 4b87c47..c34293bd 100644
--- a/content/browser/renderer_host/render_view_host_delegate.h
+++ b/content/browser/renderer_host/render_view_host_delegate.h
@@ -180,7 +180,7 @@
   virtual void DidFirstVisuallyNonEmptyPaint(RenderViewHostImpl* source) {}
 
   // Returns true if the render view is rendering a portal.
-  virtual bool IsPortal() const;
+  virtual bool IsPortal();
 
   // Called when the theme color for the underlying document as specified
   // by theme-color meta tag has changed.
diff --git a/content/browser/renderer_host/render_widget_host_delegate.cc b/content/browser/renderer_host/render_widget_host_delegate.cc
index 62bd672..7c12613 100644
--- a/content/browser/renderer_host/render_widget_host_delegate.cc
+++ b/content/browser/renderer_host/render_widget_host_delegate.cc
@@ -159,7 +159,7 @@
   return nullptr;
 }
 
-bool RenderWidgetHostDelegate::IsPortal() const {
+bool RenderWidgetHostDelegate::IsPortal() {
   return false;
 }
 
diff --git a/content/browser/renderer_host/render_widget_host_delegate.h b/content/browser/renderer_host/render_widget_host_delegate.h
index 234baae..ec91ee51 100644
--- a/content/browser/renderer_host/render_widget_host_delegate.h
+++ b/content/browser/renderer_host/render_widget_host_delegate.h
@@ -329,7 +329,7 @@
       viz::VerticalScrollDirection scroll_direction) {}
 
   // Returns true if the delegate is a portal.
-  virtual bool IsPortal() const;
+  virtual bool IsPortal();
 
   // Notify the delegate that the screen orientation has been changed.
   virtual void DidChangeScreenOrientation() {}
diff --git a/content/browser/service_worker/service_worker_navigation_loader_unittest.cc b/content/browser/service_worker/service_worker_navigation_loader_unittest.cc
index e1f3d1d2..a8b46fb 100644
--- a/content/browser/service_worker/service_worker_navigation_loader_unittest.cc
+++ b/content/browser/service_worker/service_worker_navigation_loader_unittest.cc
@@ -989,19 +989,5 @@
   EXPECT_EQ(net::ERR_ABORTED, client_.completion_status().error_code);
 }
 
-TEST_F(ServiceWorkerNavigationLoaderTest, ResponseWithCoop) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatures({network::features::kCrossOriginOpenerPolicy},
-                                {});
-  service_worker_->RespondWithHeaders(
-      {{"Cross-Origin-Opener-Policy", "same-origin"}});
-
-  // Perform the request.
-  StartRequest(CreateRequest());
-  client_.RunUntilComplete();
-  EXPECT_EQ(network::mojom::CrossOriginOpenerPolicy::kSameOrigin,
-            client_.response_head()->cross_origin_opener_policy);
-}
-
 }  // namespace service_worker_navigation_loader_unittest
 }  // namespace content
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index f0ae355b..bb1c8195 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -2017,8 +2017,9 @@
   DISALLOW_COPY_AND_ASSIGN(ScrollObserver);
 };
 
-// crbug.com/825629
-#if defined(OS_ANDROID)
+// Android: crbug.com/825629
+// NDEBUG: crbug.com/1063045
+#if defined(OS_ANDROID) || defined(NDEBUG)
 #define MAYBE_ScrollBubblingFromNestedOOPIFTest \
   DISABLED_ScrollBubblingFromNestedOOPIFTest
 #else
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 0fe5a498d..67688aa8 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -5240,7 +5240,7 @@
   }
 }
 
-bool WebContentsImpl::IsPortal() const {
+bool WebContentsImpl::IsPortal() {
   return portal();
 }
 
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 33ff205..05254ab 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -393,6 +393,7 @@
                               RenderFrameHost* render_frame_host,
                               bool is_full_page) override;
   bool IsInnerWebContentsForGuest() override;
+  bool IsPortal() override;
   RenderFrameHostImpl* GetOuterWebContentsFrame() override;
   WebContentsImpl* GetOuterWebContents() override;
   WebContentsImpl* GetOutermostWebContents() override;
@@ -784,7 +785,6 @@
   bool IsSpatialNavigationDisabled() const override;
   RenderFrameHostImpl* GetPendingMainFrame() override;
   void DidFirstVisuallyNonEmptyPaint(RenderViewHostImpl* source) override;
-  bool IsPortal() const override;
   void OnThemeColorChanged(RenderViewHostImpl* source) override;
 
   // NavigatorDelegate ---------------------------------------------------------
diff --git a/content/common/BUILD.gn b/content/common/BUILD.gn
index f22f6c8..45b6a64 100644
--- a/content/common/BUILD.gn
+++ b/content/common/BUILD.gn
@@ -731,6 +731,7 @@
         "//services/network/public/cpp",
         "//third_party/blink/public/common",
         "//ui/accessibility",
+        "//ui/base/cursor",
         "//ui/base/ime:text_input_types",
         "//ui/events/blink",
         "//ui/gfx/ipc",
diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h
index b471bd1f..deafffd 100644
--- a/content/public/browser/web_contents.h
+++ b/content/public/browser/web_contents.h
@@ -612,6 +612,11 @@
   // GetOuterWebContents instead.
   virtual bool IsInnerWebContentsForGuest() = 0;
 
+  // Returns whether this WebContents is a portal. This returns true even when
+  // this WebContents is not attached to its portal host's WebContents tree.
+  // This value may change over time due to portal activation and adoption.
+  virtual bool IsPortal() = 0;
+
   // Returns the outer WebContents frame, the same frame that this WebContents
   // was attached in AttachToOuterWebContentsFrame().
   virtual RenderFrameHost* GetOuterWebContentsFrame() = 0;
diff --git a/content/public/test/web_test_support_browser.h b/content/public/test/web_test_support_browser.h
new file mode 100644
index 0000000..ca7356d
--- /dev/null
+++ b/content/public/test/web_test_support_browser.h
@@ -0,0 +1,22 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_PUBLIC_TEST_WEB_TEST_SUPPORT_BROWSER_H_
+#define CONTENT_PUBLIC_TEST_WEB_TEST_SUPPORT_BROWSER_H_
+
+namespace content {
+
+// Turn the browser process into web test mode.
+void EnableBrowserWebTestMode();
+
+// Sets the scan duration to reflect the given setting.
+enum class BluetoothTestScanDurationSetting {
+  kImmediateTimeout,  // Set the scan duration to 0 seconds.
+  kNeverTimeout,  // Set the scan duration to base::TimeDelta::Max() seconds.
+};
+void SetTestBluetoothScanDuration(BluetoothTestScanDurationSetting setting);
+
+}  // namespace content
+
+#endif  // CONTENT_PUBLIC_TEST_WEB_TEST_SUPPORT_BROWSER_H_
diff --git a/content/public/test/web_test_support.h b/content/public/test/web_test_support_renderer.h
similarity index 84%
rename from content/public/test/web_test_support.h
rename to content/public/test/web_test_support_renderer.h
index 2706b9b..429eb18 100644
--- a/content/public/test/web_test_support.h
+++ b/content/public/test/web_test_support_renderer.h
@@ -1,9 +1,9 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CONTENT_PUBLIC_TEST_WEB_TEST_SUPPORT_H_
-#define CONTENT_PUBLIC_TEST_WEB_TEST_SUPPORT_H_
+#ifndef CONTENT_PUBLIC_TEST_WEB_TEST_SUPPORT_RENDERER_H_
+#define CONTENT_PUBLIC_TEST_WEB_TEST_SUPPORT_RENDERER_H_
 
 #include <stddef.h>
 
@@ -36,12 +36,6 @@
 class RenderFrame;
 class RenderView;
 
-// Turn the browser process into web test mode.
-void EnableBrowserWebTestMode();
-
-///////////////////////////////////////////////////////////////////////////////
-// The following methods are meant to be used from a renderer.
-
 // Turn a renderer into web test mode.
 void EnableRendererWebTestMode();
 
@@ -91,13 +85,6 @@
 void SetDeviceColorSpace(RenderView* render_view,
                          const gfx::ColorSpace& color_space);
 
-// Sets the scan duration to reflect the given setting.
-enum class BluetoothTestScanDurationSetting {
-  kImmediateTimeout,  // Set the scan duration to 0 seconds.
-  kNeverTimeout,  // Set the scan duration to base::TimeDelta::Max() seconds.
-};
-void SetTestBluetoothScanDuration(BluetoothTestScanDurationSetting setting);
-
 // Enables or disables synchronous resize mode. When enabled, all window-sizing
 // machinery is short-circuited inside the renderer. This mode is necessary for
 // some tests that were written before browsers had multi-process architecture
@@ -127,4 +114,4 @@
 
 }  // namespace content
 
-#endif  // CONTENT_PUBLIC_TEST_WEB_TEST_SUPPORT_H_
+#endif  // CONTENT_PUBLIC_TEST_WEB_TEST_SUPPORT_RENDERER_H_
diff --git a/content/shell/BUILD.gn b/content/shell/BUILD.gn
index 6eb5abb7..dd82ebe 100644
--- a/content/shell/BUILD.gn
+++ b/content/shell/BUILD.gn
@@ -327,7 +327,8 @@
     "//content/test:content_test_mojo_bindings",
     "//content/test:mojo_web_test_bindings",
     "//content/test:test_support",
-    "//content/test:web_test_support",
+    "//content/test:web_test_support_browser",
+    "//content/test:web_test_support_renderer",
     "//device/bluetooth",
     "//device/bluetooth:fake_bluetooth",
     "//device/bluetooth:mocks",
@@ -388,7 +389,7 @@
   }
 
   # Annoyingly, this target and web_test_support have circular includes.
-  allow_circular_includes_from = [ "//content/test:web_test_support" ]
+  allow_circular_includes_from = [ "//content/test:web_test_support_renderer" ]
 
   if (enable_plugins) {
     deps += [
diff --git a/content/shell/browser/web_test/blink_test_controller.cc b/content/shell/browser/web_test/blink_test_controller.cc
index d159c49..5319490 100644
--- a/content/shell/browser/web_test/blink_test_controller.cc
+++ b/content/shell/browser/web_test/blink_test_controller.cc
@@ -58,7 +58,6 @@
 #include "content/public/common/page_state.h"
 #include "content/public/common/url_constants.h"
 #include "content/public/test/blink_test_browser_support.h"
-#include "content/public/test/web_test_support.h"
 #include "content/shell/browser/shell.h"
 #include "content/shell/browser/shell_browser_context.h"
 #include "content/shell/browser/shell_content_browser_client.h"
diff --git a/content/shell/browser/web_test/fake_bluetooth_chooser.cc b/content/shell/browser/web_test/fake_bluetooth_chooser.cc
index 232f67aa9..77a8d42 100644
--- a/content/shell/browser/web_test/fake_bluetooth_chooser.cc
+++ b/content/shell/browser/web_test/fake_bluetooth_chooser.cc
@@ -8,7 +8,7 @@
 #include <utility>
 
 #include "content/public/browser/bluetooth_chooser.h"
-#include "content/public/test/web_test_support.h"
+#include "content/public/test/web_test_support_browser.h"
 #include "content/shell/common/web_test/fake_bluetooth_chooser.mojom.h"
 
 namespace content {
diff --git a/content/shell/browser/web_test/web_test_bluetooth_fake_adapter_setter_impl.cc b/content/shell/browser/web_test/web_test_bluetooth_fake_adapter_setter_impl.cc
index 0200b1e..9a5d3a1d 100644
--- a/content/shell/browser/web_test/web_test_bluetooth_fake_adapter_setter_impl.cc
+++ b/content/shell/browser/web_test/web_test_bluetooth_fake_adapter_setter_impl.cc
@@ -8,7 +8,7 @@
 #include <string>
 #include <utility>
 
-#include "content/public/test/web_test_support.h"
+#include "content/public/test/web_test_support_browser.h"
 #include "content/shell/browser/web_test/web_test_bluetooth_adapter_provider.h"
 #include "content/shell/common/web_test/web_test_bluetooth_fake_adapter_setter.mojom.h"
 #include "device/bluetooth/bluetooth_adapter_factory_wrapper.h"
@@ -17,11 +17,11 @@
 
 namespace content {
 
-WebTestBluetoothFakeAdapterSetterImpl::WebTestBluetoothFakeAdapterSetterImpl() {
-}
+WebTestBluetoothFakeAdapterSetterImpl::WebTestBluetoothFakeAdapterSetterImpl() =
+    default;
 
 WebTestBluetoothFakeAdapterSetterImpl::
-    ~WebTestBluetoothFakeAdapterSetterImpl() {}
+    ~WebTestBluetoothFakeAdapterSetterImpl() = default;
 
 // static
 void WebTestBluetoothFakeAdapterSetterImpl::Create(
diff --git a/content/shell/browser/web_test/web_test_browser_main_runner.cc b/content/shell/browser/web_test/web_test_browser_main_runner.cc
index a7a057c..1779c269 100644
--- a/content/shell/browser/web_test/web_test_browser_main_runner.cc
+++ b/content/shell/browser/web_test/web_test_browser_main_runner.cc
@@ -28,7 +28,7 @@
 #include "content/public/common/content_switches.h"
 #include "content/public/common/url_constants.h"
 #include "content/public/test/ppapi_test_utils.h"
-#include "content/public/test/web_test_support.h"
+#include "content/public/test/web_test_support_browser.h"
 #include "content/shell/browser/shell.h"
 #include "content/shell/browser/web_test/blink_test_controller.h"
 #include "content/shell/browser/web_test/test_info_extractor.h"
diff --git a/content/shell/browser/web_test/web_test_content_browser_client.cc b/content/shell/browser/web_test/web_test_content_browser_client.cc
index ebb72ae..bfe29ec 100644
--- a/content/shell/browser/web_test/web_test_content_browser_client.cc
+++ b/content/shell/browser/web_test/web_test_content_browser_client.cc
@@ -213,13 +213,10 @@
   associated_registry->AddInterface(
       base::BindRepeating(&WebTestContentBrowserClient::BindBlinkTestController,
                           base::Unretained(this)));
-  StoragePartition* partition =
-      BrowserContext::GetDefaultStoragePartition(browser_context());
   associated_registry->AddInterface(base::BindRepeating(
       &WebTestContentBrowserClient::BindWebTestController,
-      base::Unretained(this), render_process_host->GetID(),
-      partition->GetQuotaManager(), partition->GetDatabaseTracker(),
-      partition->GetNetworkContext()));
+      render_process_host->GetID(),
+      BrowserContext::GetDefaultStoragePartition(browser_context())));
 }
 
 base::Optional<service_manager::Manifest>
@@ -471,14 +468,15 @@
     BlinkTestController::Get()->AddBlinkTestClientReceiver(std::move(receiver));
 }
 
+// static
 void WebTestContentBrowserClient::BindWebTestController(
     int render_process_id,
-    storage::QuotaManager* quota_manager,
-    storage::DatabaseTracker* database_tracker,
-    network::mojom::NetworkContext* network_context,
+    StoragePartition* partition,
     mojo::PendingAssociatedReceiver<mojom::WebTestClient> receiver) {
-  WebTestClientImpl::Create(render_process_id, quota_manager, database_tracker,
-                            network_context, std::move(receiver));
+  WebTestClientImpl::Create(render_process_id, partition->GetQuotaManager(),
+                            partition->GetDatabaseTracker(),
+                            partition->GetNetworkContext(),
+                            std::move(receiver));
 }
 
 #if defined(OS_WIN)
diff --git a/content/shell/browser/web_test/web_test_content_browser_client.h b/content/shell/browser/web_test/web_test_content_browser_client.h
index 001c938..953bf81d 100644
--- a/content/shell/browser/web_test/web_test_content_browser_client.h
+++ b/content/shell/browser/web_test/web_test_content_browser_client.h
@@ -23,19 +23,7 @@
 #include "third_party/blink/public/mojom/clipboard/clipboard.mojom.h"
 #include "third_party/blink/public/mojom/permissions/permission_automation.mojom-forward.h"
 
-namespace network {
-namespace mojom {
-class NetworkContext;
-}  // namespace mojom
-}  // namespace network
-
-namespace storage {
-class DatabaseTracker;
-class QuotaManager;
-}  // namespace storage
-
 namespace content {
-
 class FakeBluetoothChooser;
 class FakeBluetoothChooserFactory;
 class FakeBluetoothDelegate;
@@ -141,11 +129,9 @@
   void BindBlinkTestController(
       mojo::PendingAssociatedReceiver<mojom::BlinkTestClient> receiver);
 
-  void BindWebTestController(
+  static void BindWebTestController(
       int render_process_id,
-      storage::QuotaManager* quota_manager,
-      storage::DatabaseTracker* database_tracker,
-      network::mojom::NetworkContext* network_context,
+      StoragePartition* partition,
       mojo::PendingAssociatedReceiver<mojom::WebTestClient> receiver);
 
   std::unique_ptr<MockPlatformNotificationService>
diff --git a/content/shell/renderer/web_test/blink_test_runner.cc b/content/shell/renderer/web_test/blink_test_runner.cc
index 7eda74d..45a2c15 100644
--- a/content/shell/renderer/web_test/blink_test_runner.cc
+++ b/content/shell/renderer/web_test/blink_test_runner.cc
@@ -40,7 +40,7 @@
 #include "content/public/renderer/render_thread.h"
 #include "content/public/renderer/render_view.h"
 #include "content/public/renderer/render_view_visitor.h"
-#include "content/public/test/web_test_support.h"
+#include "content/public/test/web_test_support_renderer.h"
 #include "content/shell/common/shell_switches.h"
 #include "content/shell/renderer/web_test/blink_test_helpers.h"
 #include "content/shell/renderer/web_test/web_test_render_thread_observer.h"
diff --git a/content/shell/renderer/web_test/web_test_content_renderer_client.cc b/content/shell/renderer/web_test/web_test_content_renderer_client.cc
index 8e605df..7ea0008 100644
--- a/content/shell/renderer/web_test/web_test_content_renderer_client.cc
+++ b/content/shell/renderer/web_test/web_test_content_renderer_client.cc
@@ -17,7 +17,7 @@
 #include "content/public/renderer/render_frame.h"
 #include "content/public/renderer/render_thread.h"
 #include "content/public/renderer/render_view.h"
-#include "content/public/test/web_test_support.h"
+#include "content/public/test/web_test_support_renderer.h"
 #include "content/shell/common/shell_switches.h"
 #include "content/shell/common/web_test/web_test_switches.h"
 #include "content/shell/renderer/shell_render_view_observer.h"
diff --git a/content/shell/renderer/web_test/web_test_render_thread_observer.cc b/content/shell/renderer/web_test/web_test_render_thread_observer.cc
index c004d9b..49b5533 100644
--- a/content/shell/renderer/web_test/web_test_render_thread_observer.cc
+++ b/content/shell/renderer/web_test/web_test_render_thread_observer.cc
@@ -6,7 +6,7 @@
 
 #include "content/public/common/content_client.h"
 #include "content/public/renderer/render_thread.h"
-#include "content/public/test/web_test_support.h"
+#include "content/public/test/web_test_support_renderer.h"
 #include "content/shell/common/web_test/web_test_switches.h"
 #include "content/shell/test_runner/test_interfaces.h"
 #include "content/shell/test_runner/test_runner.h"
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index f584607..f02e9e0 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -737,7 +737,7 @@
   }
 }
 
-static_library("web_test_support") {
+static_library("web_test_support_browser") {
   testonly = true
 
   # See comment at the top of //content/BUILD.gn for why this is disabled in
@@ -747,28 +747,39 @@
   }
 
   sources = [
-    "../public/test/web_test_support.h",
-    "web_test_support.cc",
+    "../public/test/web_test_support_browser.h",
+    "web_test_support_browser.cc",
+  ]
+  deps = [
+    "//content/browser:for_content_tests",  # For non-component builds.
+    "//content/public/browser",  # For component builds.
+    "//net:test_support",
+  ]
+}
+
+static_library("web_test_support_renderer") {
+  testonly = true
+
+  # See comment at the top of //content/BUILD.gn for why this is disabled in
+  # component builds.
+  if (is_component_build) {
+    check_includes = false
+  }
+
+  sources = [
+    "../public/test/web_test_support_renderer.h",
+    "web_test_support_renderer.cc",
   ]
 
   deps = [
     ":test_support",
-    "//content/browser:for_content_tests",
-    "//content/child:for_content_tests",
     "//content/public/common",
     "//content/public/renderer",
     "//content/renderer:for_content_tests",
     "//content/shell/test_runner:test_runner",
-    "//device/bluetooth",
-    "//device/gamepad/public/cpp:shared_with_blink",
-    "//net:test_support",
-    "//services/device/public/cpp/generic_sensor",
-    "//skia",
-    "//ui/accessibility:ax_enums_mojo",
     "//ui/events/blink",
     "//ui/gfx:test_support",
     "//ui/gfx/geometry",
-    "//v8",
 
     # This is required to ensure
     #   content/shell/common/web_test/blink_test.mojom.h
diff --git a/content/test/web_test_support_browser.cc b/content/test/web_test_support_browser.cc
new file mode 100644
index 0000000..da2e29d5
--- /dev/null
+++ b/content/test/web_test_support_browser.cc
@@ -0,0 +1,45 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/public/test/web_test_support_browser.h"
+
+#include "build/build_config.h"
+#include "content/browser/bluetooth/bluetooth_device_chooser_controller.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+
+#if defined(OS_MACOSX)
+#include "content/browser/frame_host/popup_menu_helper_mac.h"
+#include "content/browser/sandbox_parameters_mac.h"
+#include "net/test/test_data_directory.h"
+#endif
+
+namespace content {
+
+void EnableBrowserWebTestMode() {
+#if defined(OS_MACOSX)
+  PopupMenuHelper::DontShowPopupMenuForTesting();
+
+  // Expand the network service sandbox to allow reading the test TLS
+  // certificates.
+  SetNetworkTestCertsDirectoryForTesting(net::GetTestCertsDirectory());
+#endif
+  RenderWidgetHostImpl::DisableResizeAckCheckForTesting();
+}
+
+void SetTestBluetoothScanDuration(BluetoothTestScanDurationSetting setting) {
+  switch (setting) {
+    case BluetoothTestScanDurationSetting::kImmediateTimeout:
+      BluetoothDeviceChooserController::SetTestScanDurationForTesting(
+          BluetoothDeviceChooserController::TestScanDurationSetting::
+              IMMEDIATE_TIMEOUT);
+      break;
+    case BluetoothTestScanDurationSetting::kNeverTimeout:
+      BluetoothDeviceChooserController::SetTestScanDurationForTesting(
+          BluetoothDeviceChooserController::TestScanDurationSetting::
+              NEVER_TIMEOUT);
+      break;
+  }
+}
+
+}  // namespace content
diff --git a/content/test/web_test_support.cc b/content/test/web_test_support_renderer.cc
similarity index 84%
rename from content/test/web_test_support.cc
rename to content/test/web_test_support_renderer.cc
index 9dd89d7..179921b 100644
--- a/content/test/web_test_support.cc
+++ b/content/test/web_test_support_renderer.cc
@@ -2,19 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/public/test/web_test_support.h"
+#include "content/public/test/web_test_support_renderer.h"
 
 #include <memory>
 #include <string>
 #include <utility>
 
 #include "base/callback.h"
-#include "build/build_config.h"
-#include "content/browser/bluetooth/bluetooth_device_chooser_controller.h"
-#include "content/browser/renderer_host/render_widget_host_impl.h"
 #include "content/common/renderer.mojom.h"
 #include "content/common/unique_name_helper.h"
-#include "content/public/browser/storage_partition.h"
 #include "content/renderer/input/render_widget_input_handler_delegate.h"
 #include "content/renderer/loader/request_extra_data.h"
 #include "content/renderer/loader/web_worker_fetch_context_impl.h"
@@ -41,15 +37,6 @@
 #include "ui/gfx/icc_profile.h"
 #include "ui/gfx/test/icc_profiles.h"
 
-#if defined(OS_MACOSX)
-#include "content/browser/frame_host/popup_menu_helper_mac.h"
-#include "content/browser/sandbox_parameters_mac.h"
-#include "net/test/test_data_directory.h"
-#endif
-
-using blink::WebRect;
-using blink::WebSize;
-
 namespace content {
 
 namespace {
@@ -131,17 +118,6 @@
   UniqueNameHelper::PreserveStableUniqueNameForTesting();
 }
 
-void EnableBrowserWebTestMode() {
-#if defined(OS_MACOSX)
-  PopupMenuHelper::DontShowPopupMenuForTesting();
-
-  // Expand the network service sandbox to allow reading the test TLS
-  // certificates.
-  SetNetworkTestCertsDirectoryForTesting(net::GetTestCertsDirectory());
-#endif
-  RenderWidgetHostImpl::DisableResizeAckCheckForTesting();
-}
-
 int GetLocalSessionHistoryLength(RenderView* render_view) {
   return static_cast<RenderViewImpl*>(render_view)
       ->GetLocalSessionHistoryLengthForTesting();
@@ -152,7 +128,8 @@
       ->SetFocusAndActivateForTesting(enable);
 }
 
-void ForceResizeRenderView(RenderView* render_view, const WebSize& new_size) {
+void ForceResizeRenderView(RenderView* render_view,
+                           const blink::WebSize& new_size) {
   RenderViewImpl* render_view_impl = static_cast<RenderViewImpl*>(render_view);
   RenderFrameImpl* main_frame = render_view_impl->GetMainRenderFrame();
   if (!main_frame)
@@ -233,21 +210,6 @@
   render_widget->SetDeviceColorSpaceForTesting(color_space);
 }
 
-void SetTestBluetoothScanDuration(BluetoothTestScanDurationSetting setting) {
-  switch (setting) {
-    case BluetoothTestScanDurationSetting::kImmediateTimeout:
-      BluetoothDeviceChooserController::SetTestScanDurationForTesting(
-          BluetoothDeviceChooserController::TestScanDurationSetting::
-              IMMEDIATE_TIMEOUT);
-      break;
-    case BluetoothTestScanDurationSetting::kNeverTimeout:
-      BluetoothDeviceChooserController::SetTestScanDurationForTesting(
-          BluetoothDeviceChooserController::TestScanDurationSetting::
-              NEVER_TIMEOUT);
-      break;
-  }
-}
-
 void UseSynchronousResizeMode(RenderView* render_view, bool enable) {
   RenderViewImpl* render_view_impl = static_cast<RenderViewImpl*>(render_view);
   RenderFrameImpl* main_frame = render_view_impl->GetMainRenderFrame();
@@ -258,8 +220,8 @@
 }
 
 void EnableAutoResizeMode(RenderView* render_view,
-                          const WebSize& min_size,
-                          const WebSize& max_size) {
+                          const blink::WebSize& min_size,
+                          const blink::WebSize& max_size) {
   RenderViewImpl* render_view_impl = static_cast<RenderViewImpl*>(render_view);
   RenderFrameImpl* main_frame = render_view_impl->GetMainRenderFrame();
   if (!main_frame)
@@ -268,7 +230,8 @@
   render_widget->EnableAutoResizeForTesting(min_size, max_size);
 }
 
-void DisableAutoResizeMode(RenderView* render_view, const WebSize& new_size) {
+void DisableAutoResizeMode(RenderView* render_view,
+                           const blink::WebSize& new_size) {
   RenderViewImpl* render_view_impl = static_cast<RenderViewImpl*>(render_view);
   RenderFrameImpl* main_frame = render_view_impl->GetMainRenderFrame();
   if (!main_frame)
diff --git a/docs/patterns/synchronous-runloop.md b/docs/patterns/synchronous-runloop.md
new file mode 100644
index 0000000..a0e8bacf
--- /dev/null
+++ b/docs/patterns/synchronous-runloop.md
@@ -0,0 +1,216 @@
+# The Synchronous RunLoop Pattern
+
+The synchronous RunLoop pattern involves creating a new RunLoop, setting up a
+specified quit condition for it, then calling Run() on it to block the current
+thread until that quit condition is reached.
+
+## Use this pattern when:
+
+You need to **block the current thread** until an event happens, and you have a
+way to get notified of that event, via a callback or observer interface or
+similar. A couple of common scenarios might be:
+
+* Waiting for an asynchronous event (like a network request) to complete
+* Waiting for an animation to finish
+* Waiting for a page to have loaded
+* Waiting for some call that requires a thread hop to complete
+
+The fact that this blocks a thread means it is **almost never appropriate
+outside test code**.
+
+## Don't use this pattern when:
+
+* You don't really need the entire thread to wait
+* You don't have and can't add a way to get notified when the event happens
+* You're waiting for a timer to fire - for that, [TaskEnvironment] is likely a
+  better fit.
+
+## Alternatives / see also:
+
+* [TaskEnvironment]
+* Restructuring your code to not require blocking a thread
+
+## How this pattern works:
+
+This pattern relies on two important facts about [base::RunLoop]:
+
+1. `base::RunLoop::Quit()` is idempotent - once a RunLoop enters the quit
+   state, quitting it again does nothing
+2. Once a RunLoop is in the quit state, calling `base::RunLoop::Run()` on it is
+   a no-op
+
+That means that if your code does this:
+
+```c++
+  base::RunLoop loop;
+  maybe-asynchronously { loop.Quit(); }
+  loop.Run();
+  LOG(INFO) << "Hello!";
+```
+
+then regardless of whether the maybe-asynchronous `loop.Quit()` is executed
+before or after `loop.Run()`,  the "Hello!" message will never be printed before
+both `loop.Run()` and `loop.Quit()` have happened. If the `Quit` happens
+before the `Run`, the `Run` will be a no-op; if the `Quit` happens after the
+`Run` has started, the `Run` will exit after the `Quit`.
+
+## How to use this pattern in Chromium:
+
+If the asynchronous thing in question takes a completion callback:
+
+```c++
+  base::RunLoop run_loop;
+  Reply reply;
+  DoThingAndReply(
+      base::BindLambdaForTesting([&](const Reply& r) {
+          reply = r;
+          run_loop.Quit();
+      }));
+  run_loop.Run();
+```
+
+or perhaps even just:
+
+```c++
+  base::RunLoop run_loop;
+  DoThing(run_loop.QuitClosure());
+  run_loop.Run();
+```
+
+If there exists a GizmoObserver interface with an OnThingDone event:
+
+```c++
+  class TestGizmoObserver : public GizmoObserver {
+   public:
+    TestGizmoObserver(base::RunLoop* loop, Gizmo* thing)
+        : GizmoObserver(thing), loop_(loop) {}
+
+    // GizmoObserver:
+    void OnThingStarted(Gizmo* observed_gizmo) override { ... }
+    void OnThingProgressed(Gizmo* observed_gizmo) override { ... }
+    void OnThingDone(Gizmo* observed_gizmo) override {
+      loop_->Quit();
+    }
+  };
+
+  base::RunLoop run_loop;
+  TestGizmoObserver observer(&run_loop, gizmo);
+  gizmo->StartDoingThing();
+  run_loop.Run();
+```
+
+This is sometimes wrapped up into a helper class that internally constructs the
+RunLoop like so, if all you need to do is wait for the event but don't care
+about observing any intermediate states too:
+
+```c++
+  class ThingDoneWaiter : public GizmoObserver {
+   public:
+    ThingDoneWaiter(Gizmo* thing) : GizmoObserver(thing) {}
+
+    void Wait() {
+      run_loop_.Run();
+    }
+
+    // GizmoObserver:
+    void OnThingDone(Gizmo* observed_gismo) {
+      run_loop_.Quit();
+    }
+
+   private:
+    RunLoop run_loop_;
+  };
+
+  ThingDoneWaiter waiter(gizmo);
+  gizmo->StartDoingThing();
+  waiter.Wait();
+```
+
+## Sharp edges
+
+### Starting waiting too late
+
+A common mis-use of this pattern is like so:
+
+```c++
+  gizmo->StartDoingThing();
+  base::RunLoop run_loop;
+  TestGizmoObserver observer(&run_loop, gizmo);
+  run_loop.Run();
+```
+
+This looks tempting because it seems that you can write a helper function:
+
+```c++
+  void TerribleHorribleNoGoodVeryBadWaitForThing(Gizmo* gizmo) {
+    base::RunLoop run_loop;
+    TestGizmoObserver observer(&run_loop, gizmo);
+    run_loop.Run();
+  }
+```
+
+and then your test code can simply read:
+
+```c++
+  gizmo->StartDoingThing();
+  TerribleHorribleNoGoodVeryBadWaitForThing(gizmo);
+```
+
+However, this is a recipe for a flaky test: if `gizmo->StartDoingThing()`
+*completes* and would deliver the `OnThingDone` callback before your
+`TestGizmoObserver` is ever constructed, the `TestGizmoObserver` will never
+receive `OnThingDone`, and then your `run_loop.Run()` will run forever,
+frustrating a future tree sheriff (and then probably you, shortly afterward).
+This is especially dangerous when `gizmo->StartDoingThing()` involves a thread
+hop or network request, because these can unpredictably complete before or after
+your observer gets constructed. To be safe, always begin observing the event
+*before* running the code that will eventually cause the event!
+
+If you still really want a helper function, perhaps you just want to inline the
+start:
+
+```c++
+  void NiceFriendlyDoThingAndWait(Gizmo* gizmo) {
+    base::RunLoop run_loop;
+    TestGizmoObserver observer(&run_loop, gizmo);
+    gizmo->StartDoingThing();
+    run_loop.Run();
+  }
+```
+
+with the test code being:
+
+```c++
+  NiceFriendlyDoThingAndWait(gizmo);
+```
+
+### Guessing RunLoop cycles
+
+Sometimes, there's no easy way to observe completion of an event. In that case,
+if the code under test looks like this:
+
+```c++
+  void StartDoingThing() { PostTask(&StepOne); }
+  void StepOne() { PostTask(&StepTwo); }
+  void StepTwo() { /* done! */ }
+```
+
+it can be tempting to do:
+
+```c++
+  gizmo->StartDoingThing();
+  base::RunLoop().RunUntilIdle();
+  /* now it's done! */
+```
+
+However, doing this is adding dependencies to your test code on the exact async
+behavior of the production code - for example, the production code may depend on
+work happening on another TaskRunner, which this won't successfully wait for.
+This will make your test brittle and flaky.
+
+Instead of doing this, it's vastly better to add a way (even if it's just via a
+[test API]) to observe the event you're interested in.
+
+[base::RunLoop]: ../../base/run_loop.h
+[TaskEnvironment]: ../threading_and_tasks_testing.md
+[test API]: testapi.md
diff --git a/fuchsia/BUILD.gn b/fuchsia/BUILD.gn
index 1220435..b6b4b1ab 100644
--- a/fuchsia/BUILD.gn
+++ b/fuchsia/BUILD.gn
@@ -20,6 +20,7 @@
   ]
 
   public_deps = [
+    "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.media.sessions2",
     "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.ui.gfx",
     "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.web",
   ]
diff --git a/fuchsia/base/config_reader.cc b/fuchsia/base/config_reader.cc
index f3ac3d5..3cb7115db 100644
--- a/fuchsia/base/config_reader.cc
+++ b/fuchsia/base/config_reader.cc
@@ -6,10 +6,13 @@
 
 #include "base/files/file_util.h"
 #include "base/json/json_reader.h"
+#include "base/no_destructor.h"
 
 namespace cr_fuchsia {
 
-base::Optional<base::Value> LoadPackageConfig() {
+namespace {
+
+base::Optional<base::Value> ReadPackageConfig() {
   constexpr char kConfigPath[] = "/config/data/config.json";
 
   base::FilePath path(kConfigPath);
@@ -35,4 +38,14 @@
   return std::move(parsed.value());
 }
 
+}  // namespace
+
+const base::Optional<base::Value>& LoadPackageConfig() {
+  // Package configurations do not change at run-time, so read the configuration
+  // on the first call and cache the result.
+  static base::NoDestructor<base::Optional<base::Value>> config(
+      ReadPackageConfig());
+  return *config;
+}
+
 }  // namespace cr_fuchsia
diff --git a/fuchsia/base/config_reader.h b/fuchsia/base/config_reader.h
index 7e94d82..d76449a 100644
--- a/fuchsia/base/config_reader.h
+++ b/fuchsia/base/config_reader.h
@@ -13,7 +13,7 @@
 // Loads and parses configuration data from the environment.
 // Returns a null value if the file(s) do not exist.
 // CHECK-fails if the file(s) are present but not parseable.
-base::Optional<base::Value> LoadPackageConfig();
+const base::Optional<base::Value>& LoadPackageConfig();
 
 }  // namespace cr_fuchsia
 
diff --git a/fuchsia/engine/context_provider_impl.cc b/fuchsia/engine/context_provider_impl.cc
index 373ece5..313451d1 100644
--- a/fuchsia/engine/context_provider_impl.cc
+++ b/fuchsia/engine/context_provider_impl.cc
@@ -255,9 +255,10 @@
     PA_HND(PA_USER0, 0);
 
 ContextProviderImpl::ContextProviderImpl() {
-  base::Optional<base::Value> default_config = cr_fuchsia::LoadPackageConfig();
+  const base::Optional<base::Value>& default_config =
+      cr_fuchsia::LoadPackageConfig();
   if (default_config) {
-    config_default_ = std::move(default_config.value());
+    config_default_ = default_config->Clone();
   } else {
     config_default_ = base::Value(base::Value::Type::DICTIONARY);
   }
@@ -571,14 +572,14 @@
   if (!config_override_.is_none())
     return config_override_.Clone();
 
-  base::Optional<base::Value> config = cr_fuchsia::LoadPackageConfig();
+  const base::Optional<base::Value>& config = cr_fuchsia::LoadPackageConfig();
   if (!config) {
     DLOG(WARNING) << "Configuration data not found. Using default "
                      "WebEngine configuration.";
     return base::Value(base::Value::Type::DICTIONARY);
   }
 
-  return std::move(*config);
+  return config->Clone();
 }
 
 void ContextProviderImpl::EnableDevTools(
diff --git a/fuchsia/fidl/cast/application_controller.fidl b/fuchsia/fidl/cast/application_controller.fidl
index 7d7e0c21..4278003 100644
--- a/fuchsia/fidl/cast/application_controller.fidl
+++ b/fuchsia/fidl/cast/application_controller.fidl
@@ -4,9 +4,14 @@
 
 library chromium.cast;
 
+using fuchsia.media.sessions2;
+
 /// Allows clients to access and modify certain aspects of the Cast receiver
 /// application runtime.
 protocol ApplicationController {
   /// Enables or disables touch event processing.
   SetTouchInputEnabled(bool enable);
+
+  /// Connects to the application's media control & observation API.
+  GetMediaPlayer(request<fuchsia.media.sessions2.Player> request);
 };
diff --git a/fuchsia/runners/cast/application_controller_impl.cc b/fuchsia/runners/cast/application_controller_impl.cc
index 90fac16b..2373447 100644
--- a/fuchsia/runners/cast/application_controller_impl.cc
+++ b/fuchsia/runners/cast/application_controller_impl.cc
@@ -31,3 +31,8 @@
                               (enable ? fuchsia::web::AllowInputState::ALLOW
                                       : fuchsia::web::AllowInputState::DENY));
 }
+
+void ApplicationControllerImpl::GetMediaPlayer(
+    fidl::InterfaceRequest<fuchsia::media::sessions2::Player> request) {
+  frame_->GetMediaPlayer(std::move(request));
+}
diff --git a/fuchsia/runners/cast/application_controller_impl.h b/fuchsia/runners/cast/application_controller_impl.h
index 790f4c6..87b9e8c 100644
--- a/fuchsia/runners/cast/application_controller_impl.h
+++ b/fuchsia/runners/cast/application_controller_impl.h
@@ -5,8 +5,10 @@
 #ifndef FUCHSIA_RUNNERS_CAST_APPLICATION_CONTROLLER_IMPL_H_
 #define FUCHSIA_RUNNERS_CAST_APPLICATION_CONTROLLER_IMPL_H_
 
+#include <fuchsia/media/sessions2/cpp/fidl.h>
 #include <fuchsia/web/cpp/fidl.h>
 #include <lib/fidl/cpp/binding.h>
+#include <lib/fidl/cpp/interface_request.h>
 
 #include "base/macros.h"
 #include "fuchsia/fidl/chromium/cast/cpp/fidl.h"
@@ -19,7 +21,10 @@
   ~ApplicationControllerImpl() final;
 
  protected:
-  void SetTouchInputEnabled(bool enable) override;
+  void SetTouchInputEnabled(bool enable) final;
+  void GetMediaPlayer(
+      ::fidl::InterfaceRequest<fuchsia::media::sessions2::Player> request)
+      final;
 
  private:
   fidl::Binding<chromium::cast::ApplicationController> binding_;
diff --git a/fuchsia/runners/cast/main.cc b/fuchsia/runners/cast/main.cc
index 3ef456f..f8cdfafd 100644
--- a/fuchsia/runners/cast/main.cc
+++ b/fuchsia/runners/cast/main.cc
@@ -24,7 +24,7 @@
 bool IsHeadless() {
   constexpr char kHeadlessConfigKey[] = "headless";
 
-  base::Optional<base::Value> config = cr_fuchsia::LoadPackageConfig();
+  const base::Optional<base::Value>& config = cr_fuchsia::LoadPackageConfig();
   if (config) {
     base::Optional<bool> headless = config->FindBoolPath(kHeadlessConfigKey);
     return headless && *headless;
diff --git a/gin/gin_features.cc b/gin/gin_features.cc
index d24cf1e4d..60f16e51 100644
--- a/gin/gin_features.cc
+++ b/gin/gin_features.cc
@@ -14,6 +14,10 @@
 const base::Feature kV8FlushBytecode{"V8FlushBytecode",
                                      base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Enables finalizing streaming JS compilations on a background thread.
+const base::Feature kV8OffThreadFinalization{"V8OffThreadFinalization",
+                                             base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables lazy feedback allocation in V8.
 const base::Feature kV8LazyFeedbackAllocation{"V8LazyFeedbackAllocation",
                                               base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/gin/gin_features.h b/gin/gin_features.h
index 6502de3..9a04e51a 100644
--- a/gin/gin_features.h
+++ b/gin/gin_features.h
@@ -12,6 +12,7 @@
 
 GIN_EXPORT extern const base::Feature kV8OptimizeJavascript;
 GIN_EXPORT extern const base::Feature kV8FlushBytecode;
+GIN_EXPORT extern const base::Feature kV8OffThreadFinalization;
 GIN_EXPORT extern const base::Feature kV8LazyFeedbackAllocation;
 GIN_EXPORT extern const base::Feature kV8ConcurrentInlining;
 GIN_EXPORT extern const base::Feature kV8PerContextMarkingWorklist;
diff --git a/gin/v8_initializer.cc b/gin/v8_initializer.cc
index 3a3742e..2e2e346 100644
--- a/gin/v8_initializer.cc
+++ b/gin/v8_initializer.cc
@@ -211,6 +211,13 @@
                                sizeof(no_flush_bytecode) - 1);
   }
 
+  if (base::FeatureList::IsEnabled(features::kV8OffThreadFinalization)) {
+    static const char finalize_streaming_on_background[] =
+        "--finalize-streaming-on-background";
+    v8::V8::SetFlagsFromString(finalize_streaming_on_background,
+                               sizeof(finalize_streaming_on_background) - 1);
+  }
+
   if (!base::FeatureList::IsEnabled(features::kV8LazyFeedbackAllocation)) {
     static const char no_lazy_feedback_allocation[] =
         "--no-lazy-feedback-allocation";
diff --git a/gpu/command_buffer/service/shared_image_factory.cc b/gpu/command_buffer/service/shared_image_factory.cc
index e1848e8..36f32b82 100644
--- a/gpu/command_buffer/service/shared_image_factory.cc
+++ b/gpu/command_buffer/service/shared_image_factory.cc
@@ -147,7 +147,7 @@
   // For Windows
   bool use_passthrough = gpu_preferences.use_passthrough_cmd_decoder &&
                          gles2::PassthroughCommandDecoderSupported();
-  if (use_passthrough) {
+  if (use_passthrough && !using_vulkan_) {
     // Only supported for passthrough command decoder.
     interop_backing_factory_ = std::make_unique<SharedImageBackingFactoryD3D>();
   }
diff --git a/gpu/vulkan/vulkan_fence_helper_unittest.cc b/gpu/vulkan/vulkan_fence_helper_unittest.cc
index a921560..f972415 100644
--- a/gpu/vulkan/vulkan_fence_helper_unittest.cc
+++ b/gpu/vulkan/vulkan_fence_helper_unittest.cc
@@ -5,6 +5,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 #include "base/bind.h"
+#include "build/build_config.h"
 #include "gpu/vulkan/tests/basic_vulkan_test.h"
 #include "gpu/vulkan/vulkan_device_queue.h"
 #include "gpu/vulkan/vulkan_fence_helper.h"
@@ -147,7 +148,13 @@
   EXPECT_EQ(10u, cleanups_run);
 }
 
+// The test failed on Win with GTX1660 GPU.
+// https://crbug.com/1066854
+#if defined(OS_WIN)
+TEST_F(VulkanFenceHelperTest, DISABLED_SkiaCallbackAfterFences) {
+#else
 TEST_F(VulkanFenceHelperTest, SkiaCallbackAfterFences) {
+#endif
   VulkanFenceHelper* fence_helper = GetDeviceQueue()->GetFenceHelper();
   uint32_t cleanups_run = 0;
   auto increment_cleanups_callback =
@@ -168,7 +175,8 @@
       fence_helper->GenerateCleanupFence();
   EXPECT_TRUE(fence_handle.is_valid());
 
-  // Call vkQueueWaitIdle() to make sure the |fence_handle| is passed.
+  // Call vkQueueWaitIdle() to make sure the |fence_handle| is passed,
+  // however it doesn't work on Win with GTX1660 GPU.
   vkQueueWaitIdle(queue());
 
   // Enqueue 5 more callbacks.
diff --git a/infra/config/README.md b/infra/config/README.md
index a599304f..ea1cdad6 100644
--- a/infra/config/README.md
+++ b/infra/config/README.md
@@ -3,25 +3,50 @@
 
 This directory contains chromium project-wide configurations
 for Chrome Operations services.
-For example, [cr-buildbucket.cfg](cr-buildbucket.cfg) defines builders.
+For example, [cr-buildbucket.cfg](generated/cr-buildbucket.cfg) defines
+builders.
 
 **Remember** Change these configs on `master` branch only!
 
 Currently active version can be checked at
 https://luci-config.appspot.com/#/projects/chromium .
 
-The configuration files are currently in the process of being migrated to
-lucicfg/starlark. See
+The configuration is written using starlark, which is executed using lucicfg to
+generate the raw cfg files located in [generated](generated). See
 https://chromium.googlesource.com/infra/luci/luci-go/+/HEAD/lucicfg/doc/README.md
-for more information on lucicfg/starlark. If a hand-written configuration file
-is still present alongside this file, you can modify that file directly.
+for more information on lucicfg/starlark.
 
-The remainder of the configuration files are generated by starlark. The starlark
-configuration is rooted in main.star and dev.star, which execute other starlark
-files to generate a subset of the LUCI service configuration files to the
-`generated` subdirectory. A presubmit check enforces that the generated files
-are kept in sync with the generated output of the starlark configuration.
+The starlark configuration is rooted in `main.star` and `dev.star`, which
+execute other starlark files to generate a subset of the LUCI service
+configuration files. A presubmit check enforces that the generated files are
+kept in sync with the output of the starlark configuration.
 
-The starlark configuration also copies over the not-yet migrated files to the
-`generated` directory, so updating the hand-written configuration files will
-require re-'generating' the configuration.
+The configuration rooted at [main.star](main.star) defines the LUCI services
+configuration for the chromium project on the production instance of LUCI. The
+configuration is responsible for generating the raw configuration files that do
+not end in -dev.cfg as well as the markdown file
+[cq-builders.md](generated/cq-builders.md). Starlark files in the following
+directories are consumed by the configuration:
+
+*   buckets - Definitions of builders for the chromium project. There is a .star
+    file for each bucket in the chromium project where the bucket and the
+    builders for that bucket are defined.
+*   consoles - Definitions of milo consoles for the chromium project. There is a
+    .star for each console that defines the console.
+*   generators - Definitions of lucicfg generators that do various things to
+    post-process the LUCI configuration before the output files are generated
+    (e.g. generate no-op jobs to workaround limitations of our recipe config) or
+    generate additional files (e.g. the CQ builders markdown document).
+*   validators - Definitions of lucicfg generators that perform additional
+    validation on the the LUCI configuration (e.g. ensure all builders are added
+    to at least one console).
+*   versioned - Definitions of builders for the main waterfall for the chromium
+    project. Builders on the main waterfall are branched so that when a new
+    milestone is cut, ci and try buckets specific to that milestone are created
+    with an equivalent set of builders, schedulers, CQ groups, etc. to enable
+    CI/CQ on the new branch.
+
+The configuration rooted at [dev.star](dev.star) defines the LUCI services
+configuration for the chromium project on the dev instance of LUCI. This
+configuration is responsible for generating the raw configuration files ending
+in -dev.cfg.
diff --git a/infra/config/buckets/ci.star b/infra/config/buckets/ci.star
index 8a2abb4b..baaa900 100644
--- a/infra/config/buckets/ci.star
+++ b/infra/config/buckets/ci.star
@@ -20,6 +20,7 @@
 exec('//versioned/trunk/buckets/ci.star')
 exec('//versioned/milestones/m80/buckets/ci.star')
 exec('//versioned/milestones/m81/buckets/ci.star')
+exec('//versioned/milestones/m83/buckets/ci.star')
 
 
 # *** After this point everything is trunk only ***
@@ -1045,15 +1046,6 @@
 )
 
 ci.fyi_ios_builder(
-    name = 'ios-simulator-cronet',
-    executable = 'recipe:chromium',
-    notifies = ['cronet'],
-    properties = {
-        'xcode_build_version': '11c29',
-    },
-)
-
-ci.fyi_ios_builder(
     name = 'ios-webkit-tot',
     caches = [xcode_cache.x11c505wk],
     executable = 'recipe:chromium',
@@ -1641,11 +1633,6 @@
 )
 
 ci.mac_ios_builder(
-    name = 'ios-simulator-full-configs',
-    executable = 'recipe:chromium',
-)
-
-ci.mac_ios_builder(
     name = 'ios-simulator-noncq',
 )
 
@@ -1698,15 +1685,6 @@
 )
 
 ci.memory_builder(
-    name = 'Linux TSan Builder',
-)
-
-ci.memory_builder(
-    name = 'Linux TSan Tests',
-    triggered_by = ['Linux TSan Builder'],
-)
-
-ci.memory_builder(
     name = 'Mac ASan 64 Builder',
     builderless = False,
     goma_debug = True,  # TODO(hinoka): Remove this after debugging.
diff --git a/infra/config/buckets/try.star b/infra/config/buckets/try.star
index 5016e42..5afaff43 100644
--- a/infra/config/buckets/try.star
+++ b/infra/config/buckets/try.star
@@ -30,6 +30,7 @@
 exec('//versioned/trunk/buckets/try.star')
 exec('//versioned/milestones/m80/buckets/try.star')
 exec('//versioned/milestones/m81/buckets/try.star')
+exec('//versioned/milestones/m83/buckets/try.star')
 
 
 # *** After this point everything is trunk only ***
diff --git a/infra/config/consoles/main.star b/infra/config/consoles/main.star
index 67ada62..01612b1 100644
--- a/infra/config/consoles/main.star
+++ b/infra/config/consoles/main.star
@@ -1,3 +1,4 @@
 exec('//versioned/trunk/consoles/main.star')
 exec('//versioned/milestones/m80/consoles/main.star')
 exec('//versioned/milestones/m81/consoles/main.star')
+exec('//versioned/milestones/m83/consoles/main.star')
diff --git a/infra/config/consoles/try-m83.star b/infra/config/consoles/try-m83.star
new file mode 100644
index 0000000..262aabe
--- /dev/null
+++ b/infra/config/consoles/try-m83.star
@@ -0,0 +1,57 @@
+luci.list_view(
+    name = 'try-m83',
+    entries = [
+        'try-m83/android-binary-size',
+        'try-m83/android-cronet-arm-dbg',
+        'try-m83/android-kitkat-arm-rel',
+        'try-m83/android-marshmallow-arm64-rel',
+        'try-m83/android-pie-arm64-dbg',
+        'try-m83/android-pie-arm64-rel',
+        'try-m83/android_compile_dbg',
+        'try-m83/android_compile_x64_dbg',
+        'try-m83/android_compile_x86_dbg',
+        'try-m83/android_cronet',
+        'try-m83/android_optional_gpu_tests_rel',
+        'try-m83/cast_shell_android',
+        'try-m83/cast_shell_linux',
+        'try-m83/chromeos-amd64-generic-dbg',
+        'try-m83/chromeos-amd64-generic-rel',
+        'try-m83/chromeos-arm-generic-rel',
+        'try-m83/chromeos-kevin-compile-rel',
+        'try-m83/chromeos-kevin-rel',
+        'try-m83/chromium_presubmit',
+        'try-m83/closure_compilation',
+        'try-m83/dawn-linux-x64-deps-rel',
+        'try-m83/dawn-mac-x64-deps-rel',
+        'try-m83/dawn-win10-x64-deps-rel',
+        'try-m83/dawn-win10-x86-deps-rel',
+        'try-m83/fuchsia-arm64-cast',
+        'try-m83/fuchsia-x64-cast',
+        'try-m83/fuchsia_arm64',
+        'try-m83/fuchsia_x64',
+        'try-m83/ios-simulator',
+        'try-m83/ios-simulator-cronet',
+        'try-m83/ios-simulator-full-configs',
+        'try-m83/linux-blink-rel',
+        'try-m83/linux-chromeos-compile-dbg',
+        'try-m83/linux-chromeos-rel',
+        'try-m83/linux-libfuzzer-asan-rel',
+        'try-m83/linux-ozone-rel',
+        'try-m83/linux-rel',
+        'try-m83/linux_chromium_asan_rel_ng',
+        'try-m83/linux_chromium_compile_dbg_ng',
+        'try-m83/linux_chromium_dbg_ng',
+        'try-m83/linux_chromium_tsan_rel_ng',
+        'try-m83/linux_layout_tests_composite_after_paint',
+        'try-m83/linux_layout_tests_layout_ng_disabled',
+        'try-m83/linux_optional_gpu_tests_rel',
+        'try-m83/linux_vr',
+        'try-m83/mac-rel',
+        'try-m83/mac_chromium_compile_dbg_ng',
+        'try-m83/mac_optional_gpu_tests_rel',
+        'try-m83/win-libfuzzer-asan-rel',
+        'try-m83/win10_chromium_x64_rel_ng',
+        'try-m83/win_chromium_compile_dbg_ng',
+        'try-m83/win_optional_gpu_tests_rel',
+    ],
+)
diff --git a/infra/config/generated/commit-queue.cfg b/infra/config/generated/commit-queue.cfg
index e3735d1..4530dd1 100644
--- a/infra/config/generated/commit-queue.cfg
+++ b/infra/config/generated/commit-queue.cfg
@@ -1383,6 +1383,335 @@
   >
 >
 config_groups: <
+  name: "cq-m83"
+  gerrit: <
+    url: "https://chromium-review.googlesource.com"
+    projects: <
+      name: "chromium/src"
+      ref_regexp: "refs/branch-heads/4103"
+    >
+  >
+  verifiers: <
+    gerrit_cq_ability: <
+      committer_list: "project-chromium-committers"
+      dry_run_access_list: "project-chromium-tryjob-access"
+    >
+    tryjob: <
+      builders: <
+        name: "chromium/try-m83/android-binary-size"
+      >
+      builders: <
+        name: "chromium/try-m83/android-cronet-arm-dbg"
+        location_regexp: ".+/[+]/components/cronet/.+"
+        location_regexp: ".+/[+]/components/grpc_support/.+"
+        location_regexp: ".+/[+]/build/android/.+"
+        location_regexp: ".+/[+]/build/config/android/.+"
+        location_regexp_exclude: ".+/[+]/components/cronet/ios/.+"
+      >
+      builders: <
+        name: "chromium/try-m83/android-kitkat-arm-rel"
+      >
+      builders: <
+        name: "chromium/try-m83/android-marshmallow-arm64-rel"
+      >
+      builders: <
+        name: "chromium/try-m83/android-pie-arm64-dbg"
+        location_regexp: ".+/[+]/chrome/android/features/vr/.+"
+        location_regexp: ".+/[+]/chrome/android/java/src/org/chromium/chrome/browser/vr/.+"
+        location_regexp: ".+/[+]/chrome/android/javatests/src/org/chromium/chrome/browser/vr/.+"
+        location_regexp: ".+/[+]/chrome/browser/vr/.+"
+        location_regexp: ".+/[+]/third_party/gvr-android-sdk/.+"
+        location_regexp: ".+/[+]/third_party/arcore-android-sdk/.+"
+        location_regexp: ".+/[+]/third_party/arcore-android-sdk-client/.+"
+      >
+      builders: <
+        name: "chromium/try-m83/android-pie-arm64-rel"
+      >
+      builders: <
+        name: "chromium/try-m83/android_compile_dbg"
+      >
+      builders: <
+        name: "chromium/try-m83/android_compile_x64_dbg"
+        location_regexp: ".+/[+]/chrome/android/java/src/org/chromium/chrome/browser/vr/.+"
+        location_regexp: ".+/[+]/chrome/browser/vr/.+"
+        location_regexp: ".+/[+]/sandbox/linux/seccomp-bpf/.+"
+        location_regexp: ".+/[+]/sandbox/linux/seccomp-bpf-helpers/.+"
+        location_regexp: ".+/[+]/sandbox/linux/system_headers/.+"
+        location_regexp: ".+/[+]/sandbox/linux/tests/.+"
+        location_regexp: ".+/[+]/third_party/gvr-android-sdk/.+"
+      >
+      builders: <
+        name: "chromium/try-m83/android_compile_x86_dbg"
+        location_regexp: ".+/[+]/chrome/android/java/src/org/chromium/chrome/browser/vr/.+"
+        location_regexp: ".+/[+]/chrome/browser/vr/.+"
+        location_regexp: ".+/[+]/sandbox/linux/seccomp-bpf/.+"
+        location_regexp: ".+/[+]/sandbox/linux/seccomp-bpf-helpers/.+"
+        location_regexp: ".+/[+]/sandbox/linux/system_headers/.+"
+        location_regexp: ".+/[+]/sandbox/linux/tests/.+"
+        location_regexp: ".+/[+]/third_party/gvr-android-sdk/.+"
+      >
+      builders: <
+        name: "chromium/try-m83/android_cronet"
+      >
+      builders: <
+        name: "chromium/try-m83/android_optional_gpu_tests_rel"
+        location_regexp: ".+/[+]/cc/.+"
+        location_regexp: ".+/[+]/chrome/browser/vr/.+"
+        location_regexp: ".+/[+]/components/viz/.+"
+        location_regexp: ".+/[+]/content/test/gpu/.+"
+        location_regexp: ".+/[+]/gpu/.+"
+        location_regexp: ".+/[+]/media/audio/.+"
+        location_regexp: ".+/[+]/media/filters/.+"
+        location_regexp: ".+/[+]/media/gpu/.+"
+        location_regexp: ".+/[+]/services/viz/.+"
+        location_regexp: ".+/[+]/testing/trigger_scripts/.+"
+        location_regexp: ".+/[+]/third_party/blink/renderer/modules/webgl/.+"
+        location_regexp: ".+/[+]/third_party/blink/renderer/platform/graphics/gpu/.+"
+        location_regexp: ".+/[+]/tools/clang/scripts/update.py"
+        location_regexp: ".+/[+]/ui/gl/.+"
+      >
+      builders: <
+        name: "chromium/try-m83/cast_shell_android"
+      >
+      builders: <
+        name: "chromium/try-m83/cast_shell_linux"
+      >
+      builders: <
+        name: "chromium/try-m83/chromeos-amd64-generic-dbg"
+        location_regexp: ".+/[+]/content/gpu/.+"
+        location_regexp: ".+/[+]/media/.+"
+      >
+      builders: <
+        name: "chromium/try-m83/chromeos-amd64-generic-rel"
+      >
+      builders: <
+        name: "chromium/try-m83/chromeos-arm-generic-rel"
+      >
+      builders: <
+        name: "chromium/try-m83/chromeos-kevin-compile-rel"
+        location_regexp: ".+/[+]/chromeos/CHROMEOS_LKGM"
+      >
+      builders: <
+        name: "chromium/try-m83/chromeos-kevin-rel"
+        location_regexp: ".+/[+]/build/chromeos/.+"
+        location_regexp: ".+/[+]/build/config/chromeos/.*"
+      >
+      builders: <
+        name: "chromium/try-m83/chromium_presubmit"
+        disable_reuse: true
+      >
+      builders: <
+        name: "chromium/try-m83/closure_compilation"
+        location_regexp: ".+/[+]/third_party/closure_compiler/.+"
+      >
+      builders: <
+        name: "chromium/try-m83/dawn-linux-x64-deps-rel"
+        location_regexp: ".+/[+]/gpu/.+"
+        location_regexp: ".+/[+]/testing/buildbot/chromium.dawn.json"
+        location_regexp: ".+/[+]/third_party/blink/renderer/modules/webgpu/.+"
+        location_regexp: ".+/[+]/third_party/blink/web_tests/external/wpt/webgpu/.+"
+        location_regexp: ".+/[+]/third_party/blink/web_tests/wpt_internal/webgpu/.+"
+        location_regexp: ".+/[+]/third_party/blink/web_tests/WebGPUExpectations"
+        location_regexp: ".+/[+]/third_party/dawn/.+"
+        location_regexp: ".+/[+]/tools/clang/scripts/update.py"
+        location_regexp: ".+/[+]/ui/gl/features.gni"
+      >
+      builders: <
+        name: "chromium/try-m83/dawn-mac-x64-deps-rel"
+        location_regexp: ".+/[+]/gpu/.+"
+        location_regexp: ".+/[+]/testing/buildbot/chromium.dawn.json"
+        location_regexp: ".+/[+]/third_party/blink/renderer/modules/webgpu/.+"
+        location_regexp: ".+/[+]/third_party/blink/web_tests/external/wpt/webgpu/.+"
+        location_regexp: ".+/[+]/third_party/blink/web_tests/wpt_internal/webgpu/.+"
+        location_regexp: ".+/[+]/third_party/blink/web_tests/WebGPUExpectations"
+        location_regexp: ".+/[+]/third_party/dawn/.+"
+        location_regexp: ".+/[+]/tools/clang/scripts/update.py"
+        location_regexp: ".+/[+]/ui/gl/features.gni"
+      >
+      builders: <
+        name: "chromium/try-m83/dawn-win10-x64-deps-rel"
+        location_regexp: ".+/[+]/gpu/.+"
+        location_regexp: ".+/[+]/testing/buildbot/chromium.dawn.json"
+        location_regexp: ".+/[+]/third_party/blink/renderer/modules/webgpu/.+"
+        location_regexp: ".+/[+]/third_party/blink/web_tests/external/wpt/webgpu/.+"
+        location_regexp: ".+/[+]/third_party/blink/web_tests/wpt_internal/webgpu/.+"
+        location_regexp: ".+/[+]/third_party/blink/web_tests/WebGPUExpectations"
+        location_regexp: ".+/[+]/third_party/dawn/.+"
+        location_regexp: ".+/[+]/tools/clang/scripts/update.py"
+        location_regexp: ".+/[+]/ui/gl/features.gni"
+      >
+      builders: <
+        name: "chromium/try-m83/dawn-win10-x86-deps-rel"
+        location_regexp: ".+/[+]/gpu/.+"
+        location_regexp: ".+/[+]/testing/buildbot/chromium.dawn.json"
+        location_regexp: ".+/[+]/third_party/blink/renderer/modules/webgpu/.+"
+        location_regexp: ".+/[+]/third_party/blink/web_tests/external/wpt/webgpu/.+"
+        location_regexp: ".+/[+]/third_party/blink/web_tests/wpt_internal/webgpu/.+"
+        location_regexp: ".+/[+]/third_party/blink/web_tests/WebGPUExpectations"
+        location_regexp: ".+/[+]/third_party/dawn/.+"
+        location_regexp: ".+/[+]/tools/clang/scripts/update.py"
+        location_regexp: ".+/[+]/ui/gl/features.gni"
+      >
+      builders: <
+        name: "chromium/try-m83/fuchsia-arm64-cast"
+        location_regexp: ".+/[+]/chromecast/.+"
+      >
+      builders: <
+        name: "chromium/try-m83/fuchsia-x64-cast"
+      >
+      builders: <
+        name: "chromium/try-m83/fuchsia_arm64"
+      >
+      builders: <
+        name: "chromium/try-m83/fuchsia_x64"
+      >
+      builders: <
+        name: "chromium/try-m83/ios-simulator"
+      >
+      builders: <
+        name: "chromium/try-m83/ios-simulator-cronet"
+        location_regexp: ".+/[+]/components/cronet/.+"
+        location_regexp: ".+/[+]/components/grpc_support/.+"
+        location_regexp: ".+/[+]/ios/.+"
+        location_regexp_exclude: ".+/[+]/components/cronet/android/.+"
+      >
+      builders: <
+        name: "chromium/try-m83/ios-simulator-full-configs"
+        location_regexp: ".+/[+]/ios/.+"
+      >
+      builders: <
+        name: "chromium/try-m83/linux-blink-rel"
+        location_regexp: ".+/[+]/cc/.+"
+        location_regexp: ".+/[+]/third_party/blink/renderer/core/paint/.+"
+        location_regexp: ".+/[+]/third_party/blink/renderer/core/svg/.+"
+        location_regexp: ".+/[+]/third_party/blink/renderer/platform/graphics/.+"
+      >
+      builders: <
+        name: "chromium/try-m83/linux-chromeos-compile-dbg"
+      >
+      builders: <
+        name: "chromium/try-m83/linux-chromeos-rel"
+      >
+      builders: <
+        name: "chromium/try-m83/linux-libfuzzer-asan-rel"
+      >
+      builders: <
+        name: "chromium/try-m83/linux-ozone-rel"
+      >
+      builders: <
+        name: "chromium/try-m83/linux-rel"
+      >
+      builders: <
+        name: "chromium/try-m83/linux_chromium_asan_rel_ng"
+      >
+      builders: <
+        name: "chromium/try-m83/linux_chromium_compile_dbg_ng"
+      >
+      builders: <
+        name: "chromium/try-m83/linux_chromium_dbg_ng"
+        location_regexp: ".+/[+]/build/.*check_gn_headers.*"
+      >
+      builders: <
+        name: "chromium/try-m83/linux_chromium_tsan_rel_ng"
+      >
+      builders: <
+        name: "chromium/try-m83/linux_layout_tests_composite_after_paint"
+        location_regexp: ".+/[+]/third_party/blink/renderer/core/paint/.+"
+        location_regexp: ".+/[+]/third_party/blink/renderer/core/svg/.+"
+        location_regexp: ".+/[+]/third_party/blink/renderer/platform/graphics/.+"
+        location_regexp: ".+/[+]/third_party/blink/web_tests/.+"
+      >
+      builders: <
+        name: "chromium/try-m83/linux_layout_tests_layout_ng_disabled"
+        location_regexp: ".+/[+]/third_party/blink/renderer/core/editing/.+"
+        location_regexp: ".+/[+]/third_party/blink/renderer/core/layout/.+"
+        location_regexp: ".+/[+]/third_party/blink/renderer/core/paint/.+"
+        location_regexp: ".+/[+]/third_party/blink/renderer/core/svg/.+"
+        location_regexp: ".+/[+]/third_party/blink/renderer/platform/fonts/shaping/.+"
+        location_regexp: ".+/[+]/third_party/blink/renderer/platform/graphics/.+"
+        location_regexp: ".+/[+]/third_party/blink/web_tests/FlagExpectations/disable-layout-ng"
+        location_regexp: ".+/[+]/third_party/blink/web_tests/flag-specific/disable-layout-ng/.+"
+      >
+      builders: <
+        name: "chromium/try-m83/linux_optional_gpu_tests_rel"
+        location_regexp: ".+/[+]/chrome/browser/vr/.+"
+        location_regexp: ".+/[+]/content/test/gpu/.+"
+        location_regexp: ".+/[+]/gpu/.+"
+        location_regexp: ".+/[+]/media/audio/.+"
+        location_regexp: ".+/[+]/media/filters/.+"
+        location_regexp: ".+/[+]/media/gpu/.+"
+        location_regexp: ".+/[+]/testing/buildbot/chromium.gpu.fyi.json"
+        location_regexp: ".+/[+]/testing/trigger_scripts/.+"
+        location_regexp: ".+/[+]/third_party/blink/renderer/modules/webgl/.+"
+        location_regexp: ".+/[+]/third_party/blink/renderer/platform/graphics/gpu/.+"
+        location_regexp: ".+/[+]/tools/clang/scripts/update.py"
+        location_regexp: ".+/[+]/ui/gl/.+"
+      >
+      builders: <
+        name: "chromium/try-m83/linux_vr"
+        location_regexp: ".+/[+]/chrome/browser/vr/.+"
+      >
+      builders: <
+        name: "chromium/try-m83/mac-rel"
+      >
+      builders: <
+        name: "chromium/try-m83/mac_chromium_compile_dbg_ng"
+      >
+      builders: <
+        name: "chromium/try-m83/mac_optional_gpu_tests_rel"
+        location_regexp: ".+/[+]/chrome/browser/vr/.+"
+        location_regexp: ".+/[+]/content/test/gpu/.+"
+        location_regexp: ".+/[+]/gpu/.+"
+        location_regexp: ".+/[+]/media/audio/.+"
+        location_regexp: ".+/[+]/media/filters/.+"
+        location_regexp: ".+/[+]/media/gpu/.+"
+        location_regexp: ".+/[+]/services/shape_detection/.+"
+        location_regexp: ".+/[+]/testing/buildbot/chromium.gpu.fyi.json"
+        location_regexp: ".+/[+]/testing/trigger_scripts/.+"
+        location_regexp: ".+/[+]/third_party/blink/renderer/modules/webgl/.+"
+        location_regexp: ".+/[+]/third_party/blink/renderer/platform/graphics/gpu/.+"
+        location_regexp: ".+/[+]/tools/clang/scripts/update.py"
+        location_regexp: ".+/[+]/ui/gl/.+"
+      >
+      builders: <
+        name: "chromium/try-m83/win-libfuzzer-asan-rel"
+      >
+      builders: <
+        name: "chromium/try-m83/win10_chromium_x64_rel_ng"
+      >
+      builders: <
+        name: "chromium/try-m83/win_chromium_compile_dbg_ng"
+      >
+      builders: <
+        name: "chromium/try-m83/win_optional_gpu_tests_rel"
+        location_regexp: ".+/[+]/chrome/browser/vr/.+"
+        location_regexp: ".+/[+]/content/test/gpu/.+"
+        location_regexp: ".+/[+]/device/vr/.+"
+        location_regexp: ".+/[+]/gpu/.+"
+        location_regexp: ".+/[+]/media/audio/.+"
+        location_regexp: ".+/[+]/media/filters/.+"
+        location_regexp: ".+/[+]/media/gpu/.+"
+        location_regexp: ".+/[+]/testing/buildbot/chromium.gpu.fyi.json"
+        location_regexp: ".+/[+]/testing/trigger_scripts/.+"
+        location_regexp: ".+/[+]/third_party/blink/renderer/modules/vr/.+"
+        location_regexp: ".+/[+]/third_party/blink/renderer/modules/webgl/.+"
+        location_regexp: ".+/[+]/third_party/blink/renderer/modules/xr/.+"
+        location_regexp: ".+/[+]/third_party/blink/renderer/platform/graphics/gpu/.+"
+        location_regexp: ".+/[+]/tools/clang/scripts/update.py"
+        location_regexp: ".+/[+]/ui/gl/.+"
+      >
+      retry_config: <
+        single_quota: 1
+        global_quota: 2
+        failure_weight: 1
+        transient_failure_weight: 1
+        timeout_weight: 2
+      >
+      cancel_stale_tryjobs: YES
+    >
+  >
+>
+config_groups: <
   name: "fallback-empty-cq"
   gerrit: <
     url: "https://chromium-review.googlesource.com"
diff --git a/infra/config/generated/cr-buildbucket.cfg b/infra/config/generated/cr-buildbucket.cfg
index 3d87222..6d69a551 100644
--- a/infra/config/generated/cr-buildbucket.cfg
+++ b/infra/config/generated/cr-buildbucket.cfg
@@ -3172,6 +3172,7 @@
         name: "chromium"
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
         cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
         properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":500,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
         properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
         properties_j: "mastername:\"chromium.memory\""
@@ -3193,6 +3194,7 @@
         name: "chromium"
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
         cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
         properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":500,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
         properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
         properties_j: "mastername:\"chromium.memory\""
@@ -6995,6 +6997,7 @@
         name: "chromium"
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
         cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
         properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
         properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
         properties_j: "mastername:\"chromium.fyi\""
@@ -7019,6 +7022,7 @@
         name: "chromium"
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
         cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
         properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
         properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
         properties_j: "mastername:\"chromium.mac\""
@@ -10155,6 +10159,1871 @@
   >
 >
 buckets: <
+  name: "ci-m83"
+  acls: <
+    role: WRITER
+    group: "google/luci-task-force@google.com"
+  >
+  acls: <
+    group: "all"
+  >
+  acls: <
+    role: SCHEDULER
+    group: "project-chromium-ci-schedulers"
+  >
+  swarming: <
+    builders: <
+      name: "Android Release (Nexus 5X)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.gpu\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Android WebView M (dbg)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.android\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Android WebView N (dbg)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.android\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Android WebView O (dbg)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.android\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Android WebView P (dbg)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.android\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Android arm Builder (dbg)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.android\""
+      >
+      execution_timeout_secs: 14400
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Android arm64 Builder (dbg)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":500,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.android\""
+      >
+      execution_timeout_secs: 14400
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Android x64 Builder (dbg)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.android\""
+      >
+      execution_timeout_secs: 14400
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Android x86 Builder (dbg)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.android\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Cast Android (dbg)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.android\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Cast Linux"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":50,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.linux\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Dawn Linux x64 DEPS Builder"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.dawn\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Dawn Linux x64 DEPS Release (Intel HD 630)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:2"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.dawn\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Dawn Linux x64 DEPS Release (NVIDIA)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:2"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.dawn\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Dawn Mac x64 DEPS Builder"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:Dawn Mac x64 DEPS Builder"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Mac"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.dawn\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Dawn Mac x64 DEPS Release (AMD)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:2"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.dawn\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Dawn Mac x64 DEPS Release (Intel)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:2"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.dawn\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Dawn Win10 x64 DEPS Builder"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Windows"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.dawn\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Dawn Win10 x64 DEPS Release (Intel HD 630)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:2"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.dawn\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Dawn Win10 x64 DEPS Release (NVIDIA)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:2"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.dawn\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Dawn Win10 x86 DEPS Builder"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Windows"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.dawn\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Dawn Win10 x86 DEPS Release (Intel HD 630)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:2"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.dawn\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Dawn Win10 x86 DEPS Release (NVIDIA)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:2"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.dawn\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Fuchsia ARM64"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":500,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.linux\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Fuchsia x64"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":500,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.linux\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "GPU Linux Builder"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.gpu\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "GPU Mac Builder"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:GPU Mac Builder"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Mac"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.gpu\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "GPU Win x64 Builder"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Windows"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.gpu\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Linux ASan LSan Builder"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:1"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":500,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.memory\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Linux ASan LSan Tests (1)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":500,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.memory\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Linux ASan Tests (sandboxed)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":500,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.memory\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Linux Builder"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":500,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.linux\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Linux Builder (dbg)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":500,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.linux\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Linux Ozone Tester (Headless)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":500,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.linux\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Linux Ozone Tester (Wayland)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":500,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.linux\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Linux Ozone Tester (X11)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":500,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.linux\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Linux Release (NVIDIA)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:2"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.gpu\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Linux TSan Builder"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":500,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.memory\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Linux TSan Tests"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":500,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.memory\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Linux Tests"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"jobs\":500}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.linux\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Linux Tests (dbg)(1)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":500,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.linux\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Mac Builder"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:Mac Builder"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Mac-10.14"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.mac\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Mac Builder (dbg)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:Mac Builder (dbg)"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Mac"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.mac\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Mac Release (Intel)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:2"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.gpu\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Mac Retina Release (AMD)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:2"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.gpu\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Mac10.10 Tests"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:Mac10.10 Tests"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Mac-10.13"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.mac\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Mac10.11 Tests"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:Mac10.11 Tests"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Mac-10.13"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.mac\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Mac10.12 Tests"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:Mac10.12 Tests"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Mac-10.12"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.mac\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Mac10.13 Tests"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:Mac10.13 Tests"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Mac-10.13"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.mac\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Mac10.13 Tests (dbg)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:Mac10.13 Tests (dbg)"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Mac"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.mac\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Mac10.14 Tests"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:Mac10.14 Tests"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Mac-10.14"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.mac\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Marshmallow 64 bit Tester"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.android\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Nougat Phone Tester"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.android\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Oreo Phone Tester"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.android\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "VR Linux"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.fyi\""
+      >
+      execution_timeout_secs: 36000
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "WebKit Mac10.13 (retina)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:WebKit Mac10.13 (retina)"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Mac-10.13"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.mac\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Win 7 Tests x64 (1)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:Win 7 Tests x64 (1)"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Windows-7"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.win\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Win Builder (dbg)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:Win Builder (dbg)"
+      dimensions: "cores:32"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Windows"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.win\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Win x64 Builder"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:Win x64 Builder"
+      dimensions: "cores:32"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Windows"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.win\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Win10 Tests x64"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:Win10 Tests x64"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Windows-10"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.win\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Win10 Tests x64 1803"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:Win10 Tests x64 1803"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Windows-10"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.fyi\""
+      >
+      execution_timeout_secs: 36000
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Win10 x64 Release (NVIDIA)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:2"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.gpu\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "Win7 Tests (dbg)(1)"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:Win7 Tests (dbg)(1)"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Windows-7"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.win\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "android-cronet-arm-dbg"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.android\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "android-cronet-arm-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.android\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "android-cronet-kitkat-arm-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.android\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "android-cronet-lollipop-arm-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.android\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "android-kitkat-arm-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.android\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "android-marshmallow-arm64-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.android\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "android-pie-arm64-dbg"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.android\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "android-pie-arm64-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.android\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "chromeos-amd64-generic-dbg"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.chromiumos\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "chromeos-amd64-generic-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.chromiumos\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "chromeos-arm-generic-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.chromiumos\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "chromeos-kevin-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.chromiumos\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "chromeos-kevin-rel-hw-tests"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.fyi\""
+      >
+      execution_timeout_secs: 36000
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "fuchsia-arm64-cast"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":500,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.linux\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "fuchsia-x64-cast"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":500,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.linux\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "ios-simulator"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:ios-simulator"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Mac"
+      recipe: <
+        name: "ios/unified_builder_tester"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.mac\""
+        properties_j: "xcode_build_version:\"11c29\""
+      >
+      execution_timeout_secs: 10800
+      caches: <
+        name: "xcode_ios_11c29"
+        path: "xcode_ios_11c29.app"
+      >
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "ios-simulator-cronet"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:ios-simulator-cronet"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Mac"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.fyi\""
+        properties_j: "xcode_build_version:\"11c29\""
+      >
+      execution_timeout_secs: 36000
+      caches: <
+        name: "xcode_ios_11c29"
+        path: "xcode_ios_11c29.app"
+      >
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "ios-simulator-full-configs"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:ios-simulator-full-configs"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Mac"
+      recipe: <
+        name: "ios/unified_builder_tester"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.mac\""
+        properties_j: "xcode_build_version:\"11c29\""
+      >
+      execution_timeout_secs: 10800
+      caches: <
+        name: "xcode_ios_11c29"
+        path: "xcode_ios_11c29.app"
+      >
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "linux-chromeos-dbg"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.chromiumos\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "linux-chromeos-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.chromiumos\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "linux-ozone-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":500,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.linux\""
+      >
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+    builders: <
+      name: "mac-osxbeta-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/chromium_tests:{\"bucketed_triggers\":true}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"chromium.fyi\""
+      >
+      execution_timeout_secs: 36000
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+    >
+  >
+>
+buckets: <
   name: "findit"
   acls: <
     identity: "user:findit-builder@chops-service-accounts.iam.gserviceaccount.com"
@@ -20474,6 +22343,1553 @@
   >
 >
 buckets: <
+  name: "try-m83"
+  acls: <
+    role: WRITER
+    group: "service-account-chromium-tryserver"
+  >
+  acls: <
+    group: "all"
+  >
+  acls: <
+    role: SCHEDULER
+    identity: "user:findit-for-me@appspot.gserviceaccount.com"
+  >
+  acls: <
+    role: SCHEDULER
+    identity: "user:tricium-prod@appspot.gserviceaccount.com"
+  >
+  acls: <
+    role: SCHEDULER
+    group: "project-chromium-tryjob-access"
+  >
+  acls: <
+    role: SCHEDULER
+    group: "service-account-chromeperf"
+  >
+  acls: <
+    role: SCHEDULER
+    group: "service-account-cq"
+  >
+  swarming: <
+    builders: <
+      name: "android-binary-size"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "binary_size_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.android\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "android-cronet-arm-dbg"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.android\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "android-kitkat-arm-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.android\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "android-marshmallow-arm64-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:16"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:1"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/code_coverage:{\"use_java_coverage\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":300,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.android\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "android-pie-arm64-dbg"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.android\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "android-pie-arm64-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:16"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:1"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":300,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.android\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "android_compile_dbg"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.android\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "android_compile_x64_dbg"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.android\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "android_compile_x86_dbg"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.android\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "android_cronet"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.android\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "android_optional_gpu_tests_rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:android_optional_gpu_tests_rel"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.android\""
+      >
+      execution_timeout_secs: 21600
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "cast_shell_android"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.android\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "cast_shell_linux"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.linux\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "chromeos-amd64-generic-dbg"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.chromiumos\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "chromeos-amd64-generic-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.chromiumos\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "chromeos-arm-generic-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.chromiumos\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "chromeos-kevin-compile-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.chromiumos\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "chromeos-kevin-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.chromiumos\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "chromium_presubmit"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "presubmit"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$depot_tools/presubmit:{\"runhooks\":true,\"timeout_s\":480}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.linux\""
+        properties_j: "repo_name:\"chromium\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "closure_compilation"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "closure_compilation"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.linux\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "dawn-linux-x64-deps-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:dawn-linux-x64-deps-rel"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.dawn\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "dawn-mac-x64-deps-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:dawn-mac-x64-deps-rel"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Mac"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.dawn\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "dawn-win10-x64-deps-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:dawn-win10-x64-deps-rel"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Windows"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.dawn\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "dawn-win10-x86-deps-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:dawn-win10-x86-deps-rel"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Windows"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.dawn\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "fuchsia-arm64-cast"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.linux\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "fuchsia-x64-cast"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.linux\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "fuchsia_arm64"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.linux\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "fuchsia_x64"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.linux\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "ios-simulator"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:ios-simulator"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Mac"
+      recipe: <
+        name: "ios/try"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.mac\""
+        properties_j: "xcode_build_version:\"11c29\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      caches: <
+        name: "xcode_ios_11c29"
+        path: "xcode_ios_11c29.app"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "ios-simulator-cronet"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:ios-simulator-cronet"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Mac"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.mac\""
+        properties_j: "xcode_build_version:\"11c29\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      caches: <
+        name: "xcode_ios_11c29"
+        path: "xcode_ios_11c29.app"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "ios-simulator-full-configs"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:ios-simulator-full-configs"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Mac"
+      recipe: <
+        name: "ios/try"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.mac\""
+        properties_j: "xcode_build_version:\"11c29\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      caches: <
+        name: "xcode_ios_11c29"
+        path: "xcode_ios_11c29.app"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "linux-blink-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.blink\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "linux-chromeos-compile-dbg"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.chromiumos\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "linux-chromeos-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/code_coverage:{\"use_clang_coverage\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.chromiumos\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "linux-libfuzzer-asan-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_libfuzzer_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.linux\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "linux-ozone-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.linux\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "linux-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/code_coverage:{\"use_clang_coverage\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.linux\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "linux_chromium_asan_rel_ng"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:1"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.linux\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "linux_chromium_compile_dbg_ng"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.linux\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "builder"
+        path: "linux_debug"
+      >
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "linux_chromium_dbg_ng"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.linux\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "builder"
+        path: "linux_debug"
+      >
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "linux_chromium_tsan_rel_ng"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.linux\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "linux_layout_tests_composite_after_paint"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.linux\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "linux_layout_tests_layout_ng_disabled"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.linux\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "linux_optional_gpu_tests_rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:linux_optional_gpu_tests_rel"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.linux\""
+      >
+      execution_timeout_secs: 21600
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "linux_vr"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.linux\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "mac-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Mac"
+      dimensions: "ssd:1"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.mac\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "mac_chromium_compile_dbg_ng"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Mac-10.13"
+      dimensions: "ssd:1"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.mac\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "mac_optional_gpu_tests_rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:mac_optional_gpu_tests_rel"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Mac"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.mac\""
+      >
+      execution_timeout_secs: 21600
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "win-libfuzzer-asan-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builder:win-libfuzzer-asan-rel"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Windows"
+      recipe: <
+        name: "chromium_libfuzzer_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.win\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "win10_chromium_x64_rel_ng"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Windows-10"
+      dimensions: "ssd:1"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/code_coverage:{\"use_clang_coverage\":true}"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.win\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "win_chromium_compile_dbg_ng"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Windows-10"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"jobs\":150,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.win\""
+      >
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+    builders: <
+      name: "win_optional_gpu_tests_rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Windows-10"
+      dimensions: "ssd:0"
+      recipe: <
+        name: "chromium_trybot"
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        properties_j: "$build/goma:{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\"}"
+        properties_j: "$kitchen:{\"devshell\":true,\"git_auth\":true}"
+        properties_j: "mastername:\"tryserver.chromium.win\""
+      >
+      execution_timeout_secs: 21600
+      expiration_secs: 7200
+      caches: <
+        name: "win_toolchain"
+        path: "win_toolchain"
+      >
+      build_numbers: YES
+      service_account: "chromium-try-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage: <
+        value: 5
+      >
+    >
+  >
+>
+buckets: <
   name: "webrtc"
   acls: <
     role: WRITER
diff --git a/infra/config/generated/luci-milo.cfg b/infra/config/generated/luci-milo.cfg
index 72dc9bb9..7fda2f1b41 100644
--- a/infra/config/generated/luci-milo.cfg
+++ b/infra/config/generated/luci-milo.cfg
@@ -10597,26 +10597,11 @@
     short_name: "64"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/Win Builder"
-    category: "chromium.win|release|builder"
-    short_name: "32"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/Win x64 Builder"
     category: "chromium.win|release|builder"
     short_name: "64"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/Win7 (32) Tests"
-    category: "chromium.win|release|tester"
-    short_name: "32"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/Win7 Tests (1)"
-    category: "chromium.win|release|tester"
-    short_name: "32"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/Win 7 Tests x64 (1)"
     category: "chromium.win|release|tester"
     short_name: "64"
@@ -10627,11 +10612,6 @@
     short_name: "w10"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/Win x64 Builder (dbg)"
-    category: "chromium.win|debug|builder"
-    short_name: "64"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/Win Builder (dbg)"
     category: "chromium.win|debug|builder"
     short_name: "32"
@@ -10642,21 +10622,6 @@
     short_name: "7"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/Win10 Tests x64 (dbg)"
-    category: "chromium.win|debug|tester"
-    short_name: "10"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/Windows deterministic"
-    category: "chromium.win|misc"
-    short_name: "det"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/WebKit Win10"
-    category: "chromium.win|misc"
-    short_name: "wbk"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/Mac Builder"
     category: "chromium.mac|release"
     short_name: "bld"
@@ -10702,11 +10667,6 @@
     short_name: "13"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/ios-device"
-    category: "chromium.mac|ios|default"
-    short_name: "dev"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/ios-simulator"
     category: "chromium.mac|ios|default"
     short_name: "sim"
@@ -10717,11 +10677,6 @@
     short_name: "ful"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/ios-simulator-noncq"
-    category: "chromium.mac|ios|default"
-    short_name: "non"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/Linux Builder"
     category: "chromium.linux|release"
     short_name: "bld"
@@ -10732,46 +10687,16 @@
     short_name: "tst"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/Network Service Linux"
-    category: "chromium.linux|release"
-    short_name: "nsl"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/linux-gcc-rel"
-    category: "chromium.linux|release"
-    short_name: "gcc"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/Deterministic Linux"
-    category: "chromium.linux|release"
-    short_name: "det"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/linux-ozone-rel"
     category: "chromium.linux|release"
     short_name: "ozo"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/linux-trusty-rel"
-    category: "chromium.linux|release"
-    short_name: "tru"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/Linux Builder (dbg)(32)"
-    category: "chromium.linux|debug|builder"
-    short_name: "32"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/Linux Builder (dbg)"
     category: "chromium.linux|debug|builder"
     short_name: "64"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/Deterministic Linux (dbg)"
-    category: "chromium.linux|debug|builder"
-    short_name: "det"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/Linux Tests (dbg)(1)"
     category: "chromium.linux|debug|tester"
     short_name: "64"
@@ -10782,11 +10707,6 @@
     short_name: "vid"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/Cast Audio Linux"
-    category: "chromium.linux|cast"
-    short_name: "aud"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/Fuchsia ARM64"
     category: "chromium.linux|fuchsia|a64"
   >
@@ -10801,21 +10721,11 @@
     short_name: "x64"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/Deterministic Fuchsia (dbg)"
-    category: "chromium.linux|fuchsia|x64"
-    short_name: "det"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/Fuchsia x64"
     category: "chromium.linux|fuchsia|x64"
     short_name: "rel"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/Linux ChromiumOS Full"
-    category: "chromium.chromiumos|default"
-    short_name: "ful"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/linux-chromeos-rel"
     category: "chromium.chromiumos|default"
     short_name: "rel"
@@ -10826,16 +10736,6 @@
     short_name: "dbg"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/chromeos-amd64-generic-asan-rel"
-    category: "chromium.chromiumos|simple|release|x64"
-    short_name: "asn"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/chromeos-amd64-generic-cfi-thin-lto-rel"
-    category: "chromium.chromiumos|simple|release|x64"
-    short_name: "cfi"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/chromeos-amd64-generic-dbg"
     category: "chromium.chromiumos|simple|debug|x64"
     short_name: "dbg"
@@ -10846,11 +10746,6 @@
     short_name: "rel"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/chromeos-arm-generic-dbg"
-    category: "chromium.chromiumos|simple|debug"
-    short_name: "arm"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/chromeos-arm-generic-rel"
     category: "chromium.chromiumos|simple|release"
     short_name: "arm"
@@ -11011,21 +10906,6 @@
     short_name: "win"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/win-asan"
-    category: "chromium.memory|win"
-    short_name: "asn"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/Mac ASan 64 Builder"
-    category: "chromium.memory|mac"
-    short_name: "bld"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/Mac ASan 64 Tests (1)"
-    category: "chromium.memory|mac"
-    short_name: "tst"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/Linux TSan Builder"
     category: "chromium.memory|linux|TSan v2"
     short_name: "bld"
@@ -11051,61 +10931,6 @@
     short_name: "sbx"
   >
   builders: <
-    name: "buildbucket/luci.chromium.ci/Linux MSan Builder"
-    category: "chromium.memory|linux|msan"
-    short_name: "bld"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/Linux MSan Tests"
-    category: "chromium.memory|linux|msan"
-    short_name: "tst"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/WebKit Linux ASAN"
-    category: "chromium.memory|linux|webkit"
-    short_name: "asn"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/WebKit Linux MSAN"
-    category: "chromium.memory|linux|webkit"
-    short_name: "msn"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/WebKit Linux Leak"
-    category: "chromium.memory|linux|webkit"
-    short_name: "lk"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/Linux Chromium OS ASan LSan Builder"
-    category: "chromium.memory|cros|asan"
-    short_name: "bld"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/Linux Chromium OS ASan LSan Tests (1)"
-    category: "chromium.memory|cros|asan"
-    short_name: "tst"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/Linux ChromiumOS MSan Builder"
-    category: "chromium.memory|cros|msan"
-    short_name: "bld"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/Linux ChromiumOS MSan Tests"
-    category: "chromium.memory|cros|msan"
-    short_name: "tst"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/android-asan"
-    category: "chromium.memory|android"
-    short_name: "asn"
-  >
-  builders: <
-    name: "buildbucket/luci.chromium.ci/Linux CFI"
-    category: "chromium.memory|cfi"
-    short_name: "lnx"
-  >
-  builders: <
     name: "buildbucket/luci.chromium.ci/Dawn Linux x64 DEPS Builder"
     category: "chromium.dawn|DEPS|Linux|Builder"
     short_name: "x64"
@@ -11166,6 +10991,14 @@
     short_name: "x64"
   >
   builders: <
+    name: "buildbucket/luci.chromium.ci/GPU Win x64 Builder"
+    category: "chromium.gpu|Windows"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci/Win10 x64 Release (NVIDIA)"
+    category: "chromium.gpu|Windows"
+  >
+  builders: <
     name: "buildbucket/luci.chromium.ci/GPU Mac Builder"
     category: "chromium.gpu|Mac"
   >
@@ -11207,6 +11040,11 @@
     category: "chromium.fyi|linux"
   >
   builders: <
+    name: "buildbucket/luci.chromium.ci/Linux Ozone Tester (Headless)"
+    category: "chromium.fyi|linux"
+    short_name: "loh"
+  >
+  builders: <
     name: "buildbucket/luci.chromium.ci/Linux Ozone Tester (Wayland)"
     category: "chromium.fyi|linux"
     short_name: "low"
@@ -11216,6 +11054,10 @@
     category: "chromium.fyi|linux"
     short_name: "lox"
   >
+  builders: <
+    name: "buildbucket/luci.chromium.ci/Win10 Tests x64 1803"
+    category: "chromium.fyi|win10|1803"
+  >
   header: <
     oncalls: <
       name: "Chromium"
@@ -12403,6 +12245,710 @@
   >
 >
 consoles: <
+  id: "main-m83"
+  name: "Chromium M83 Console"
+  repo_url: "https://chromium.googlesource.com/chromium/src"
+  refs: "regexp:refs/branch-heads/4103"
+  manifest_name: "REVISION"
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Win x64 Builder"
+    category: "chromium.win|release|builder"
+    short_name: "64"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Win 7 Tests x64 (1)"
+    category: "chromium.win|release|tester"
+    short_name: "64"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Win10 Tests x64"
+    category: "chromium.win|release|tester"
+    short_name: "w10"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Win Builder (dbg)"
+    category: "chromium.win|debug|builder"
+    short_name: "32"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Win7 Tests (dbg)(1)"
+    category: "chromium.win|debug|tester"
+    short_name: "7"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Mac Builder"
+    category: "chromium.mac|release"
+    short_name: "bld"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Mac10.10 Tests"
+    category: "chromium.mac|release"
+    short_name: "10"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Mac10.11 Tests"
+    category: "chromium.mac|release"
+    short_name: "11"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Mac10.12 Tests"
+    category: "chromium.mac|release"
+    short_name: "12"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Mac10.13 Tests"
+    category: "chromium.mac|release"
+    short_name: "13"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Mac10.14 Tests"
+    category: "chromium.mac|release"
+    short_name: "14"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/WebKit Mac10.13 (retina)"
+    category: "chromium.mac|release"
+    short_name: "ret"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Mac Builder (dbg)"
+    category: "chromium.mac|debug"
+    short_name: "bld"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Mac10.13 Tests (dbg)"
+    category: "chromium.mac|debug"
+    short_name: "13"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/ios-simulator"
+    category: "chromium.mac|ios|default"
+    short_name: "sim"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/ios-simulator-full-configs"
+    category: "chromium.mac|ios|default"
+    short_name: "ful"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Linux Builder"
+    category: "chromium.linux|release"
+    short_name: "bld"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Linux Tests"
+    category: "chromium.linux|release"
+    short_name: "tst"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/linux-ozone-rel"
+    category: "chromium.linux|release"
+    short_name: "ozo"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Linux Builder (dbg)"
+    category: "chromium.linux|debug|builder"
+    short_name: "64"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Linux Tests (dbg)(1)"
+    category: "chromium.linux|debug|tester"
+    short_name: "64"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Cast Linux"
+    category: "chromium.linux|cast"
+    short_name: "vid"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Fuchsia ARM64"
+    category: "chromium.linux|fuchsia|a64"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/fuchsia-arm64-cast"
+    category: "chromium.linux|fuchsia|cast"
+    short_name: "a64"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/fuchsia-x64-cast"
+    category: "chromium.linux|fuchsia|cast"
+    short_name: "x64"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Fuchsia x64"
+    category: "chromium.linux|fuchsia|x64"
+    short_name: "rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/linux-chromeos-rel"
+    category: "chromium.chromiumos|default"
+    short_name: "rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/linux-chromeos-dbg"
+    category: "chromium.chromiumos|default"
+    short_name: "dbg"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/chromeos-amd64-generic-dbg"
+    category: "chromium.chromiumos|simple|debug|x64"
+    short_name: "dbg"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/chromeos-amd64-generic-rel"
+    category: "chromium.chromiumos|simple|release|x64"
+    short_name: "rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/chromeos-arm-generic-rel"
+    category: "chromium.chromiumos|simple|release"
+    short_name: "arm"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/chromeos-kevin-rel"
+    category: "chromium.chromiumos|simple|release"
+    short_name: "kvn"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/android-cronet-arm-dbg"
+    category: "chromium.android|cronet|arm"
+    short_name: "dbg"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/android-cronet-arm-rel"
+    category: "chromium.android|cronet|arm"
+    short_name: "rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/android-cronet-kitkat-arm-rel"
+    category: "chromium.android|cronet|test"
+    short_name: "k"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/android-cronet-lollipop-arm-rel"
+    category: "chromium.android|cronet|test"
+    short_name: "l"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Android arm Builder (dbg)"
+    category: "chromium.android|builder|arm"
+    short_name: "32"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Android arm64 Builder (dbg)"
+    category: "chromium.android|builder|arm"
+    short_name: "64"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Android x86 Builder (dbg)"
+    category: "chromium.android|builder|x86"
+    short_name: "32"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Android x64 Builder (dbg)"
+    category: "chromium.android|builder|x86"
+    short_name: "64"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Marshmallow 64 bit Tester"
+    category: "chromium.android|tester|phone"
+    short_name: "M"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Nougat Phone Tester"
+    category: "chromium.android|tester|phone"
+    short_name: "N"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Oreo Phone Tester"
+    category: "chromium.android|tester|phone"
+    short_name: "O"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/android-pie-arm64-dbg"
+    category: "chromium.android|tester|phone"
+    short_name: "P"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Android WebView M (dbg)"
+    category: "chromium.android|tester|webview"
+    short_name: "M"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Android WebView N (dbg)"
+    category: "chromium.android|tester|webview"
+    short_name: "N"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Android WebView O (dbg)"
+    category: "chromium.android|tester|webview"
+    short_name: "O"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Android WebView P (dbg)"
+    category: "chromium.android|tester|webview"
+    short_name: "P"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/android-kitkat-arm-rel"
+    category: "chromium.android|on_cq"
+    short_name: "K"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/android-marshmallow-arm64-rel"
+    category: "chromium.android|on_cq"
+    short_name: "M"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Cast Android (dbg)"
+    category: "chromium.android|on_cq"
+    short_name: "cst"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/android-pie-arm64-rel"
+    category: "chromium.android|on_cq"
+    short_name: "P"
+  >
+  builders: <
+    name: "buildbucket/luci.chrome.ci/linux-chromeos-chrome"
+    category: "chrome"
+    short_name: "cro"
+  >
+  builders: <
+    name: "buildbucket/luci.chrome.ci/linux-chrome"
+    category: "chrome"
+    short_name: "lnx"
+  >
+  builders: <
+    name: "buildbucket/luci.chrome.ci/mac-chrome"
+    category: "chrome"
+    short_name: "mac"
+  >
+  builders: <
+    name: "buildbucket/luci.chrome.ci/win-chrome"
+    category: "chrome"
+    short_name: "win"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Linux TSan Builder"
+    category: "chromium.memory|linux|TSan v2"
+    short_name: "bld"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Linux TSan Tests"
+    category: "chromium.memory|linux|TSan v2"
+    short_name: "tst"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Linux ASan LSan Builder"
+    category: "chromium.memory|linux|asan lsan"
+    short_name: "bld"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Linux ASan LSan Tests (1)"
+    category: "chromium.memory|linux|asan lsan"
+    short_name: "tst"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Linux ASan Tests (sandboxed)"
+    category: "chromium.memory|linux|asan lsan"
+    short_name: "sbx"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Dawn Linux x64 DEPS Builder"
+    category: "chromium.dawn|DEPS|Linux|Builder"
+    short_name: "x64"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Dawn Linux x64 DEPS Release (Intel HD 630)"
+    category: "chromium.dawn|DEPS|Linux|Intel"
+    short_name: "x64"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Dawn Linux x64 DEPS Release (NVIDIA)"
+    category: "chromium.dawn|DEPS|Linux|Nvidia"
+    short_name: "x64"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Dawn Mac x64 DEPS Builder"
+    category: "chromium.dawn|DEPS|Mac|Builder"
+    short_name: "x64"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Dawn Mac x64 DEPS Release (AMD)"
+    category: "chromium.dawn|DEPS|Mac|AMD"
+    short_name: "x64"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Dawn Mac x64 DEPS Release (Intel)"
+    category: "chromium.dawn|DEPS|Mac|Intel"
+    short_name: "x64"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Dawn Win10 x86 DEPS Builder"
+    category: "chromium.dawn|DEPS|Windows|Builder"
+    short_name: "x86"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Dawn Win10 x64 DEPS Builder"
+    category: "chromium.dawn|DEPS|Windows|Builder"
+    short_name: "x64"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Dawn Win10 x86 DEPS Release (Intel HD 630)"
+    category: "chromium.dawn|DEPS|Windows|Intel"
+    short_name: "x86"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Dawn Win10 x64 DEPS Release (Intel HD 630)"
+    category: "chromium.dawn|DEPS|Windows|Intel"
+    short_name: "x64"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Dawn Win10 x86 DEPS Release (NVIDIA)"
+    category: "chromium.dawn|DEPS|Windows|Nvidia"
+    short_name: "x86"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Dawn Win10 x64 DEPS Release (NVIDIA)"
+    category: "chromium.dawn|DEPS|Windows|Nvidia"
+    short_name: "x64"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/GPU Win x64 Builder"
+    category: "chromium.gpu|Windows"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Win10 x64 Release (NVIDIA)"
+    category: "chromium.gpu|Windows"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/GPU Mac Builder"
+    category: "chromium.gpu|Mac"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Mac Release (Intel)"
+    category: "chromium.gpu|Mac"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Mac Retina Release (AMD)"
+    category: "chromium.gpu|Mac"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/GPU Linux Builder"
+    category: "chromium.gpu|Linux"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Linux Release (NVIDIA)"
+    category: "chromium.gpu|Linux"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Android Release (Nexus 5X)"
+    category: "chromium.gpu|Android"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/ios-simulator-cronet"
+    category: "chromium.fyi|cronet"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/mac-osxbeta-rel"
+    category: "chromium.fyi|mac"
+    short_name: "beta"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/chromeos-kevin-rel-hw-tests"
+    category: "chromium.fyi|chromos"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/VR Linux"
+    category: "chromium.fyi|linux"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Linux Ozone Tester (Headless)"
+    category: "chromium.fyi|linux"
+    short_name: "loh"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Linux Ozone Tester (Wayland)"
+    category: "chromium.fyi|linux"
+    short_name: "low"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Linux Ozone Tester (X11)"
+    category: "chromium.fyi|linux"
+    short_name: "lox"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.ci-m83/Win10 Tests x64 1803"
+    category: "chromium.fyi|win10|1803"
+  >
+  header: <
+    oncalls: <
+      name: "Chromium"
+      url: "https://rota-ng.appspot.com/legacy/sheriff.json"
+    >
+    oncalls: <
+      name: "Android"
+      url: "https://rota-ng.appspot.com/legacy/sheriff_android.json"
+    >
+    oncalls: <
+      name: "iOS"
+      url: "https://rota-ng.appspot.com/legacy/sheriff_ios.json"
+    >
+    oncalls: <
+      name: "GPU"
+      url: "https://rota-ng.appspot.com/legacy/sheriff_gpu.json"
+    >
+    oncalls: <
+      name: "Angle"
+      url: "https://rota-ng.appspot.com/legacy/sheriff_angle.json"
+    >
+    oncalls: <
+      name: "Perf"
+      url: "https://rota-ng.appspot.com/legacy/sheriff_perf.json"
+    >
+    oncalls: <
+      name: "Perfbot"
+      url: "https://rota-ng.appspot.com/legacy/sheriff_perfbot.json"
+    >
+    oncalls: <
+      name: "V8"
+      url: "https://rota-ng.appspot.com/legacy/sheriff_v8.json"
+    >
+    oncalls: <
+      name: "Trooper"
+      url: "https://rota-ng.appspot.com/legacy/trooper.json"
+    >
+    links: <
+      name: "Builds"
+      links: <
+        text: "continuous"
+        url: "https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html"
+        alt: "Continuous browser snapshots"
+      >
+      links: <
+        text: "symbols"
+        url: "https://www.chromium.org/developers/how-tos/debugging-on-windows"
+        alt: "Windows Symbols"
+      >
+      links: <
+        text: "status"
+        url: "https://chromium-status.appspot.com/"
+        alt: "Current tree status"
+      >
+    >
+    links: <
+      name: "Dashboards"
+      links: <
+        text: "perf"
+        url: "https://chromeperf.appspot.com/"
+        alt: "Chrome perf dashboard"
+      >
+      links: <
+        text: "flake-portal"
+        url: "https://analysis.chromium.org/p/chromium/flake-portal"
+        alt: "New flake portal"
+      >
+      links: <
+        text: "legacy-flakiness"
+        url: "https://test-results.appspot.com/dashboards/flakiness_dashboard.html"
+        alt: "Legacy flakiness dashboard"
+      >
+    >
+    links: <
+      name: "Chromium"
+      links: <
+        text: "source"
+        url: "https://chromium.googlesource.com/chromium/src"
+        alt: "Chromium source code repository"
+      >
+      links: <
+        text: "reviews"
+        url: "https://chromium-review.googlesource.com"
+        alt: "Chromium code review tool"
+      >
+      links: <
+        text: "bugs"
+        url: "https://crbug.com"
+        alt: "Chromium bug tracker"
+      >
+      links: <
+        text: "coverage"
+        url: "https://analysis.chromium.org/p/chromium/coverage"
+        alt: "Chromium code coverage dashboard"
+      >
+      links: <
+        text: "dev"
+        url: "https://dev.chromium.org/Home"
+        alt: "Chromium developer home page"
+      >
+      links: <
+        text: "support"
+        url: "https://support.google.com/chrome/#topic=7438008"
+        alt: "Google Chrome help center"
+      >
+    >
+    links: <
+      name: "Consoles"
+      links: <
+        text: "android"
+        url: "/p/chromium/g/chromium.android"
+        alt: "Chromium Android console"
+      >
+      links: <
+        text: "clang"
+        url: "/p/chromium/g/chromium.clang"
+        alt: "Chromium Clang console"
+      >
+      links: <
+        text: "dawn"
+        url: "/p/chromium/g/chromium.dawn"
+        alt: "Chromium Dawn console"
+      >
+      links: <
+        text: "fuzz"
+        url: "/p/chromium/g/chromium.fuzz"
+        alt: "Chromium Fuzz console"
+      >
+      links: <
+        text: "fyi"
+        url: "/p/chromium/g/chromium.fyi"
+        alt: "Chromium FYI console"
+      >
+      links: <
+        text: "gpu"
+        url: "/p/chromium/g/chromium.gpu"
+        alt: "Chromium GPU console"
+      >
+      links: <
+        text: "perf"
+        url: "/p/chrome/g/chrome.perf/console"
+        alt: "Chromium Perf console"
+      >
+      links: <
+        text: "perf.fyi"
+        url: "/p/chrome/g/chrome.perf.fyi/console"
+        alt: "Chromium Perf FYI console"
+      >
+      links: <
+        text: "swangle"
+        url: "/p/chromium/g/chromium.swangle"
+        alt: "Chromium SWANGLE console"
+      >
+      links: <
+        text: "webrtc"
+        url: "/p/chromium/g/chromium.webrtc"
+        alt: "Chromium WebRTC console"
+      >
+      links: <
+        text: "chromiumos"
+        url: "/p/chromium/g/chromium.chromiumos"
+        alt: "ChromiumOS console"
+      >
+    >
+    links: <
+      name: "Branch Consoles"
+      links: <
+        text: "m81"
+        url: "/p/chromium/g/main-m81/console"
+        alt: "Beta branch console"
+      >
+      links: <
+        text: "m80"
+        url: "/p/chromium/g/main-m80/console"
+        alt: "Stable branch console"
+      >
+      links: <
+        text: "trunk"
+        url: "/p/chromium/g/main/console"
+        alt: "Trunk (ToT) console"
+      >
+    >
+    links: <
+      name: "Tryservers"
+      links: <
+        text: "android"
+        url: "/p/chromium/g/tryserver.chromium.android/builders"
+        alt: "Android"
+      >
+      links: <
+        text: "angle"
+        url: "/p/chromium/g/angle.try/builders"
+        alt: "Angle"
+      >
+      links: <
+        text: "blink"
+        url: "/p/chromium/g/tryserver.blink/builders"
+        alt: "Blink"
+      >
+      links: <
+        text: "chrome"
+        url: "/p/chrome/g/tryserver.chrome/builders"
+        alt: "Chrome"
+      >
+      links: <
+        text: "chromiumos"
+        url: "/p/chromium/g/tryserver.chromium.chromiumos/builders"
+        alt: "ChromiumOS"
+      >
+      links: <
+        text: "linux"
+        url: "/p/chromium/g/tryserver.chromium.linux/builders"
+        alt: "Linux"
+      >
+      links: <
+        text: "mac"
+        url: "/p/chromium/g/tryserver.chromium.mac/builders"
+        alt: "Mac"
+      >
+      links: <
+        text: "swangle"
+        url: "/p/chromium/g/tryserver.chromium.swangle/builders"
+        alt: "SWANGLE"
+      >
+      links: <
+        text: "win"
+        url: "/p/chromium/g/tryserver.chromium.win/builders"
+        alt: "Win"
+      >
+    >
+    links: <
+      name: "Navigate"
+      links: <
+        text: "about"
+        url: "http://dev.chromium.org/developers/testing/chromium-build-infrastructure/tour-of-the-chromium-buildbot"
+        alt: "Tour of the console"
+      >
+      links: <
+        text: "customize"
+        url: "https://chromium.googlesource.com/chromium/src/+/master/infra/config/luci-milo.cfg"
+        alt: "Customize this console"
+      >
+    >
+    console_groups: <
+      title: <
+        text: "Tree Closers"
+        url: "https://chromium-status.appspot.com/"
+      >
+      console_ids: "chromium/chromium"
+      console_ids: "chromium/chromium.win"
+      console_ids: "chromium/chromium.mac"
+      console_ids: "chromium/chromium.linux"
+      console_ids: "chromium/chromium.chromiumos"
+      console_ids: "chrome/chrome"
+      console_ids: "chromium/chromium.memory"
+      console_ids: "chromium/chromium.gpu"
+    >
+    console_groups: <
+      console_ids: "chromium/chromium.android"
+      console_ids: "chrome/chrome.perf"
+      console_ids: "chromium/chromium.gpu.fyi"
+      console_ids: "chromium/chromium.fuzz"
+    >
+    tree_status_host: "chromium-status.appspot.com"
+  >
+>
+consoles: <
   id: "sheriff.ios"
   name: "iOS Sheriff Console"
   repo_url: "https://chromium.googlesource.com/chromium/src"
@@ -12825,6 +13371,167 @@
   builder_view_only: true
 >
 consoles: <
+  id: "try-m83"
+  name: "try-m83"
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/android-binary-size"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/android-cronet-arm-dbg"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/android-kitkat-arm-rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/android-marshmallow-arm64-rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/android-pie-arm64-dbg"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/android-pie-arm64-rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/android_compile_dbg"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/android_compile_x64_dbg"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/android_compile_x86_dbg"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/android_cronet"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/android_optional_gpu_tests_rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/cast_shell_android"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/cast_shell_linux"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/chromeos-amd64-generic-dbg"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/chromeos-amd64-generic-rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/chromeos-arm-generic-rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/chromeos-kevin-compile-rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/chromeos-kevin-rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/chromium_presubmit"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/closure_compilation"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/dawn-linux-x64-deps-rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/dawn-mac-x64-deps-rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/dawn-win10-x64-deps-rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/dawn-win10-x86-deps-rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/fuchsia-arm64-cast"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/fuchsia-x64-cast"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/fuchsia_arm64"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/fuchsia_x64"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/ios-simulator"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/ios-simulator-cronet"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/ios-simulator-full-configs"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/linux-blink-rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/linux-chromeos-compile-dbg"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/linux-chromeos-rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/linux-libfuzzer-asan-rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/linux-ozone-rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/linux-rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/linux_chromium_asan_rel_ng"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/linux_chromium_compile_dbg_ng"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/linux_chromium_dbg_ng"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/linux_chromium_tsan_rel_ng"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/linux_layout_tests_composite_after_paint"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/linux_layout_tests_layout_ng_disabled"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/linux_optional_gpu_tests_rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/linux_vr"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/mac-rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/mac_chromium_compile_dbg_ng"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/mac_optional_gpu_tests_rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/win-libfuzzer-asan-rel"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/win10_chromium_x64_rel_ng"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/win_chromium_compile_dbg_ng"
+  >
+  builders: <
+    name: "buildbucket/luci.chromium.try-m83/win_optional_gpu_tests_rel"
+  >
+  builder_view_only: true
+>
+consoles: <
   id: "tryserver.blink"
   name: "tryserver.blink"
   builders: <
diff --git a/infra/config/generated/luci-notify.cfg b/infra/config/generated/luci-notify.cfg
index 4bb8784..eeb634e3 100644
--- a/infra/config/generated/luci-notify.cfg
+++ b/infra/config/generated/luci-notify.cfg
@@ -727,3 +727,118 @@
     name: "android-cronet-lollipop-arm-rel"
   >
 >
+notifiers: <
+  notifications: <
+    on_change: true
+    email: <
+      recipients: "cr-fuchsia+bot@chromium.org"
+    >
+  >
+  builders: <
+    bucket: "ci-m83"
+    name: "Fuchsia ARM64"
+    repository: "https://chromium.googlesource.com/chromium/src"
+  >
+>
+notifiers: <
+  notifications: <
+    on_change: true
+    email: <
+      recipients: "cr-fuchsia+bot@chromium.org"
+    >
+  >
+  builders: <
+    bucket: "ci-m83"
+    name: "Fuchsia x64"
+    repository: "https://chromium.googlesource.com/chromium/src"
+  >
+>
+notifiers: <
+  notifications: <
+    on_change: true
+    email: <
+      recipients: "cronet-bots-observer@google.com"
+    >
+  >
+  builders: <
+    bucket: "ci-m83"
+    name: "android-cronet-arm-dbg"
+    repository: "https://chromium.googlesource.com/chromium/src"
+  >
+>
+notifiers: <
+  notifications: <
+    on_change: true
+    email: <
+      recipients: "cronet-bots-observer@google.com"
+    >
+  >
+  builders: <
+    bucket: "ci-m83"
+    name: "android-cronet-arm-rel"
+    repository: "https://chromium.googlesource.com/chromium/src"
+  >
+>
+notifiers: <
+  notifications: <
+    on_change: true
+    email: <
+      recipients: "cronet-bots-observer@google.com"
+    >
+  >
+  builders: <
+    bucket: "ci-m83"
+    name: "android-cronet-kitkat-arm-rel"
+  >
+>
+notifiers: <
+  notifications: <
+    on_change: true
+    email: <
+      recipients: "cronet-bots-observer@google.com"
+    >
+  >
+  builders: <
+    bucket: "ci-m83"
+    name: "android-cronet-lollipop-arm-rel"
+  >
+>
+notifiers: <
+  notifications: <
+    on_change: true
+    email: <
+      recipients: "cr-fuchsia+bot@chromium.org"
+    >
+  >
+  builders: <
+    bucket: "ci-m83"
+    name: "fuchsia-arm64-cast"
+    repository: "https://chromium.googlesource.com/chromium/src"
+  >
+>
+notifiers: <
+  notifications: <
+    on_change: true
+    email: <
+      recipients: "cr-fuchsia+bot@chromium.org"
+    >
+  >
+  builders: <
+    bucket: "ci-m83"
+    name: "fuchsia-x64-cast"
+    repository: "https://chromium.googlesource.com/chromium/src"
+  >
+>
+notifiers: <
+  notifications: <
+    on_change: true
+    email: <
+      recipients: "cronet-bots-observer@google.com"
+    >
+  >
+  builders: <
+    bucket: "ci-m83"
+    name: "ios-simulator-cronet"
+    repository: "https://chromium.googlesource.com/chromium/src"
+  >
+>
diff --git a/infra/config/generated/luci-scheduler.cfg b/infra/config/generated/luci-scheduler.cfg
index 18ed3ee5..ffac76b 100644
--- a/infra/config/generated/luci-scheduler.cfg
+++ b/infra/config/generated/luci-scheduler.cfg
@@ -9050,6 +9050,939 @@
   >
 >
 job: <
+  id: "ci-m83-Android Release (Nexus 5X)"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Android Release (Nexus 5X)"
+  >
+>
+job: <
+  id: "ci-m83-Android WebView M (dbg)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Android WebView M (dbg)"
+  >
+>
+job: <
+  id: "ci-m83-Android WebView N (dbg)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Android WebView N (dbg)"
+  >
+>
+job: <
+  id: "ci-m83-Android WebView O (dbg)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Android WebView O (dbg)"
+  >
+>
+job: <
+  id: "ci-m83-Android WebView P (dbg)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Android WebView P (dbg)"
+  >
+>
+job: <
+  id: "ci-m83-Android arm Builder (dbg)"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Android arm Builder (dbg)"
+  >
+>
+job: <
+  id: "ci-m83-Android arm64 Builder (dbg)"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Android arm64 Builder (dbg)"
+  >
+>
+job: <
+  id: "ci-m83-Android x64 Builder (dbg)"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Android x64 Builder (dbg)"
+  >
+>
+job: <
+  id: "ci-m83-Android x86 Builder (dbg)"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Android x86 Builder (dbg)"
+  >
+>
+job: <
+  id: "ci-m83-Cast Android (dbg)"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Cast Android (dbg)"
+  >
+>
+job: <
+  id: "ci-m83-Cast Linux"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Cast Linux"
+  >
+>
+job: <
+  id: "ci-m83-Dawn Linux x64 DEPS Builder"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Dawn Linux x64 DEPS Builder"
+  >
+>
+job: <
+  id: "ci-m83-Dawn Linux x64 DEPS Release (Intel HD 630)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Dawn Linux x64 DEPS Release (Intel HD 630)"
+  >
+>
+job: <
+  id: "ci-m83-Dawn Linux x64 DEPS Release (NVIDIA)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Dawn Linux x64 DEPS Release (NVIDIA)"
+  >
+>
+job: <
+  id: "ci-m83-Dawn Mac x64 DEPS Builder"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Dawn Mac x64 DEPS Builder"
+  >
+>
+job: <
+  id: "ci-m83-Dawn Mac x64 DEPS Release (AMD)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Dawn Mac x64 DEPS Release (AMD)"
+  >
+>
+job: <
+  id: "ci-m83-Dawn Mac x64 DEPS Release (Intel)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Dawn Mac x64 DEPS Release (Intel)"
+  >
+>
+job: <
+  id: "ci-m83-Dawn Win10 x64 DEPS Builder"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Dawn Win10 x64 DEPS Builder"
+  >
+>
+job: <
+  id: "ci-m83-Dawn Win10 x64 DEPS Release (Intel HD 630)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Dawn Win10 x64 DEPS Release (Intel HD 630)"
+  >
+>
+job: <
+  id: "ci-m83-Dawn Win10 x64 DEPS Release (NVIDIA)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Dawn Win10 x64 DEPS Release (NVIDIA)"
+  >
+>
+job: <
+  id: "ci-m83-Dawn Win10 x86 DEPS Builder"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Dawn Win10 x86 DEPS Builder"
+  >
+>
+job: <
+  id: "ci-m83-Dawn Win10 x86 DEPS Release (Intel HD 630)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Dawn Win10 x86 DEPS Release (Intel HD 630)"
+  >
+>
+job: <
+  id: "ci-m83-Dawn Win10 x86 DEPS Release (NVIDIA)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Dawn Win10 x86 DEPS Release (NVIDIA)"
+  >
+>
+job: <
+  id: "ci-m83-Fuchsia ARM64"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Fuchsia ARM64"
+  >
+>
+job: <
+  id: "ci-m83-Fuchsia x64"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Fuchsia x64"
+  >
+>
+job: <
+  id: "ci-m83-GPU Linux Builder"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "GPU Linux Builder"
+  >
+>
+job: <
+  id: "ci-m83-GPU Mac Builder"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "GPU Mac Builder"
+  >
+>
+job: <
+  id: "ci-m83-GPU Win x64 Builder"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "GPU Win x64 Builder"
+  >
+>
+job: <
+  id: "ci-m83-Linux ASan LSan Builder"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Linux ASan LSan Builder"
+  >
+>
+job: <
+  id: "ci-m83-Linux ASan LSan Tests (1)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Linux ASan LSan Tests (1)"
+  >
+>
+job: <
+  id: "ci-m83-Linux ASan Tests (sandboxed)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Linux ASan Tests (sandboxed)"
+  >
+>
+job: <
+  id: "ci-m83-Linux Builder"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Linux Builder"
+  >
+>
+job: <
+  id: "ci-m83-Linux Builder (dbg)"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Linux Builder (dbg)"
+  >
+>
+job: <
+  id: "ci-m83-Linux Ozone Tester (Headless)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Linux Ozone Tester (Headless)"
+  >
+>
+job: <
+  id: "ci-m83-Linux Ozone Tester (Wayland)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Linux Ozone Tester (Wayland)"
+  >
+>
+job: <
+  id: "ci-m83-Linux Ozone Tester (X11)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Linux Ozone Tester (X11)"
+  >
+>
+job: <
+  id: "ci-m83-Linux Release (NVIDIA)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Linux Release (NVIDIA)"
+  >
+>
+job: <
+  id: "ci-m83-Linux TSan Builder"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Linux TSan Builder"
+  >
+>
+job: <
+  id: "ci-m83-Linux TSan Tests"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Linux TSan Tests"
+  >
+>
+job: <
+  id: "ci-m83-Linux Tests"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Linux Tests"
+  >
+>
+job: <
+  id: "ci-m83-Linux Tests (dbg)(1)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Linux Tests (dbg)(1)"
+  >
+>
+job: <
+  id: "ci-m83-Mac Builder"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Mac Builder"
+  >
+>
+job: <
+  id: "ci-m83-Mac Builder (dbg)"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Mac Builder (dbg)"
+  >
+>
+job: <
+  id: "ci-m83-Mac Release (Intel)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Mac Release (Intel)"
+  >
+>
+job: <
+  id: "ci-m83-Mac Retina Release (AMD)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Mac Retina Release (AMD)"
+  >
+>
+job: <
+  id: "ci-m83-Mac10.10 Tests"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Mac10.10 Tests"
+  >
+>
+job: <
+  id: "ci-m83-Mac10.11 Tests"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Mac10.11 Tests"
+  >
+>
+job: <
+  id: "ci-m83-Mac10.12 Tests"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Mac10.12 Tests"
+  >
+>
+job: <
+  id: "ci-m83-Mac10.13 Tests"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Mac10.13 Tests"
+  >
+>
+job: <
+  id: "ci-m83-Mac10.13 Tests (dbg)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Mac10.13 Tests (dbg)"
+  >
+>
+job: <
+  id: "ci-m83-Mac10.14 Tests"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Mac10.14 Tests"
+  >
+>
+job: <
+  id: "ci-m83-Marshmallow 64 bit Tester"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Marshmallow 64 bit Tester"
+  >
+>
+job: <
+  id: "ci-m83-Nougat Phone Tester"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Nougat Phone Tester"
+  >
+>
+job: <
+  id: "ci-m83-Oreo Phone Tester"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Oreo Phone Tester"
+  >
+>
+job: <
+  id: "ci-m83-VR Linux"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "VR Linux"
+  >
+>
+job: <
+  id: "ci-m83-WebKit Mac10.13 (retina)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "WebKit Mac10.13 (retina)"
+  >
+>
+job: <
+  id: "ci-m83-Win 7 Tests x64 (1)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Win 7 Tests x64 (1)"
+  >
+>
+job: <
+  id: "ci-m83-Win Builder (dbg)"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Win Builder (dbg)"
+  >
+>
+job: <
+  id: "ci-m83-Win x64 Builder"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Win x64 Builder"
+  >
+>
+job: <
+  id: "ci-m83-Win10 Tests x64"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Win10 Tests x64"
+  >
+>
+job: <
+  id: "ci-m83-Win10 Tests x64 1803"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Win10 Tests x64 1803"
+  >
+>
+job: <
+  id: "ci-m83-Win10 x64 Release (NVIDIA)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Win10 x64 Release (NVIDIA)"
+  >
+>
+job: <
+  id: "ci-m83-Win7 Tests (dbg)(1)"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "Win7 Tests (dbg)(1)"
+  >
+>
+job: <
+  id: "ci-m83-android-cronet-arm-dbg"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "android-cronet-arm-dbg"
+  >
+>
+job: <
+  id: "ci-m83-android-cronet-arm-rel"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "android-cronet-arm-rel"
+  >
+>
+job: <
+  id: "ci-m83-android-cronet-kitkat-arm-rel"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "android-cronet-kitkat-arm-rel"
+  >
+>
+job: <
+  id: "ci-m83-android-cronet-lollipop-arm-rel"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "android-cronet-lollipop-arm-rel"
+  >
+>
+job: <
+  id: "ci-m83-android-kitkat-arm-rel"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "android-kitkat-arm-rel"
+  >
+>
+job: <
+  id: "ci-m83-android-marshmallow-arm64-rel"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "android-marshmallow-arm64-rel"
+  >
+>
+job: <
+  id: "ci-m83-android-pie-arm64-dbg"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "android-pie-arm64-dbg"
+  >
+>
+job: <
+  id: "ci-m83-android-pie-arm64-rel"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "android-pie-arm64-rel"
+  >
+>
+job: <
+  id: "ci-m83-chromeos-amd64-generic-dbg"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "chromeos-amd64-generic-dbg"
+  >
+>
+job: <
+  id: "ci-m83-chromeos-amd64-generic-rel"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "chromeos-amd64-generic-rel"
+  >
+>
+job: <
+  id: "ci-m83-chromeos-arm-generic-rel"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "chromeos-arm-generic-rel"
+  >
+>
+job: <
+  id: "ci-m83-chromeos-kevin-rel"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "chromeos-kevin-rel"
+  >
+>
+job: <
+  id: "ci-m83-chromeos-kevin-rel-hw-tests"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "chromeos-kevin-rel-hw-tests"
+  >
+>
+job: <
+  id: "ci-m83-fuchsia-arm64-cast"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "fuchsia-arm64-cast"
+  >
+>
+job: <
+  id: "ci-m83-fuchsia-x64-cast"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "fuchsia-x64-cast"
+  >
+>
+job: <
+  id: "ci-m83-ios-simulator"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "ios-simulator"
+  >
+>
+job: <
+  id: "ci-m83-ios-simulator-cronet"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "ios-simulator-cronet"
+  >
+>
+job: <
+  id: "ci-m83-ios-simulator-full-configs"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "ios-simulator-full-configs"
+  >
+>
+job: <
+  id: "ci-m83-linux-chromeos-dbg"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "linux-chromeos-dbg"
+  >
+>
+job: <
+  id: "ci-m83-linux-chromeos-rel"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "linux-chromeos-rel"
+  >
+>
+job: <
+  id: "ci-m83-linux-ozone-rel"
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "linux-ozone-rel"
+  >
+>
+job: <
+  id: "ci-m83-mac-osxbeta-rel"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  buildbucket: <
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci-m83"
+    builder: "mac-osxbeta-rel"
+  >
+>
+job: <
   id: "ci-mac-archive-dbg"
   acl_sets: "ci"
   buildbucket: <
@@ -10206,6 +11139,16 @@
   noop: <>
 >
 job: <
+  id: "ci-m83-Android WebView L (dbg)"
+  schedule: "triggered"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  noop: <>
+>
+job: <
   id: "ci-m80-KitKat Phone Tester (dbg)"
   schedule: "triggered"
   acls: <
@@ -10226,6 +11169,16 @@
   noop: <>
 >
 job: <
+  id: "ci-m83-KitKat Phone Tester (dbg)"
+  schedule: "triggered"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  noop: <>
+>
+job: <
   id: "ci-m80-KitKat Tablet Tester"
   schedule: "triggered"
   acls: <
@@ -10246,6 +11199,16 @@
   noop: <>
 >
 job: <
+  id: "ci-m83-KitKat Tablet Tester"
+  schedule: "triggered"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  noop: <>
+>
+job: <
   id: "ci-m80-Lollipop Phone Tester"
   schedule: "triggered"
   acls: <
@@ -10266,6 +11229,16 @@
   noop: <>
 >
 job: <
+  id: "ci-m83-Lollipop Phone Tester"
+  schedule: "triggered"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  noop: <>
+>
+job: <
   id: "ci-m80-Lollipop Tablet Tester"
   schedule: "triggered"
   acls: <
@@ -10286,6 +11259,16 @@
   noop: <>
 >
 job: <
+  id: "ci-m83-Lollipop Tablet Tester"
+  schedule: "triggered"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  noop: <>
+>
+job: <
   id: "ci-m80-Marshmallow Tablet Tester"
   schedule: "triggered"
   acls: <
@@ -10305,6 +11288,16 @@
   acl_sets: "ci-m81"
   noop: <>
 >
+job: <
+  id: "ci-m83-Marshmallow Tablet Tester"
+  schedule: "triggered"
+  acls: <
+    role: TRIGGERER
+    granted_to: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+  >
+  acl_sets: "ci-m83"
+  noop: <>
+>
 trigger: <
   id: "m80-gitiles-trigger"
   acl_sets: "ci-m80"
@@ -10359,6 +11352,57 @@
   >
 >
 trigger: <
+  id: "m83-gitiles-trigger"
+  acl_sets: "ci-m83"
+  triggers: "ci-m83-Android Release (Nexus 5X)"
+  triggers: "ci-m83-Android arm Builder (dbg)"
+  triggers: "ci-m83-Android arm64 Builder (dbg)"
+  triggers: "ci-m83-Android x64 Builder (dbg)"
+  triggers: "ci-m83-Android x86 Builder (dbg)"
+  triggers: "ci-m83-Cast Android (dbg)"
+  triggers: "ci-m83-Cast Linux"
+  triggers: "ci-m83-Dawn Linux x64 DEPS Builder"
+  triggers: "ci-m83-Dawn Mac x64 DEPS Builder"
+  triggers: "ci-m83-Dawn Win10 x64 DEPS Builder"
+  triggers: "ci-m83-Dawn Win10 x86 DEPS Builder"
+  triggers: "ci-m83-Fuchsia ARM64"
+  triggers: "ci-m83-Fuchsia x64"
+  triggers: "ci-m83-GPU Linux Builder"
+  triggers: "ci-m83-GPU Mac Builder"
+  triggers: "ci-m83-GPU Win x64 Builder"
+  triggers: "ci-m83-Linux ASan LSan Builder"
+  triggers: "ci-m83-Linux Builder"
+  triggers: "ci-m83-Linux Builder (dbg)"
+  triggers: "ci-m83-Linux TSan Builder"
+  triggers: "ci-m83-Mac Builder"
+  triggers: "ci-m83-Mac Builder (dbg)"
+  triggers: "ci-m83-VR Linux"
+  triggers: "ci-m83-Win Builder (dbg)"
+  triggers: "ci-m83-Win x64 Builder"
+  triggers: "ci-m83-android-cronet-arm-dbg"
+  triggers: "ci-m83-android-cronet-arm-rel"
+  triggers: "ci-m83-android-kitkat-arm-rel"
+  triggers: "ci-m83-android-marshmallow-arm64-rel"
+  triggers: "ci-m83-android-pie-arm64-rel"
+  triggers: "ci-m83-chromeos-amd64-generic-dbg"
+  triggers: "ci-m83-chromeos-amd64-generic-rel"
+  triggers: "ci-m83-chromeos-arm-generic-rel"
+  triggers: "ci-m83-chromeos-kevin-rel"
+  triggers: "ci-m83-chromeos-kevin-rel-hw-tests"
+  triggers: "ci-m83-fuchsia-arm64-cast"
+  triggers: "ci-m83-fuchsia-x64-cast"
+  triggers: "ci-m83-ios-simulator"
+  triggers: "ci-m83-ios-simulator-cronet"
+  triggers: "ci-m83-ios-simulator-full-configs"
+  triggers: "ci-m83-linux-chromeos-dbg"
+  triggers: "ci-m83-linux-chromeos-rel"
+  triggers: "ci-m83-linux-ozone-rel"
+  gitiles: <
+    repo: "https://chromium.googlesource.com/chromium/src"
+    refs: "regexp:refs/branch-heads/4103"
+  >
+>
+trigger: <
   id: "master-gitiles-trigger"
   acl_sets: "ci"
   triggers: "ASAN Debug"
@@ -10389,9 +11433,9 @@
   triggers: "Android WebView P Blink-CORS FYI (rel)"
   triggers: "Android WebView P FYI (rel)"
   triggers: "ci-Android arm Builder (dbg)"
-  triggers: "Android arm64 Builder (dbg)"
-  triggers: "Android x64 Builder (dbg)"
-  triggers: "Android x86 Builder (dbg)"
+  triggers: "ci-Android arm64 Builder (dbg)"
+  triggers: "ci-Android x64 Builder (dbg)"
+  triggers: "ci-Android x86 Builder (dbg)"
   triggers: "CFI Linux CF"
   triggers: "CFI Linux ToT"
   triggers: "ci-Cast Android (dbg)"
@@ -10402,13 +11446,13 @@
   triggers: "CrWinAsan"
   triggers: "CrWinAsan(dll)"
   triggers: "Dawn Linux x64 Builder"
-  triggers: "Dawn Linux x64 DEPS Builder"
+  triggers: "ci-Dawn Linux x64 DEPS Builder"
   triggers: "Dawn Mac x64 Builder"
-  triggers: "Dawn Mac x64 DEPS Builder"
+  triggers: "ci-Dawn Mac x64 DEPS Builder"
   triggers: "Dawn Win10 x64 Builder"
-  triggers: "Dawn Win10 x64 DEPS Builder"
+  triggers: "ci-Dawn Win10 x64 DEPS Builder"
   triggers: "Dawn Win10 x86 Builder"
-  triggers: "Dawn Win10 x86 DEPS Builder"
+  triggers: "ci-Dawn Win10 x86 DEPS Builder"
   triggers: "Deterministic Android"
   triggers: "Deterministic Android (dbg)"
   triggers: "Deterministic Fuchsia (dbg)"
@@ -10455,7 +11499,7 @@
   triggers: "Libfuzzer Upload Windows ASan"
   triggers: "ci-Linux ASan LSan Builder"
   triggers: "ci-Linux Builder"
-  triggers: "Linux Builder (dbg)"
+  triggers: "ci-Linux Builder (dbg)"
   triggers: "Linux Builder (dbg)(32)"
   triggers: "Linux CFI"
   triggers: "Linux Chromium OS ASan LSan Builder"
@@ -10464,7 +11508,7 @@
   triggers: "Linux FYI GPU TSAN Release"
   triggers: "Linux FYI SkiaRenderer Dawn Release (Intel HD 630)"
   triggers: "Linux MSan Builder"
-  triggers: "Linux TSan Builder"
+  triggers: "ci-Linux TSan Builder"
   triggers: "Linux Viz"
   triggers: "Linux remote_run Builder"
   triggers: "MSAN Release (chained origins)"
@@ -10518,7 +11562,7 @@
   triggers: "UBSan Release"
   triggers: "UBSan vptr Release"
   triggers: "UBSanVptr Linux"
-  triggers: "VR Linux"
+  triggers: "ci-VR Linux"
   triggers: "WebKit Linux ASAN"
   triggers: "WebKit Linux Leak"
   triggers: "WebKit Linux MSAN"
@@ -10538,7 +11582,7 @@
   triggers: "android-asan"
   triggers: "android-bfcache-rel"
   triggers: "android-code-coverage-native"
-  triggers: "android-cronet-arm-dbg"
+  triggers: "ci-android-cronet-arm-dbg"
   triggers: "ci-android-cronet-arm-rel"
   triggers: "android-cronet-arm64-dbg"
   triggers: "android-cronet-arm64-rel"
@@ -10551,30 +11595,30 @@
   triggers: "android-lollipop-arm-rel"
   triggers: "ci-android-marshmallow-arm64-rel"
   triggers: "android-mojo-webview-rel"
-  triggers: "android-pie-arm64-rel"
+  triggers: "ci-android-pie-arm64-rel"
   triggers: "android-pie-x86-fyi-rel"
   triggers: "android-pie-x86-rel"
   triggers: "chromeos-amd64-generic-asan-rel"
   triggers: "chromeos-amd64-generic-cfi-thin-lto-rel"
-  triggers: "chromeos-amd64-generic-dbg"
+  triggers: "ci-chromeos-amd64-generic-dbg"
   triggers: "ci-chromeos-amd64-generic-rel"
   triggers: "chromeos-amd64-generic-rel-vm-tests"
   triggers: "chromeos-arm-generic-dbg"
   triggers: "ci-chromeos-arm-generic-rel"
-  triggers: "chromeos-kevin-rel"
-  triggers: "chromeos-kevin-rel-hw-tests"
-  triggers: "fuchsia-arm64-cast"
+  triggers: "ci-chromeos-kevin-rel"
+  triggers: "ci-chromeos-kevin-rel-hw-tests"
+  triggers: "ci-fuchsia-arm64-cast"
   triggers: "fuchsia-fyi-arm64-rel"
   triggers: "fuchsia-fyi-x64-dbg"
   triggers: "fuchsia-fyi-x64-rel"
-  triggers: "fuchsia-x64-cast"
+  triggers: "ci-fuchsia-x64-cast"
   triggers: "fuchsia-x64-dbg"
   triggers: "ios-device"
   triggers: "ci-ios-simulator"
   triggers: "ios-simulator-code-coverage"
   triggers: "ios-simulator-cr-recipe"
-  triggers: "ios-simulator-cronet"
-  triggers: "ios-simulator-full-configs"
+  triggers: "ci-ios-simulator-cronet"
+  triggers: "ci-ios-simulator-full-configs"
   triggers: "ios-simulator-noncq"
   triggers: "ios13-beta-simulator"
   triggers: "ios13-sdk-device"
@@ -10757,6 +11801,16 @@
   >
 >
 acl_sets: <
+  name: "ci-m83"
+  acls: <
+    role: OWNER
+    granted_to: "group:project-chromium-admins"
+  >
+  acls: <
+    granted_to: "group:all"
+  >
+>
+acl_sets: <
   name: "findit"
   acls: <
     role: OWNER
diff --git a/infra/config/generators/scheduler-noop-jobs.star b/infra/config/generators/scheduler-noop-jobs.star
index 8ced9a2..f66914d 100644
--- a/infra/config/generators/scheduler-noop-jobs.star
+++ b/infra/config/generators/scheduler-noop-jobs.star
@@ -23,6 +23,7 @@
 ) for builder in _ANDROID_NON_BRANCHED_TESTERS for bucket in (
     'ci-m80',
     'ci-m81',
+    'ci-m83',
 )]
 
 
diff --git a/infra/config/main.star b/infra/config/main.star
index 1bd98e50..81b2910 100755
--- a/infra/config/main.star
+++ b/infra/config/main.star
@@ -132,6 +132,7 @@
 exec('//consoles/sheriff.ios.star')
 exec('//consoles/try-m80.star')
 exec('//consoles/try-m81.star')
+exec('//consoles/try-m83.star')
 exec('//consoles/tryserver.blink.star')
 exec('//consoles/tryserver.chromium.android.star')
 exec('//consoles/tryserver.chromium.chromiumos.star')
diff --git a/infra/config/versioned/milestones/m83/buckets/ci.star b/infra/config/versioned/milestones/m83/buckets/ci.star
new file mode 100644
index 0000000..ab31ada
--- /dev/null
+++ b/infra/config/versioned/milestones/m83/buckets/ci.star
@@ -0,0 +1,501 @@
+load('//lib/builders.star', 'builder_name', 'cpu', 'goma', 'os')
+load('//lib/ci.star', 'ci')
+# Load this using relative path so that the load statement doesn't
+# need to be changed when making a new milestone
+load('../vars.star', 'vars')
+
+luci.bucket(
+    name = vars.ci_bucket,
+    acls = [
+        acl.entry(
+            roles = acl.BUILDBUCKET_READER,
+            groups = 'all',
+        ),
+        acl.entry(
+            roles = acl.BUILDBUCKET_TRIGGERER,
+            groups = 'project-chromium-ci-schedulers',
+        ),
+        acl.entry(
+            roles = acl.BUILDBUCKET_OWNER,
+            groups = 'google/luci-task-force@google.com',
+        ),
+    ],
+)
+
+luci.gitiles_poller(
+    name = vars.ci_poller,
+    bucket = vars.ci_bucket,
+    repo = 'https://chromium.googlesource.com/chromium/src',
+    refs = [vars.ref],
+)
+
+
+ci.defaults.bucket.set(vars.ci_bucket)
+ci.defaults.bucketed_triggers.set(True)
+ci.defaults.triggered_by.set([vars.ci_poller])
+
+
+# Builders are sorted first lexicographically by the function used to define
+# them, then lexicographically by their name
+
+
+ci.android_builder(
+    name = 'Android WebView M (dbg)',
+    triggered_by = [builder_name('Android arm64 Builder (dbg)')],
+)
+
+ci.android_builder(
+    name = 'Android WebView N (dbg)',
+    triggered_by = [builder_name('Android arm64 Builder (dbg)')],
+)
+
+ci.android_builder(
+    name = 'Android WebView O (dbg)',
+    triggered_by = [builder_name('Android arm64 Builder (dbg)')],
+)
+
+ci.android_builder(
+    name = 'Android WebView P (dbg)',
+    triggered_by = [builder_name('Android arm64 Builder (dbg)')],
+)
+
+ci.android_builder(
+    name = 'Android arm Builder (dbg)',
+    execution_timeout = 4 * time.hour,
+)
+
+ci.android_builder(
+    name = 'Android arm64 Builder (dbg)',
+    goma_jobs = goma.jobs.MANY_JOBS_FOR_CI,
+    execution_timeout = 4 * time.hour,
+)
+
+ci.android_builder(
+    name = 'Android x64 Builder (dbg)',
+    execution_timeout = 4 * time.hour,
+)
+
+ci.android_builder(
+    name = 'Android x86 Builder (dbg)',
+)
+
+ci.android_builder(
+    name = 'Cast Android (dbg)',
+)
+
+ci.android_builder(
+    name = 'Marshmallow 64 bit Tester',
+    triggered_by = [builder_name('Android arm64 Builder (dbg)')],
+)
+
+ci.android_builder(
+    name = 'Nougat Phone Tester',
+    triggered_by = [builder_name('Android arm64 Builder (dbg)')],
+)
+
+ci.android_builder(
+    name = 'Oreo Phone Tester',
+    triggered_by = [builder_name('Android arm64 Builder (dbg)')],
+)
+
+ci.android_builder(
+    name = 'android-cronet-arm-dbg',
+    notifies = ['cronet'],
+)
+
+ci.android_builder(
+    name = 'android-cronet-arm-rel',
+    notifies = ['cronet'],
+)
+
+ci.android_builder(
+    name = 'android-cronet-kitkat-arm-rel',
+    notifies = ['cronet'],
+    triggered_by = [builder_name('android-cronet-arm-rel')],
+)
+
+ci.android_builder(
+    name = 'android-cronet-lollipop-arm-rel',
+    notifies = ['cronet'],
+    triggered_by = [builder_name('android-cronet-arm-rel')],
+)
+
+ci.android_builder(
+    name = 'android-kitkat-arm-rel',
+)
+
+ci.android_builder(
+    name = 'android-marshmallow-arm64-rel',
+)
+
+ci.android_builder(
+    name = 'android-pie-arm64-dbg',
+    triggered_by = [builder_name('Android arm64 Builder (dbg)')],
+)
+
+ci.android_builder(
+    name = 'android-pie-arm64-rel',
+)
+
+
+ci.chromiumos_builder(
+    name = 'chromeos-amd64-generic-dbg',
+)
+
+ci.chromiumos_builder(
+    name = 'chromeos-amd64-generic-rel',
+)
+
+ci.chromiumos_builder(
+    name = 'chromeos-arm-generic-rel',
+)
+
+ci.chromiumos_builder(
+    name = 'chromeos-kevin-rel',
+)
+
+ci.fyi_builder(
+    name = 'chromeos-kevin-rel-hw-tests',
+)
+
+ci.chromiumos_builder(
+    name = 'linux-chromeos-dbg',
+)
+
+ci.chromiumos_builder(
+    name = 'linux-chromeos-rel',
+)
+
+
+ci.dawn_builder(
+    name = 'Dawn Linux x64 DEPS Builder',
+)
+
+ci.dawn_builder(
+    name = 'Dawn Linux x64 DEPS Release (Intel HD 630)',
+    cores = 2,
+    os = os.LINUX_DEFAULT,
+    triggered_by = [builder_name('Dawn Linux x64 DEPS Builder')],
+)
+
+ci.dawn_builder(
+    name = 'Dawn Linux x64 DEPS Release (NVIDIA)',
+    cores = 2,
+    os = os.LINUX_DEFAULT,
+    triggered_by = [builder_name('Dawn Linux x64 DEPS Builder')],
+)
+
+ci.dawn_builder(
+    name = 'Dawn Mac x64 DEPS Builder',
+    builderless = False,
+    cores = None,
+    os = os.MAC_ANY,
+)
+
+# Note that the Mac testers are all thin Linux VMs, triggering jobs on the
+# physical Mac hardware in the Swarming pool which is why they run on linux
+ci.dawn_builder(
+    name = 'Dawn Mac x64 DEPS Release (AMD)',
+    cores = 2,
+    os = os.LINUX_DEFAULT,
+    triggered_by = [builder_name('Dawn Mac x64 DEPS Builder')],
+)
+
+ci.dawn_builder(
+    name = 'Dawn Mac x64 DEPS Release (Intel)',
+    cores = 2,
+    os = os.LINUX_DEFAULT,
+    triggered_by = [builder_name('Dawn Mac x64 DEPS Builder')],
+)
+
+ci.dawn_builder(
+    name = 'Dawn Win10 x64 DEPS Builder',
+    os = os.WINDOWS_ANY,
+)
+
+ci.dawn_builder(
+    name = 'Dawn Win10 x64 DEPS Release (Intel HD 630)',
+    cores = 2,
+    os = os.LINUX_DEFAULT,
+    triggered_by = [builder_name('Dawn Win10 x64 DEPS Builder')],
+)
+
+ci.dawn_builder(
+    name = 'Dawn Win10 x64 DEPS Release (NVIDIA)',
+    cores = 2,
+    os = os.LINUX_DEFAULT,
+    triggered_by = [builder_name('Dawn Win10 x64 DEPS Builder')],
+)
+
+ci.dawn_builder(
+    name = 'Dawn Win10 x86 DEPS Builder',
+    os = os.WINDOWS_ANY,
+)
+
+ci.dawn_builder(
+    name = 'Dawn Win10 x86 DEPS Release (Intel HD 630)',
+    cores = 2,
+    os = os.LINUX_DEFAULT,
+    triggered_by = [builder_name('Dawn Win10 x86 DEPS Builder')],
+)
+
+ci.dawn_builder(
+    name = 'Dawn Win10 x86 DEPS Release (NVIDIA)',
+    cores = 2,
+    os = os.LINUX_DEFAULT,
+    triggered_by = [builder_name('Dawn Win10 x86 DEPS Builder')],
+)
+
+
+ci.fyi_builder(
+    name = 'VR Linux',
+)
+
+# This is launching & collecting entirely isolated tests.
+# OS shouldn't matter.
+ci.fyi_builder(
+    name = 'mac-osxbeta-rel',
+    goma_backend = None,
+    triggered_by = [builder_name('Mac Builder')],
+)
+
+
+ci.fyi_ios_builder(
+    name = 'ios-simulator-cronet',
+    executable = 'recipe:chromium',
+    notifies = ['cronet'],
+    properties = {
+        'xcode_build_version': '11c29',
+    },
+)
+
+
+ci.fyi_windows_builder(
+    name = 'Win10 Tests x64 1803',
+    goma_backend = None,
+    os = os.WINDOWS_10,
+    triggered_by = [builder_name('Win x64 Builder')],
+)
+
+
+ci.gpu_builder(
+    name = 'Android Release (Nexus 5X)',
+)
+
+ci.gpu_builder(
+    name = 'GPU Linux Builder',
+)
+
+ci.gpu_builder(
+    name = 'GPU Mac Builder',
+    cores = None,
+    os = os.MAC_ANY,
+)
+
+ci.gpu_builder(
+    name = 'GPU Win x64 Builder',
+    builderless = True,
+    os = os.WINDOWS_ANY,
+)
+
+
+ci.gpu_thin_tester(
+    name = 'Linux Release (NVIDIA)',
+    triggered_by = [builder_name('GPU Linux Builder')],
+)
+
+ci.gpu_thin_tester(
+    name = 'Mac Release (Intel)',
+    triggered_by = [builder_name('GPU Mac Builder')],
+)
+
+ci.gpu_thin_tester(
+    name = 'Mac Retina Release (AMD)',
+    triggered_by = [builder_name('GPU Mac Builder')],
+)
+
+ci.gpu_thin_tester(
+    name = 'Win10 x64 Release (NVIDIA)',
+    triggered_by = [builder_name('GPU Win x64 Builder')],
+)
+
+
+ci.linux_builder(
+    name = 'Cast Linux',
+    goma_jobs = goma.jobs.J50,
+)
+
+ci.linux_builder(
+    name = 'Fuchsia ARM64',
+    notifies = ['cr-fuchsia'],
+)
+
+ci.linux_builder(
+    name = 'Fuchsia x64',
+    notifies = ['cr-fuchsia'],
+)
+
+ci.linux_builder(
+    name = 'Linux Builder',
+)
+
+ci.linux_builder(
+    name = 'Linux Builder (dbg)',
+)
+
+ci.linux_builder(
+    name = 'Linux Tests',
+    goma_backend = None,
+    triggered_by = [builder_name('Linux Builder')],
+)
+
+ci.linux_builder(
+    name = 'Linux Tests (dbg)(1)',
+    triggered_by = [builder_name('Linux Builder (dbg)')],
+)
+
+ci.linux_builder(
+    name = 'fuchsia-arm64-cast',
+    notifies = ['cr-fuchsia'],
+)
+
+ci.linux_builder(
+    name = 'fuchsia-x64-cast',
+    notifies = ['cr-fuchsia'],
+)
+
+ci.linux_builder(
+    name = 'linux-ozone-rel',
+)
+
+ci.linux_builder(
+    name = 'Linux Ozone Tester (Headless)',
+    triggered_by = [builder_name('linux-ozone-rel')],
+)
+
+ci.linux_builder(
+    name = 'Linux Ozone Tester (Wayland)',
+    triggered_by = [builder_name('linux-ozone-rel')],
+)
+
+ci.linux_builder(
+    name = 'Linux Ozone Tester (X11)',
+    triggered_by = [builder_name('linux-ozone-rel')],
+)
+
+
+ci.mac_builder(
+    name = 'Mac Builder',
+    os = os.MAC_10_14,
+)
+
+ci.mac_builder(
+    name = 'Mac Builder (dbg)',
+    os = os.MAC_ANY,
+)
+
+# The build runs on 10.13, but triggers tests on 10.10 bots.
+ci.mac_builder(
+    name = 'Mac10.10 Tests',
+    triggered_by = [builder_name('Mac Builder')],
+)
+
+# The build runs on 10.13, but triggers tests on 10.11 bots.
+ci.mac_builder(
+    name = 'Mac10.11 Tests',
+    triggered_by = [builder_name('Mac Builder')],
+)
+
+ci.mac_builder(
+    name = 'Mac10.12 Tests',
+    os = os.MAC_10_12,
+    triggered_by = [builder_name('Mac Builder')],
+)
+
+ci.mac_builder(
+    name = 'Mac10.13 Tests',
+    os = os.MAC_10_13,
+    triggered_by = [builder_name('Mac Builder')],
+)
+
+ci.mac_builder(
+    name = 'Mac10.14 Tests',
+    os = os.MAC_10_14,
+    triggered_by = [builder_name('Mac Builder')],
+)
+
+ci.mac_builder(
+    name = 'Mac10.13 Tests (dbg)',
+    os = os.MAC_ANY,
+    triggered_by = [builder_name('Mac Builder (dbg)')],
+)
+
+ci.mac_builder(
+    name = 'WebKit Mac10.13 (retina)',
+    os = os.MAC_10_13,
+    triggered_by = [builder_name('Mac Builder')],
+)
+
+
+ci.mac_ios_builder(
+    name = 'ios-simulator',
+)
+
+ci.mac_ios_builder(
+    name = 'ios-simulator-full-configs',
+)
+
+
+ci.memory_builder(
+    name = 'Linux ASan LSan Builder',
+    ssd = True,
+)
+
+ci.memory_builder(
+    name = 'Linux ASan LSan Tests (1)',
+    triggered_by = [builder_name('Linux ASan LSan Builder')],
+)
+
+ci.memory_builder(
+    name = 'Linux ASan Tests (sandboxed)',
+    triggered_by = [builder_name('Linux ASan LSan Builder')],
+)
+
+ci.memory_builder(
+    name = 'Linux TSan Builder',
+)
+
+ci.memory_builder(
+    name = 'Linux TSan Tests',
+    triggered_by = [builder_name('Linux TSan Builder')],
+)
+
+
+ci.win_builder(
+    name = 'Win7 Tests (dbg)(1)',
+    os = os.WINDOWS_7,
+    triggered_by = [builder_name('Win Builder (dbg)')],
+)
+
+ci.win_builder(
+    name = 'Win 7 Tests x64 (1)',
+    os = os.WINDOWS_7,
+    triggered_by = [builder_name('Win x64 Builder')],
+)
+
+ci.win_builder(
+    name = 'Win Builder (dbg)',
+    cores = 32,
+    os = os.WINDOWS_ANY,
+)
+
+ci.win_builder(
+    name = 'Win x64 Builder',
+    cores = 32,
+    os = os.WINDOWS_ANY,
+)
+
+ci.win_builder(
+    name = 'Win10 Tests x64',
+    triggered_by = [builder_name('Win x64 Builder')],
+)
diff --git a/infra/config/versioned/milestones/m83/buckets/try.star b/infra/config/versioned/milestones/m83/buckets/try.star
new file mode 100644
index 0000000..2234e286
--- /dev/null
+++ b/infra/config/versioned/milestones/m83/buckets/try.star
@@ -0,0 +1,616 @@
+load('//lib/builders.star', 'cpu', 'goma', 'os')
+load('//lib/try.star', 'try_')
+# Load this using relative path so that the load statement doesn't
+# need to be changed when making a new milestone
+load('../vars.star', 'vars')
+
+luci.bucket(
+    name = vars.try_bucket,
+    acls = [
+        acl.entry(
+            roles = acl.BUILDBUCKET_READER,
+            groups = 'all',
+        ),
+        acl.entry(
+            roles = acl.BUILDBUCKET_TRIGGERER,
+            users = [
+                'findit-for-me@appspot.gserviceaccount.com',
+                'tricium-prod@appspot.gserviceaccount.com',
+            ],
+            groups = [
+                'project-chromium-tryjob-access',
+                # Allow Pinpoint to trigger builds for bisection
+                'service-account-chromeperf',
+                'service-account-cq',
+            ],
+        ),
+        acl.entry(
+            roles = acl.BUILDBUCKET_OWNER,
+            groups = 'service-account-chromium-tryserver',
+        ),
+    ],
+)
+
+luci.cq_group(
+    name = vars.cq_group,
+    cancel_stale_tryjobs = True,
+    retry_config = cq.RETRY_ALL_FAILURES,
+    tree_status_host = getattr(vars, 'tree_status_host', None),
+    watch = cq.refset(
+        repo = 'https://chromium.googlesource.com/chromium/src',
+        refs = [vars.cq_ref_regexp],
+    ),
+    acls = [
+        acl.entry(
+            acl.CQ_COMMITTER,
+            groups = 'project-chromium-committers',
+        ),
+        acl.entry(
+            acl.CQ_DRY_RUNNER,
+            groups = 'project-chromium-tryjob-access',
+        ),
+    ],
+)
+
+try_.defaults.bucket.set(vars.try_bucket)
+try_.defaults.cq_group.set(vars.cq_group)
+
+
+# Builders are sorted first lexicographically by the function used to define
+# them, then lexicographically by their name
+
+
+try_.blink_builder(
+    name = 'linux-blink-rel',
+    goma_backend = goma.backend.RBE_PROD,
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/cc/.+',
+            '.+/[+]/third_party/blink/renderer/core/paint/.+',
+            '.+/[+]/third_party/blink/renderer/core/svg/.+',
+            '.+/[+]/third_party/blink/renderer/platform/graphics/.+',
+        ],
+    ),
+)
+
+
+try_.chromium_android_builder(
+    name = 'android-binary-size',
+    executable = 'recipe:binary_size_trybot',
+    goma_jobs = goma.jobs.J150,
+    tryjob = try_.job(),
+)
+
+try_.chromium_android_builder(
+    name = 'android-cronet-arm-dbg',
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/components/cronet/.+',
+            '.+/[+]/components/grpc_support/.+',
+            '.+/[+]/build/android/.+',
+            '.+/[+]/build/config/android/.+',
+        ],
+        location_regexp_exclude = [
+            '.+/[+]/components/cronet/ios/.+',
+        ],
+    ),
+)
+
+try_.chromium_android_builder(
+    name = 'android-kitkat-arm-rel',
+    goma_jobs = goma.jobs.J150,
+    tryjob = try_.job(),
+)
+
+try_.chromium_android_builder(
+    name = 'android-marshmallow-arm64-rel',
+    cores = 16,
+    goma_jobs = goma.jobs.J300,
+    ssd = True,
+    use_java_coverage = True,
+    tryjob = try_.job(),
+)
+
+try_.chromium_android_builder(
+    name = 'android-pie-arm64-dbg',
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/chrome/android/features/vr/.+',
+            '.+/[+]/chrome/android/java/src/org/chromium/chrome/browser/vr/.+',
+            '.+/[+]/chrome/android/javatests/src/org/chromium/chrome/browser/vr/.+',
+            '.+/[+]/chrome/browser/vr/.+',
+            '.+/[+]/third_party/gvr-android-sdk/.+',
+            '.+/[+]/third_party/arcore-android-sdk/.+',
+            '.+/[+]/third_party/arcore-android-sdk-client/.+',
+        ],
+    ),
+)
+
+try_.chromium_android_builder(
+    name = 'android-pie-arm64-rel',
+    cores = 16,
+    goma_jobs = goma.jobs.J300,
+    ssd = True,
+    tryjob = try_.job(),
+)
+
+try_.chromium_android_builder(
+    name = 'android_compile_dbg',
+    goma_jobs = goma.jobs.J150,
+    tryjob = try_.job(),
+)
+
+try_.chromium_android_builder(
+    name = 'android_compile_x64_dbg',
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/chrome/android/java/src/org/chromium/chrome/browser/vr/.+',
+            '.+/[+]/chrome/browser/vr/.+',
+            '.+/[+]/sandbox/linux/seccomp-bpf/.+',
+            '.+/[+]/sandbox/linux/seccomp-bpf-helpers/.+',
+            '.+/[+]/sandbox/linux/system_headers/.+',
+            '.+/[+]/sandbox/linux/tests/.+',
+            '.+/[+]/third_party/gvr-android-sdk/.+',
+        ],
+    ),
+)
+
+try_.chromium_android_builder(
+    name = 'android_compile_x86_dbg',
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/chrome/android/java/src/org/chromium/chrome/browser/vr/.+',
+            '.+/[+]/chrome/browser/vr/.+',
+            '.+/[+]/sandbox/linux/seccomp-bpf/.+',
+            '.+/[+]/sandbox/linux/seccomp-bpf-helpers/.+',
+            '.+/[+]/sandbox/linux/system_headers/.+',
+            '.+/[+]/sandbox/linux/tests/.+',
+            '.+/[+]/third_party/gvr-android-sdk/.+',
+        ],
+    ),
+)
+
+try_.chromium_android_builder(
+    name = 'android_cronet',
+    tryjob = try_.job(),
+)
+
+try_.chromium_android_builder(
+    name = 'cast_shell_android',
+    tryjob = try_.job(),
+)
+
+
+try_.chromium_chromiumos_builder(
+    name = 'chromeos-amd64-generic-dbg',
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/content/gpu/.+',
+            '.+/[+]/media/.+',
+        ],
+    ),
+)
+
+try_.chromium_chromiumos_builder(
+    name = 'chromeos-amd64-generic-rel',
+    tryjob = try_.job(),
+)
+
+try_.chromium_chromiumos_builder(
+    name = 'chromeos-arm-generic-rel',
+    tryjob = try_.job(),
+)
+
+try_.chromium_chromiumos_builder(
+    name = 'chromeos-kevin-compile-rel',
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/chromeos/CHROMEOS_LKGM',
+        ],
+    ),
+)
+
+try_.chromium_chromiumos_builder(
+    name = 'chromeos-kevin-rel',
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/build/chromeos/.+',
+            '.+/[+]/build/config/chromeos/.*',
+        ],
+    ),
+)
+
+try_.chromium_chromiumos_builder(
+    name = 'linux-chromeos-compile-dbg',
+    tryjob = try_.job(),
+)
+
+try_.chromium_chromiumos_builder(
+    name = 'linux-chromeos-rel',
+    goma_jobs = goma.jobs.J150,
+    tryjob = try_.job(),
+    use_clang_coverage = True,
+)
+
+
+try_.chromium_dawn_builder(
+    name = 'dawn-linux-x64-deps-rel',
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/gpu/.+',
+            '.+/[+]/testing/buildbot/chromium.dawn.json',
+            '.+/[+]/third_party/blink/renderer/modules/webgpu/.+',
+            '.+/[+]/third_party/blink/web_tests/external/wpt/webgpu/.+',
+            '.+/[+]/third_party/blink/web_tests/wpt_internal/webgpu/.+',
+            '.+/[+]/third_party/blink/web_tests/WebGPUExpectations',
+            '.+/[+]/third_party/dawn/.+',
+            '.+/[+]/tools/clang/scripts/update.py',
+            '.+/[+]/ui/gl/features.gni',
+        ],
+    ),
+)
+
+try_.chromium_dawn_builder(
+    name = 'dawn-mac-x64-deps-rel',
+    os = os.MAC_ANY,
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/gpu/.+',
+            '.+/[+]/testing/buildbot/chromium.dawn.json',
+            '.+/[+]/third_party/blink/renderer/modules/webgpu/.+',
+            '.+/[+]/third_party/blink/web_tests/external/wpt/webgpu/.+',
+            '.+/[+]/third_party/blink/web_tests/wpt_internal/webgpu/.+',
+            '.+/[+]/third_party/blink/web_tests/WebGPUExpectations',
+            '.+/[+]/third_party/dawn/.+',
+            '.+/[+]/tools/clang/scripts/update.py',
+            '.+/[+]/ui/gl/features.gni',
+        ],
+    ),
+)
+
+try_.chromium_dawn_builder(
+    name = 'dawn-win10-x64-deps-rel',
+    os = os.WINDOWS_ANY,
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/gpu/.+',
+            '.+/[+]/testing/buildbot/chromium.dawn.json',
+            '.+/[+]/third_party/blink/renderer/modules/webgpu/.+',
+            '.+/[+]/third_party/blink/web_tests/external/wpt/webgpu/.+',
+            '.+/[+]/third_party/blink/web_tests/wpt_internal/webgpu/.+',
+            '.+/[+]/third_party/blink/web_tests/WebGPUExpectations',
+            '.+/[+]/third_party/dawn/.+',
+            '.+/[+]/tools/clang/scripts/update.py',
+            '.+/[+]/ui/gl/features.gni',
+        ],
+    ),
+)
+
+try_.chromium_dawn_builder(
+    name = 'dawn-win10-x86-deps-rel',
+    os = os.WINDOWS_ANY,
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/gpu/.+',
+            '.+/[+]/testing/buildbot/chromium.dawn.json',
+            '.+/[+]/third_party/blink/renderer/modules/webgpu/.+',
+            '.+/[+]/third_party/blink/web_tests/external/wpt/webgpu/.+',
+            '.+/[+]/third_party/blink/web_tests/wpt_internal/webgpu/.+',
+            '.+/[+]/third_party/blink/web_tests/WebGPUExpectations',
+            '.+/[+]/third_party/dawn/.+',
+            '.+/[+]/tools/clang/scripts/update.py',
+            '.+/[+]/ui/gl/features.gni',
+        ],
+    ),
+)
+
+
+try_.chromium_linux_builder(
+    name = 'cast_shell_linux',
+    tryjob = try_.job(),
+)
+
+try_.chromium_linux_builder(
+    name = 'closure_compilation',
+    executable = 'recipe:closure_compilation',
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/third_party/closure_compiler/.+',
+        ],
+    ),
+)
+
+try_.chromium_linux_builder(
+    name = 'chromium_presubmit',
+    executable = 'recipe:presubmit',
+    goma_backend = None,
+    properties = {
+        '$depot_tools/presubmit': {
+            'runhooks': True,
+            'timeout_s': 480,
+        },
+        'repo_name': 'chromium',
+    },
+    tryjob = try_.job(
+        disable_reuse = True,
+    ),
+)
+
+try_.chromium_linux_builder(
+    name = 'fuchsia-arm64-cast',
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/chromecast/.+',
+        ],
+    ),
+)
+
+try_.chromium_linux_builder(
+    name = 'fuchsia-x64-cast',
+    tryjob = try_.job(),
+)
+
+try_.chromium_linux_builder(
+    name = 'fuchsia_arm64',
+    tryjob = try_.job(),
+)
+
+try_.chromium_linux_builder(
+    name = 'fuchsia_x64',
+    tryjob = try_.job(),
+)
+
+try_.chromium_linux_builder(
+    name = 'linux-libfuzzer-asan-rel',
+    executable = 'recipe:chromium_libfuzzer_trybot',
+    tryjob = try_.job(),
+)
+
+try_.chromium_linux_builder(
+    name = 'linux-ozone-rel',
+    tryjob = try_.job(),
+)
+
+try_.chromium_linux_builder(
+    name = 'linux-rel',
+    goma_jobs = goma.jobs.J150,
+    tryjob = try_.job(),
+    use_clang_coverage = True,
+)
+
+try_.chromium_linux_builder(
+    name = 'linux_chromium_asan_rel_ng',
+    goma_jobs = goma.jobs.J150,
+    ssd = True,
+    tryjob = try_.job(),
+)
+
+try_.chromium_linux_builder(
+    name = 'linux_chromium_compile_dbg_ng',
+    caches = [
+        swarming.cache(
+            name = 'builder',
+            path = 'linux_debug',
+        ),
+    ],
+    goma_jobs = goma.jobs.J150,
+    tryjob = try_.job(),
+)
+
+try_.chromium_linux_builder(
+    name = 'linux_chromium_dbg_ng',
+    caches = [
+        swarming.cache(
+            name = 'builder',
+            path = 'linux_debug',
+        ),
+    ],
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/build/.*check_gn_headers.*',
+        ],
+    ),
+)
+
+try_.chromium_linux_builder(
+    name = 'linux_chromium_tsan_rel_ng',
+    goma_jobs = goma.jobs.J150,
+    tryjob = try_.job(),
+)
+
+try_.chromium_linux_builder(
+    name = 'linux_layout_tests_composite_after_paint',
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/third_party/blink/renderer/core/paint/.+',
+            '.+/[+]/third_party/blink/renderer/core/svg/.+',
+            '.+/[+]/third_party/blink/renderer/platform/graphics/.+',
+            '.+/[+]/third_party/blink/web_tests/.+',
+        ],
+    ),
+)
+
+try_.chromium_linux_builder(
+    name = 'linux_layout_tests_layout_ng_disabled',
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/third_party/blink/renderer/core/editing/.+',
+            '.+/[+]/third_party/blink/renderer/core/layout/.+',
+            '.+/[+]/third_party/blink/renderer/core/paint/.+',
+            '.+/[+]/third_party/blink/renderer/core/svg/.+',
+            '.+/[+]/third_party/blink/renderer/platform/fonts/shaping/.+',
+            '.+/[+]/third_party/blink/renderer/platform/graphics/.+',
+            '.+/[+]/third_party/blink/web_tests/FlagExpectations/disable-layout-ng',
+            '.+/[+]/third_party/blink/web_tests/flag-specific/disable-layout-ng/.+',
+        ],
+    ),
+)
+
+try_.chromium_linux_builder(
+    name = 'linux_vr',
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/chrome/browser/vr/.+',
+        ],
+    ),
+)
+
+
+try_.chromium_mac_builder(
+    name = 'mac-rel',
+    goma_jobs = goma.jobs.J150,
+    tryjob = try_.job(),
+)
+
+try_.chromium_mac_builder(
+    name = 'mac_chromium_compile_dbg_ng',
+    goma_jobs = goma.jobs.J150,
+    os = os.MAC_10_13,
+    tryjob = try_.job(),
+)
+
+
+try_.chromium_mac_ios_builder(
+    name = 'ios-simulator',
+    tryjob = try_.job(),
+)
+
+try_.chromium_mac_ios_builder(
+    name = 'ios-simulator-cronet',
+    executable = 'recipe:chromium_trybot',
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/components/cronet/.+',
+            '.+/[+]/components/grpc_support/.+',
+            '.+/[+]/ios/.+',
+        ],
+        location_regexp_exclude = [
+            '.+/[+]/components/cronet/android/.+',
+        ],
+    ),
+)
+
+try_.chromium_mac_ios_builder(
+    name = 'ios-simulator-full-configs',
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/ios/.+',
+        ],
+    ),
+)
+
+
+try_.chromium_win_builder(
+    name = 'win-libfuzzer-asan-rel',
+    builderless = False,
+    executable = 'recipe:chromium_libfuzzer_trybot',
+    os = os.WINDOWS_ANY,
+    tryjob = try_.job(),
+)
+
+try_.chromium_win_builder(
+    name = 'win_chromium_compile_dbg_ng',
+    goma_jobs = goma.jobs.J150,
+    tryjob = try_.job(),
+)
+
+try_.chromium_win_builder(
+    name = 'win10_chromium_x64_rel_ng',
+    goma_jobs = goma.jobs.J150,
+    os = os.WINDOWS_10,
+    ssd = True,
+    use_clang_coverage = True,
+    tryjob = try_.job(),
+)
+
+
+try_.gpu_chromium_android_builder(
+    name = 'android_optional_gpu_tests_rel',
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/cc/.+',
+            '.+/[+]/chrome/browser/vr/.+',
+            '.+/[+]/components/viz/.+',
+            '.+/[+]/content/test/gpu/.+',
+            '.+/[+]/gpu/.+',
+            '.+/[+]/media/audio/.+',
+            '.+/[+]/media/filters/.+',
+            '.+/[+]/media/gpu/.+',
+            '.+/[+]/services/viz/.+',
+            '.+/[+]/testing/trigger_scripts/.+',
+            '.+/[+]/third_party/blink/renderer/modules/webgl/.+',
+            '.+/[+]/third_party/blink/renderer/platform/graphics/gpu/.+',
+            '.+/[+]/tools/clang/scripts/update.py',
+            '.+/[+]/ui/gl/.+',
+        ],
+    ),
+)
+
+
+try_.gpu_chromium_linux_builder(
+    name = 'linux_optional_gpu_tests_rel',
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/chrome/browser/vr/.+',
+            '.+/[+]/content/test/gpu/.+',
+            '.+/[+]/gpu/.+',
+            '.+/[+]/media/audio/.+',
+            '.+/[+]/media/filters/.+',
+            '.+/[+]/media/gpu/.+',
+            '.+/[+]/testing/buildbot/chromium.gpu.fyi.json',
+            '.+/[+]/testing/trigger_scripts/.+',
+            '.+/[+]/third_party/blink/renderer/modules/webgl/.+',
+            '.+/[+]/third_party/blink/renderer/platform/graphics/gpu/.+',
+            '.+/[+]/tools/clang/scripts/update.py',
+            '.+/[+]/ui/gl/.+',
+        ],
+    ),
+)
+
+
+try_.gpu_chromium_mac_builder(
+    name = 'mac_optional_gpu_tests_rel',
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/chrome/browser/vr/.+',
+            '.+/[+]/content/test/gpu/.+',
+            '.+/[+]/gpu/.+',
+            '.+/[+]/media/audio/.+',
+            '.+/[+]/media/filters/.+',
+            '.+/[+]/media/gpu/.+',
+            '.+/[+]/services/shape_detection/.+',
+            '.+/[+]/testing/buildbot/chromium.gpu.fyi.json',
+            '.+/[+]/testing/trigger_scripts/.+',
+            '.+/[+]/third_party/blink/renderer/modules/webgl/.+',
+            '.+/[+]/third_party/blink/renderer/platform/graphics/gpu/.+',
+            '.+/[+]/tools/clang/scripts/update.py',
+            '.+/[+]/ui/gl/.+',
+        ],
+    ),
+)
+
+
+try_.gpu_chromium_win_builder(
+    name = 'win_optional_gpu_tests_rel',
+    builderless = True,
+    os = os.WINDOWS_DEFAULT,
+    tryjob = try_.job(
+        location_regexp = [
+            '.+/[+]/chrome/browser/vr/.+',
+            '.+/[+]/content/test/gpu/.+',
+            '.+/[+]/device/vr/.+',
+            '.+/[+]/gpu/.+',
+            '.+/[+]/media/audio/.+',
+            '.+/[+]/media/filters/.+',
+            '.+/[+]/media/gpu/.+',
+            '.+/[+]/testing/buildbot/chromium.gpu.fyi.json',
+            '.+/[+]/testing/trigger_scripts/.+',
+            '.+/[+]/third_party/blink/renderer/modules/vr/.+',
+            '.+/[+]/third_party/blink/renderer/modules/webgl/.+',
+            '.+/[+]/third_party/blink/renderer/modules/xr/.+',
+            '.+/[+]/third_party/blink/renderer/platform/graphics/gpu/.+',
+            '.+/[+]/tools/clang/scripts/update.py',
+            '.+/[+]/ui/gl/.+',
+        ],
+    ),
+)
diff --git a/infra/config/versioned/milestones/m83/consoles/main.star b/infra/config/versioned/milestones/m83/consoles/main.star
new file mode 100644
index 0000000..7e59621
--- /dev/null
+++ b/infra/config/versioned/milestones/m83/consoles/main.star
@@ -0,0 +1,448 @@
+load('//lib/builders.star', 'builder_name', 'defaults')
+load('../vars.star', 'vars')
+
+defaults.bucket.set(vars.ci_bucket)
+
+luci.console_view(
+    name = vars.main_console_name,
+    header = '//consoles/chromium-header.textpb',
+    repo = 'https://chromium.googlesource.com/chromium/src',
+    refs = [vars.ref],
+    title = vars.main_console_title,
+    entries = [
+        luci.console_view_entry(
+            builder = builder_name('Win x64 Builder'),
+            category = 'chromium.win|release|builder',
+            short_name = '64',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Win 7 Tests x64 (1)'),
+            category = 'chromium.win|release|tester',
+            short_name = '64',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Win10 Tests x64'),
+            category = 'chromium.win|release|tester',
+            short_name = 'w10',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Win Builder (dbg)'),
+            category = 'chromium.win|debug|builder',
+            short_name = '32',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Win7 Tests (dbg)(1)'),
+            category = 'chromium.win|debug|tester',
+            short_name = '7',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Mac Builder'),
+            category = 'chromium.mac|release',
+            short_name = 'bld',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Mac10.10 Tests'),
+            category = 'chromium.mac|release',
+            short_name = '10',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Mac10.11 Tests'),
+            category = 'chromium.mac|release',
+            short_name = '11',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Mac10.12 Tests'),
+            category = 'chromium.mac|release',
+            short_name = '12',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Mac10.13 Tests'),
+            category = 'chromium.mac|release',
+            short_name = '13',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Mac10.14 Tests'),
+            category = 'chromium.mac|release',
+            short_name = '14',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('WebKit Mac10.13 (retina)'),
+            category = 'chromium.mac|release',
+            short_name = 'ret',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Mac Builder (dbg)'),
+            category = 'chromium.mac|debug',
+            short_name = 'bld',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Mac10.13 Tests (dbg)'),
+            category = 'chromium.mac|debug',
+            short_name = '13',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('ios-simulator'),
+            category = 'chromium.mac|ios|default',
+            short_name = 'sim',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('ios-simulator-full-configs'),
+            category = 'chromium.mac|ios|default',
+            short_name = 'ful',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Linux Builder'),
+            category = 'chromium.linux|release',
+            short_name = 'bld',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Linux Tests'),
+            category = 'chromium.linux|release',
+            short_name = 'tst',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('linux-ozone-rel'),
+            category = 'chromium.linux|release',
+            short_name = 'ozo',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Linux Builder (dbg)'),
+            category = 'chromium.linux|debug|builder',
+            short_name = '64',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Linux Tests (dbg)(1)'),
+            category = 'chromium.linux|debug|tester',
+            short_name = '64',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Cast Linux'),
+            category = 'chromium.linux|cast',
+            short_name = 'vid',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Fuchsia ARM64'),
+            category = 'chromium.linux|fuchsia|a64',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('fuchsia-arm64-cast'),
+            category = 'chromium.linux|fuchsia|cast',
+            short_name = 'a64',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('fuchsia-x64-cast'),
+            category = 'chromium.linux|fuchsia|cast',
+            short_name = 'x64',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Fuchsia x64'),
+            category = 'chromium.linux|fuchsia|x64',
+            short_name = 'rel',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('linux-chromeos-rel'),
+            category = 'chromium.chromiumos|default',
+            short_name = 'rel',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('linux-chromeos-dbg'),
+            category = 'chromium.chromiumos|default',
+            short_name = 'dbg',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('chromeos-amd64-generic-dbg'),
+            category = 'chromium.chromiumos|simple|debug|x64',
+            short_name = 'dbg',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('chromeos-amd64-generic-rel'),
+            category = 'chromium.chromiumos|simple|release|x64',
+            short_name = 'rel',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('chromeos-arm-generic-rel'),
+            category = 'chromium.chromiumos|simple|release',
+            short_name = 'arm',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('chromeos-kevin-rel'),
+            category = 'chromium.chromiumos|simple|release',
+            short_name = 'kvn',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('android-cronet-arm-dbg'),
+            category = 'chromium.android|cronet|arm',
+            short_name = 'dbg',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('android-cronet-arm-rel'),
+            category = 'chromium.android|cronet|arm',
+            short_name = 'rel',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('android-cronet-kitkat-arm-rel'),
+            category = 'chromium.android|cronet|test',
+            short_name = 'k',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('android-cronet-lollipop-arm-rel'),
+            category = 'chromium.android|cronet|test',
+            short_name = 'l',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Android arm Builder (dbg)'),
+            category = 'chromium.android|builder|arm',
+            short_name = '32',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Android arm64 Builder (dbg)'),
+            category = 'chromium.android|builder|arm',
+            short_name = '64',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Android x86 Builder (dbg)'),
+            category = 'chromium.android|builder|x86',
+            short_name = '32',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Android x64 Builder (dbg)'),
+            category = 'chromium.android|builder|x86',
+            short_name = '64',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Marshmallow 64 bit Tester'),
+            category = 'chromium.android|tester|phone',
+            short_name = 'M',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Nougat Phone Tester'),
+            category = 'chromium.android|tester|phone',
+            short_name = 'N',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Oreo Phone Tester'),
+            category = 'chromium.android|tester|phone',
+            short_name = 'O',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('android-pie-arm64-dbg'),
+            category = 'chromium.android|tester|phone',
+            short_name = 'P',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Android WebView M (dbg)'),
+            category = 'chromium.android|tester|webview',
+            short_name = 'M',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Android WebView N (dbg)'),
+            category = 'chromium.android|tester|webview',
+            short_name = 'N',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Android WebView O (dbg)'),
+            category = 'chromium.android|tester|webview',
+            short_name = 'O',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Android WebView P (dbg)'),
+            category = 'chromium.android|tester|webview',
+            short_name = 'P',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('android-kitkat-arm-rel'),
+            category = 'chromium.android|on_cq',
+            short_name = 'K',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('android-marshmallow-arm64-rel'),
+            category = 'chromium.android|on_cq',
+            short_name = 'M',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Cast Android (dbg)'),
+            category = 'chromium.android|on_cq',
+            short_name = 'cst',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('android-pie-arm64-rel'),
+            category = 'chromium.android|on_cq',
+            short_name = 'P',
+        ),
+        luci.console_view_entry(
+            builder = 'chrome:ci/linux-chromeos-chrome',
+            category = 'chrome',
+            short_name = 'cro',
+        ),
+        luci.console_view_entry(
+            builder = 'chrome:ci/linux-chrome',
+            category = 'chrome',
+            short_name = 'lnx',
+        ),
+        luci.console_view_entry(
+            builder = 'chrome:ci/mac-chrome',
+            category = 'chrome',
+            short_name = 'mac',
+        ),
+        luci.console_view_entry(
+            builder = 'chrome:ci/win-chrome',
+            category = 'chrome',
+            short_name = 'win',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Linux TSan Builder'),
+            category = 'chromium.memory|linux|TSan v2',
+            short_name = 'bld',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Linux TSan Tests'),
+            category = 'chromium.memory|linux|TSan v2',
+            short_name = 'tst',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Linux ASan LSan Builder'),
+            category = 'chromium.memory|linux|asan lsan',
+            short_name = 'bld',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Linux ASan LSan Tests (1)'),
+            category = 'chromium.memory|linux|asan lsan',
+            short_name = 'tst',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Linux ASan Tests (sandboxed)'),
+            category = 'chromium.memory|linux|asan lsan',
+            short_name = 'sbx',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Dawn Linux x64 DEPS Builder'),
+            category = 'chromium.dawn|DEPS|Linux|Builder',
+            short_name = 'x64',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Dawn Linux x64 DEPS Release (Intel HD 630)'),
+            category = 'chromium.dawn|DEPS|Linux|Intel',
+            short_name = 'x64',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Dawn Linux x64 DEPS Release (NVIDIA)'),
+            category = 'chromium.dawn|DEPS|Linux|Nvidia',
+            short_name = 'x64',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Dawn Mac x64 DEPS Builder'),
+            category = 'chromium.dawn|DEPS|Mac|Builder',
+            short_name = 'x64',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Dawn Mac x64 DEPS Release (AMD)'),
+            category = 'chromium.dawn|DEPS|Mac|AMD',
+            short_name = 'x64',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Dawn Mac x64 DEPS Release (Intel)'),
+            category = 'chromium.dawn|DEPS|Mac|Intel',
+            short_name = 'x64',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Dawn Win10 x86 DEPS Builder'),
+            category = 'chromium.dawn|DEPS|Windows|Builder',
+            short_name = 'x86',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Dawn Win10 x64 DEPS Builder'),
+            category = 'chromium.dawn|DEPS|Windows|Builder',
+            short_name = 'x64',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Dawn Win10 x86 DEPS Release (Intel HD 630)'),
+            category = 'chromium.dawn|DEPS|Windows|Intel',
+            short_name = 'x86',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Dawn Win10 x64 DEPS Release (Intel HD 630)'),
+            category = 'chromium.dawn|DEPS|Windows|Intel',
+            short_name = 'x64',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Dawn Win10 x86 DEPS Release (NVIDIA)'),
+            category = 'chromium.dawn|DEPS|Windows|Nvidia',
+            short_name = 'x86',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Dawn Win10 x64 DEPS Release (NVIDIA)'),
+            category = 'chromium.dawn|DEPS|Windows|Nvidia',
+            short_name = 'x64',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('GPU Win x64 Builder'),
+            category = 'chromium.gpu|Windows',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Win10 x64 Release (NVIDIA)'),
+            category = 'chromium.gpu|Windows',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('GPU Mac Builder'),
+            category = 'chromium.gpu|Mac',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Mac Release (Intel)'),
+            category = 'chromium.gpu|Mac',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Mac Retina Release (AMD)'),
+            category = 'chromium.gpu|Mac',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('GPU Linux Builder'),
+            category = 'chromium.gpu|Linux',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Linux Release (NVIDIA)'),
+            category = 'chromium.gpu|Linux',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Android Release (Nexus 5X)'),
+            category = 'chromium.gpu|Android',
+        ),
+        # TODO(gbeaty): FYI builders should not be mirrors for try builders on
+        # the CQ, but things are what they are
+        luci.console_view_entry(
+            builder = builder_name('ios-simulator-cronet'),
+            category = 'chromium.fyi|cronet',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('mac-osxbeta-rel'),
+            category = 'chromium.fyi|mac',
+            short_name = 'beta',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('chromeos-kevin-rel-hw-tests'),
+            category = 'chromium.fyi|chromos',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('VR Linux'),
+            category = 'chromium.fyi|linux',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Linux Ozone Tester (Headless)'),
+            category = 'chromium.fyi|linux',
+            short_name = 'loh',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Linux Ozone Tester (Wayland)'),
+            category = 'chromium.fyi|linux',
+            short_name = 'low',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Linux Ozone Tester (X11)'),
+            category = 'chromium.fyi|linux',
+            short_name = 'lox',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Win10 Tests x64 1803'),
+            category = 'chromium.fyi|win10|1803',
+        ),
+    ],
+)
diff --git a/infra/config/versioned/milestones/m83/vars.star b/infra/config/versioned/milestones/m83/vars.star
new file mode 100644
index 0000000..2223c2a
--- /dev/null
+++ b/infra/config/versioned/milestones/m83/vars.star
@@ -0,0 +1,10 @@
+vars = struct(
+    ref = 'refs/branch-heads/4103',
+    ci_bucket = 'ci-m83',
+    ci_poller = 'm83-gitiles-trigger',
+    try_bucket = 'try-m83',
+    cq_group = 'cq-m83',
+    cq_ref_regexp = 'refs/branch-heads/4103',
+    main_console_name = 'main-m83',
+    main_console_title = 'Chromium M83 Console',
+)
diff --git a/infra/config/versioned/trunk/buckets/ci.star b/infra/config/versioned/trunk/buckets/ci.star
index 10d9079..1e58e421 100644
--- a/infra/config/versioned/trunk/buckets/ci.star
+++ b/infra/config/versioned/trunk/buckets/ci.star
@@ -260,6 +260,16 @@
 )
 
 
+ci.fyi_ios_builder(
+    name = 'ios-simulator-cronet',
+    executable = 'recipe:chromium',
+    notifies = ['cronet'],
+    properties = {
+        'xcode_build_version': '11c29',
+    },
+)
+
+
 ci.fyi_windows_builder(
     name = 'Win10 Tests x64 1803',
     goma_backend = None,
@@ -435,6 +445,11 @@
     },
 )
 
+ci.mac_ios_builder(
+    name = 'ios-simulator-full-configs',
+    executable = 'recipe:chromium',
+)
+
 
 ci.memory_builder(
     name = 'Linux ASan LSan Builder',
@@ -451,6 +466,15 @@
     triggered_by = [builder_name('Linux ASan LSan Builder')],
 )
 
+ci.memory_builder(
+    name = 'Linux TSan Builder',
+)
+
+ci.memory_builder(
+    name = 'Linux TSan Tests',
+    triggered_by = [builder_name('Linux TSan Builder')],
+)
+
 
 ci.win_builder(
     name = 'Win7 Tests (dbg)(1)',
diff --git a/infra/config/versioned/trunk/consoles/main.star b/infra/config/versioned/trunk/consoles/main.star
index 47b4b69..6b417270 100644
--- a/infra/config/versioned/trunk/consoles/main.star
+++ b/infra/config/versioned/trunk/consoles/main.star
@@ -61,26 +61,11 @@
             short_name = '64',
         ),
         luci.console_view_entry(
-            builder = builder_name('Win Builder'),
-            category = 'chromium.win|release|builder',
-            short_name = '32',
-        ),
-        luci.console_view_entry(
             builder = builder_name('Win x64 Builder'),
             category = 'chromium.win|release|builder',
             short_name = '64',
         ),
         luci.console_view_entry(
-            builder = builder_name('Win7 (32) Tests'),
-            category = 'chromium.win|release|tester',
-            short_name = '32',
-        ),
-        luci.console_view_entry(
-            builder = builder_name('Win7 Tests (1)'),
-            category = 'chromium.win|release|tester',
-            short_name = '32',
-        ),
-        luci.console_view_entry(
             builder = builder_name('Win 7 Tests x64 (1)'),
             category = 'chromium.win|release|tester',
             short_name = '64',
@@ -91,11 +76,6 @@
             short_name = 'w10',
         ),
         luci.console_view_entry(
-            builder = builder_name('Win x64 Builder (dbg)'),
-            category = 'chromium.win|debug|builder',
-            short_name = '64',
-        ),
-        luci.console_view_entry(
             builder = builder_name('Win Builder (dbg)'),
             category = 'chromium.win|debug|builder',
             short_name = '32',
@@ -106,21 +86,6 @@
             short_name = '7',
         ),
         luci.console_view_entry(
-            builder = builder_name('Win10 Tests x64 (dbg)'),
-            category = 'chromium.win|debug|tester',
-            short_name = '10',
-        ),
-        luci.console_view_entry(
-            builder = builder_name('Windows deterministic'),
-            category = 'chromium.win|misc',
-            short_name = 'det',
-        ),
-        luci.console_view_entry(
-            builder = builder_name('WebKit Win10'),
-            category = 'chromium.win|misc',
-            short_name = 'wbk',
-        ),
-        luci.console_view_entry(
             builder = builder_name('Mac Builder'),
             category = 'chromium.mac|release',
             short_name = 'bld',
@@ -166,11 +131,6 @@
             short_name = '13',
         ),
         luci.console_view_entry(
-            builder = builder_name('ios-device'),
-            category = 'chromium.mac|ios|default',
-            short_name = 'dev',
-        ),
-        luci.console_view_entry(
             builder = builder_name('ios-simulator'),
             category = 'chromium.mac|ios|default',
             short_name = 'sim',
@@ -181,11 +141,6 @@
             short_name = 'ful',
         ),
         luci.console_view_entry(
-            builder = builder_name('ios-simulator-noncq'),
-            category = 'chromium.mac|ios|default',
-            short_name = 'non',
-        ),
-        luci.console_view_entry(
             builder = builder_name('Linux Builder'),
             category = 'chromium.linux|release',
             short_name = 'bld',
@@ -196,46 +151,16 @@
             short_name = 'tst',
         ),
         luci.console_view_entry(
-            builder = builder_name('Network Service Linux'),
-            category = 'chromium.linux|release',
-            short_name = 'nsl',
-        ),
-        luci.console_view_entry(
-            builder = builder_name('linux-gcc-rel'),
-            category = 'chromium.linux|release',
-            short_name = 'gcc',
-        ),
-        luci.console_view_entry(
-            builder = builder_name('Deterministic Linux'),
-            category = 'chromium.linux|release',
-            short_name = 'det',
-        ),
-        luci.console_view_entry(
             builder = builder_name('linux-ozone-rel'),
             category = 'chromium.linux|release',
             short_name = 'ozo',
         ),
         luci.console_view_entry(
-            builder = builder_name('linux-trusty-rel'),
-            category = 'chromium.linux|release',
-            short_name = 'tru',
-        ),
-        luci.console_view_entry(
-            builder = builder_name('Linux Builder (dbg)(32)'),
-            category = 'chromium.linux|debug|builder',
-            short_name = '32',
-        ),
-        luci.console_view_entry(
             builder = builder_name('Linux Builder (dbg)'),
             category = 'chromium.linux|debug|builder',
             short_name = '64',
         ),
         luci.console_view_entry(
-            builder = builder_name('Deterministic Linux (dbg)'),
-            category = 'chromium.linux|debug|builder',
-            short_name = 'det',
-        ),
-        luci.console_view_entry(
             builder = builder_name('Linux Tests (dbg)(1)'),
             category = 'chromium.linux|debug|tester',
             short_name = '64',
@@ -246,11 +171,6 @@
             short_name = 'vid',
         ),
         luci.console_view_entry(
-            builder = builder_name('Cast Audio Linux'),
-            category = 'chromium.linux|cast',
-            short_name = 'aud',
-        ),
-        luci.console_view_entry(
             builder = builder_name('Fuchsia ARM64'),
             category = 'chromium.linux|fuchsia|a64',
         ),
@@ -265,21 +185,11 @@
             short_name = 'x64',
         ),
         luci.console_view_entry(
-            builder = builder_name('Deterministic Fuchsia (dbg)'),
-            category = 'chromium.linux|fuchsia|x64',
-            short_name = 'det',
-        ),
-        luci.console_view_entry(
             builder = builder_name('Fuchsia x64'),
             category = 'chromium.linux|fuchsia|x64',
             short_name = 'rel',
         ),
         luci.console_view_entry(
-            builder = builder_name('Linux ChromiumOS Full'),
-            category = 'chromium.chromiumos|default',
-            short_name = 'ful',
-        ),
-        luci.console_view_entry(
             builder = builder_name('linux-chromeos-rel'),
             category = 'chromium.chromiumos|default',
             short_name = 'rel',
@@ -290,16 +200,6 @@
             short_name = 'dbg',
         ),
         luci.console_view_entry(
-            builder = builder_name('chromeos-amd64-generic-asan-rel'),
-            category = 'chromium.chromiumos|simple|release|x64',
-            short_name = 'asn',
-        ),
-        luci.console_view_entry(
-            builder = builder_name('chromeos-amd64-generic-cfi-thin-lto-rel'),
-            category = 'chromium.chromiumos|simple|release|x64',
-            short_name = 'cfi',
-        ),
-        luci.console_view_entry(
             builder = builder_name('chromeos-amd64-generic-dbg'),
             category = 'chromium.chromiumos|simple|debug|x64',
             short_name = 'dbg',
@@ -310,11 +210,6 @@
             short_name = 'rel',
         ),
         luci.console_view_entry(
-            builder = builder_name('chromeos-arm-generic-dbg'),
-            category = 'chromium.chromiumos|simple|debug',
-            short_name = 'arm',
-        ),
-        luci.console_view_entry(
             builder = builder_name('chromeos-arm-generic-rel'),
             category = 'chromium.chromiumos|simple|release',
             short_name = 'arm',
@@ -475,21 +370,6 @@
             short_name = 'win',
         ),
         luci.console_view_entry(
-            builder = builder_name('win-asan'),
-            category = 'chromium.memory|win',
-            short_name = 'asn',
-        ),
-        luci.console_view_entry(
-            builder = builder_name('Mac ASan 64 Builder'),
-            category = 'chromium.memory|mac',
-            short_name = 'bld',
-        ),
-        luci.console_view_entry(
-            builder = builder_name('Mac ASan 64 Tests (1)'),
-            category = 'chromium.memory|mac',
-            short_name = 'tst',
-        ),
-        luci.console_view_entry(
             builder = builder_name('Linux TSan Builder'),
             category = 'chromium.memory|linux|TSan v2',
             short_name = 'bld',
@@ -515,61 +395,6 @@
             short_name = 'sbx',
         ),
         luci.console_view_entry(
-            builder = builder_name('Linux MSan Builder'),
-            category = 'chromium.memory|linux|msan',
-            short_name = 'bld',
-        ),
-        luci.console_view_entry(
-            builder = builder_name('Linux MSan Tests'),
-            category = 'chromium.memory|linux|msan',
-            short_name = 'tst',
-        ),
-        luci.console_view_entry(
-            builder = builder_name('WebKit Linux ASAN'),
-            category = 'chromium.memory|linux|webkit',
-            short_name = 'asn',
-        ),
-        luci.console_view_entry(
-            builder = builder_name('WebKit Linux MSAN'),
-            category = 'chromium.memory|linux|webkit',
-            short_name = 'msn',
-        ),
-        luci.console_view_entry(
-            builder = builder_name('WebKit Linux Leak'),
-            category = 'chromium.memory|linux|webkit',
-            short_name = 'lk',
-        ),
-        luci.console_view_entry(
-            builder = builder_name('Linux Chromium OS ASan LSan Builder'),
-            category = 'chromium.memory|cros|asan',
-            short_name = 'bld',
-        ),
-        luci.console_view_entry(
-            builder = builder_name('Linux Chromium OS ASan LSan Tests (1)'),
-            category = 'chromium.memory|cros|asan',
-            short_name = 'tst',
-        ),
-        luci.console_view_entry(
-            builder = builder_name('Linux ChromiumOS MSan Builder'),
-            category = 'chromium.memory|cros|msan',
-            short_name = 'bld',
-        ),
-        luci.console_view_entry(
-            builder = builder_name('Linux ChromiumOS MSan Tests'),
-            category = 'chromium.memory|cros|msan',
-            short_name = 'tst',
-        ),
-        luci.console_view_entry(
-            builder = builder_name('android-asan'),
-            category = 'chromium.memory|android',
-            short_name = 'asn',
-        ),
-        luci.console_view_entry(
-            builder = builder_name('Linux CFI'),
-            category = 'chromium.memory|cfi',
-            short_name = 'lnx',
-        ),
-        luci.console_view_entry(
             builder = builder_name('Dawn Linux x64 DEPS Builder'),
             category = 'chromium.dawn|DEPS|Linux|Builder',
             short_name = 'x64',
@@ -630,6 +455,14 @@
             short_name = 'x64',
         ),
         luci.console_view_entry(
+            builder = builder_name('GPU Win x64 Builder'),
+            category = 'chromium.gpu|Windows',
+        ),
+        luci.console_view_entry(
+            builder = builder_name('Win10 x64 Release (NVIDIA)'),
+            category = 'chromium.gpu|Windows',
+        ),
+        luci.console_view_entry(
             builder = builder_name('GPU Mac Builder'),
             category = 'chromium.gpu|Mac',
         ),
@@ -673,6 +506,11 @@
             category = 'chromium.fyi|linux',
         ),
         luci.console_view_entry(
+            builder = builder_name('Linux Ozone Tester (Headless)'),
+            category = 'chromium.fyi|linux',
+            short_name = 'loh',
+        ),
+        luci.console_view_entry(
             builder = builder_name('Linux Ozone Tester (Wayland)'),
             category = 'chromium.fyi|linux',
             short_name = 'low',
@@ -682,5 +520,9 @@
             category = 'chromium.fyi|linux',
             short_name = 'lox',
         ),
+        luci.console_view_entry(
+            builder = builder_name('Win10 Tests x64 1803'),
+            category = 'chromium.fyi|win10|1803',
+        ),
     ],
 )
diff --git a/ios/build/bots/scripts/xcodebuild_runner.py b/ios/build/bots/scripts/xcodebuild_runner.py
index 19c2a52..ae706815 100644
--- a/ios/build/bots/scripts/xcodebuild_runner.py
+++ b/ios/build/bots/scripts/xcodebuild_runner.py
@@ -417,7 +417,7 @@
         # 'aborted tests' in logs is an array of strings, each string defined
         # as "{TestCase}/{testMethod}"
         for test in self.logs['aborted tests']:
-          output.mark_aborted(test)
+          output.mark_timeout(test)
 
         for test in attempt_results['passed']:
           output.mark_passed(test)
diff --git a/ios/chrome/browser/browser_state/BUILD.gn b/ios/chrome/browser/browser_state/BUILD.gn
index 9cba7401..885ec6c 100644
--- a/ios/chrome/browser/browser_state/BUILD.gn
+++ b/ios/chrome/browser/browser_state/BUILD.gn
@@ -94,6 +94,7 @@
     "//ios/chrome/browser/browsing_data",
     "//ios/chrome/browser/content_settings",
     "//ios/chrome/browser/crash_report/breadcrumbs",
+    "//ios/chrome/browser/credential_provider",
     "//ios/chrome/browser/device_sharing",
     "//ios/chrome/browser/dom_distiller",
     "//ios/chrome/browser/download",
diff --git a/ios/chrome/browser/browser_state/browser_state_keyed_service_factories.mm b/ios/chrome/browser/browser_state/browser_state_keyed_service_factories.mm
index a1dda3c..d637525 100644
--- a/ios/chrome/browser/browser_state/browser_state_keyed_service_factories.mm
+++ b/ios/chrome/browser/browser_state/browser_state_keyed_service_factories.mm
@@ -15,6 +15,7 @@
 #include "ios/chrome/browser/content_settings/cookie_settings_factory.h"
 #include "ios/chrome/browser/crash_report/breadcrumbs/breadcrumb_manager_keyed_service_factory.h"
 #include "ios/chrome/browser/crash_report/breadcrumbs/breadcrumb_persistent_storage_keyed_service_factory.h"
+#include "ios/chrome/browser/credential_provider/credential_provider_service_factory.h"
 #import "ios/chrome/browser/device_sharing/device_sharing_manager_factory.h"
 #include "ios/chrome/browser/dom_distiller/dom_distiller_service_factory.h"
 #include "ios/chrome/browser/download/browser_download_service_factory.h"
@@ -101,6 +102,7 @@
   BrowserDownloadServiceFactory::GetInstance();
   BrowsingDataRemoverFactory::GetInstance();
   ConsentAuditorFactory::GetInstance();
+  CredentialProviderServiceFactory::GetInstance();
   DeviceSharingManagerFactory::GetInstance();
   GoogleLogoServiceFactory::GetInstance();
   IdentityManagerFactory::GetInstance();
diff --git a/ios/chrome/browser/browser_state/chrome_browser_state_manager_impl.cc b/ios/chrome/browser/browser_state/chrome_browser_state_manager_impl.cc
index 8362089..dde7cf2 100644
--- a/ios/chrome/browser/browser_state/chrome_browser_state_manager_impl.cc
+++ b/ios/chrome/browser/browser_state/chrome_browser_state_manager_impl.cc
@@ -28,6 +28,8 @@
 #include "ios/chrome/browser/browser_state_metrics/browser_state_metrics.h"
 #include "ios/chrome/browser/chrome_constants.h"
 #include "ios/chrome/browser/chrome_paths.h"
+#include "ios/chrome/browser/credential_provider/credential_provider_service_factory.h"
+#include "ios/chrome/browser/credential_provider/credential_provider_support.h"
 #include "ios/chrome/browser/pref_names.h"
 #include "ios/chrome/browser/signin/account_consistency_service_factory.h"
 #include "ios/chrome/browser/signin/account_reconcilor_factory.h"
@@ -210,6 +212,10 @@
   // Initialization needs to happen after the browser context is available
   // because UnifiedConsentService's dependencies needs the URL context getter.
   UnifiedConsentServiceFactory::GetForBrowserState(browser_state);
+
+  if (IsCredentialProviderExtensionSupported()) {
+    CredentialProviderServiceFactory::GetForBrowserState(browser_state);
+  }
 }
 
 void ChromeBrowserStateManagerImpl::AddBrowserStateToCache(
diff --git a/ios/chrome/browser/credential_provider/BUILD.gn b/ios/chrome/browser/credential_provider/BUILD.gn
index 911e2164..389380f 100644
--- a/ios/chrome/browser/credential_provider/BUILD.gn
+++ b/ios/chrome/browser/credential_provider/BUILD.gn
@@ -9,12 +9,21 @@
 source_set("credential_provider") {
   configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
+    "credential_provider_service.cc",
+    "credential_provider_service.h",
+    "credential_provider_service_factory.cc",
+    "credential_provider_service_factory.h",
     "credential_provider_support.cc",
     "credential_provider_support.h",
   ]
   deps = [
     ":buildflags",
     "//base",
+    "//components/keyed_service/core",
+    "//components/keyed_service/ios",
+    "//components/password_manager/core/browser",
+    "//ios/chrome/browser/browser_state",
+    "//ios/chrome/browser/passwords",
   ]
 }
 
@@ -29,10 +38,11 @@
 source_set("unit_tests") {
   configs += [ "//build/config/compiler:enable_arc" ]
   testonly = true
-  sources = []
+  sources = [ "credential_provider_service_unittest.cc" ]
   deps = [
     ":credential_provider",
     "//base/test:test_support",
+    "//components/password_manager/core/browser:test_support",
     "//testing/gtest",
   ]
 }
diff --git a/ios/chrome/browser/credential_provider/credential_provider_service.cc b/ios/chrome/browser/credential_provider/credential_provider_service.cc
new file mode 100644
index 0000000..8f2e809
--- /dev/null
+++ b/ios/chrome/browser/credential_provider/credential_provider_service.cc
@@ -0,0 +1,18 @@
+// Copyright 2020 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 "ios/chrome/browser/credential_provider/credential_provider_service.h"
+
+#include "base/logging.h"
+#include "base/scoped_observer.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "build/build_config.h"
+
+CredentialProviderService::CredentialProviderService(
+    scoped_refptr<password_manager::PasswordStore> password_store)
+    : password_store_(password_store) {}
+
+CredentialProviderService::~CredentialProviderService() {}
+
+void CredentialProviderService::Shutdown() {}
diff --git a/ios/chrome/browser/credential_provider/credential_provider_service.h b/ios/chrome/browser/credential_provider/credential_provider_service.h
new file mode 100644
index 0000000..1db4448b
--- /dev/null
+++ b/ios/chrome/browser/credential_provider/credential_provider_service.h
@@ -0,0 +1,33 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_CREDENTIAL_PROVIDER_CREDENTIAL_PROVIDER_SERVICE_H_
+#define IOS_CHROME_BROWSER_CREDENTIAL_PROVIDER_CREDENTIAL_PROVIDER_SERVICE_H_
+
+#include "base/memory/ref_counted.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "components/password_manager/core/browser/password_store.h"
+
+// A browser-context keyed service that is used to keep the Credential Provider
+// Extension data up to date.
+class CredentialProviderService : public KeyedService {
+ public:
+  // Initializes the service.
+  CredentialProviderService(
+      scoped_refptr<password_manager::PasswordStore> password_store);
+  ~CredentialProviderService() override;
+
+  // KeyedService:
+  void Shutdown() override;
+
+ private:
+  friend class CredentialProviderServiceTest;
+
+  // The interface for getting and manipulating a user's saved passwords.
+  scoped_refptr<password_manager::PasswordStore> password_store_;
+
+  DISALLOW_COPY_AND_ASSIGN(CredentialProviderService);
+};
+
+#endif  // IOS_CHROME_BROWSER_CREDENTIAL_PROVIDER_CREDENTIAL_PROVIDER_SERVICE_H_
diff --git a/ios/chrome/browser/credential_provider/credential_provider_service_factory.cc b/ios/chrome/browser/credential_provider/credential_provider_service_factory.cc
new file mode 100644
index 0000000..e8058e6c
--- /dev/null
+++ b/ios/chrome/browser/credential_provider/credential_provider_service_factory.cc
@@ -0,0 +1,46 @@
+// Copyright 2020 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 "ios/chrome/browser/credential_provider/credential_provider_service_factory.h"
+
+#include "components/keyed_service/core/service_access_type.h"
+#include "components/keyed_service/ios/browser_state_dependency_manager.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#include "ios/chrome/browser/credential_provider/credential_provider_service.h"
+#include "ios/chrome/browser/passwords/ios_chrome_password_store_factory.h"
+
+// static
+CredentialProviderService* CredentialProviderServiceFactory::GetForBrowserState(
+    ChromeBrowserState* browser_state) {
+  return static_cast<CredentialProviderService*>(
+      GetInstance()->GetServiceForBrowserState(browser_state, true));
+}
+
+// static
+CredentialProviderServiceFactory*
+CredentialProviderServiceFactory::GetInstance() {
+  static base::NoDestructor<CredentialProviderServiceFactory> instance;
+  return instance.get();
+}
+
+CredentialProviderServiceFactory::CredentialProviderServiceFactory()
+    : BrowserStateKeyedServiceFactory(
+          "CredentialProviderService",
+          BrowserStateDependencyManager::GetInstance()) {
+  DependsOn(IOSChromePasswordStoreFactory::GetInstance());
+}
+
+CredentialProviderServiceFactory::~CredentialProviderServiceFactory() = default;
+
+std::unique_ptr<KeyedService>
+CredentialProviderServiceFactory::BuildServiceInstanceFor(
+    web::BrowserState* context) const {
+  ChromeBrowserState* browser_state =
+      ChromeBrowserState::FromBrowserState(context);
+  scoped_refptr<password_manager::PasswordStore> password_store =
+      IOSChromePasswordStoreFactory::GetForBrowserState(
+          browser_state, ServiceAccessType::IMPLICIT_ACCESS);
+
+  return std::make_unique<CredentialProviderService>(password_store);
+}
diff --git a/ios/chrome/browser/credential_provider/credential_provider_service_factory.h b/ios/chrome/browser/credential_provider/credential_provider_service_factory.h
new file mode 100644
index 0000000..64538fa92
--- /dev/null
+++ b/ios/chrome/browser/credential_provider/credential_provider_service_factory.h
@@ -0,0 +1,38 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_CREDENTIAL_PROVIDER_CREDENTIAL_PROVIDER_SERVICE_FACTORY_H_
+#define IOS_CHROME_BROWSER_CREDENTIAL_PROVIDER_CREDENTIAL_PROVIDER_SERVICE_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/no_destructor.h"
+#include "components/keyed_service/ios/browser_state_keyed_service_factory.h"
+
+class ChromeBrowserState;
+class CredentialProviderService;
+
+// Singleton that owns all CredentialProviderServices and associates them with
+// ChromeBrowserState.
+class CredentialProviderServiceFactory
+    : public BrowserStateKeyedServiceFactory {
+ public:
+  static CredentialProviderService* GetForBrowserState(
+      ChromeBrowserState* browser_state);
+
+  static CredentialProviderServiceFactory* GetInstance();
+
+ private:
+  friend class base::NoDestructor<CredentialProviderServiceFactory>;
+
+  CredentialProviderServiceFactory();
+  ~CredentialProviderServiceFactory() override;
+
+  // BrowserStateKeyedServiceFactory implementation.
+  std::unique_ptr<KeyedService> BuildServiceInstanceFor(
+      web::BrowserState* context) const override;
+
+  DISALLOW_COPY_AND_ASSIGN(CredentialProviderServiceFactory);
+};
+
+#endif  // IOS_CHROME_BROWSER_CREDENTIAL_PROVIDER_CREDENTIAL_PROVIDER_SERVICE_FACTORY_H_
diff --git a/ios/chrome/browser/credential_provider/credential_provider_service_unittest.cc b/ios/chrome/browser/credential_provider/credential_provider_service_unittest.cc
new file mode 100644
index 0000000..08af58a
--- /dev/null
+++ b/ios/chrome/browser/credential_provider/credential_provider_service_unittest.cc
@@ -0,0 +1,43 @@
+// Copyright 2020 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 "ios/chrome/browser/credential_provider/credential_provider_service.h"
+
+#include "components/password_manager/core/browser/test_password_store.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using password_manager::TestPasswordStore;
+
+class CredentialProviderServiceTest : public testing::Test {
+ public:
+  CredentialProviderServiceTest() {}
+
+  ~CredentialProviderServiceTest() override {
+    if (credential_provider_service_) {
+      credential_provider_service_->Shutdown();
+      password_store_->ShutdownOnUIThread();
+    }
+  }
+
+  void CreateConsentService() {
+    password_store_ = base::MakeRefCounted<TestPasswordStore>();
+    credential_provider_service_ =
+        std::make_unique<CredentialProviderService>(password_store_);
+  }
+
+ protected:
+  std::unique_ptr<CredentialProviderService> credential_provider_service_;
+  scoped_refptr<TestPasswordStore> password_store_;
+
+  DISALLOW_COPY_AND_ASSIGN(CredentialProviderServiceTest);
+};
+
+TEST_F(CredentialProviderServiceTest, Create) {
+  CreateConsentService();
+  EXPECT_TRUE(credential_provider_service_);
+}
+
+}  // namespace
diff --git a/ios/chrome/browser/prerender/DEPS b/ios/chrome/browser/prerender/DEPS
deleted file mode 100644
index f09c933c..0000000
--- a/ios/chrome/browser/prerender/DEPS
+++ /dev/null
@@ -1,6 +0,0 @@
-specific_include_rules = {
-  # TODO(crbug.com/620438): Remove this exception.
-  "^preload_controller\.mm$": [
-    "+ios/web/web_state/ui/crw_web_controller.h",
-  ],
-}
diff --git a/ios/chrome/browser/prerender/preload_controller.mm b/ios/chrome/browser/prerender/preload_controller.mm
index 7156f77..3a1cb94 100644
--- a/ios/chrome/browser/prerender/preload_controller.mm
+++ b/ios/chrome/browser/prerender/preload_controller.mm
@@ -32,7 +32,6 @@
 #include "ios/web/public/web_client.h"
 #import "ios/web/public/web_state.h"
 #import "ios/web/public/web_state_observer_bridge.h"
-#import "ios/web/web_state/ui/crw_web_controller.h"
 #import "net/base/mac/url_conversions.h"
 #include "ui/base/page_transition_types.h"
 
diff --git a/ios/chrome/browser/ui/settings/privacy/privacy_coordinator.mm b/ios/chrome/browser/ui/settings/privacy/privacy_coordinator.mm
index 4d94ff67..cae6a47 100644
--- a/ios/chrome/browser/ui/settings/privacy/privacy_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/privacy/privacy_coordinator.mm
@@ -4,23 +4,28 @@
 
 #import "ios/chrome/browser/ui/settings/privacy/privacy_coordinator.h"
 
+#import "base/mac/foundation_util.h"
 #include "ios/chrome/browser/main/browser.h"
 #import "ios/chrome/browser/ui/commands/browser_commands.h"
 #import "ios/chrome/browser/ui/commands/command_dispatcher.h"
+#import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
 #import "ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_table_view_controller.h"
+#import "ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_ui_delegate.h"
 #import "ios/chrome/browser/ui/settings/privacy/handoff_table_view_controller.h"
 #import "ios/chrome/browser/ui/settings/privacy/privacy_navigation_commands.h"
 #import "ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.h"
+#import "ios/chrome/browser/ui/settings/settings_navigation_controller.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
 @interface PrivacyCoordinator () <
+    ClearBrowsingDataUIDelegate,
     PrivacyNavigationCommands,
     PrivacyTableViewControllerPresentationDelegate>
 
-@property(nonatomic, strong) CommandDispatcher* dispatcher;
+@property(nonatomic, strong) id<ApplicationCommands> handler;
 @property(nonatomic, strong) PrivacyTableViewController* viewController;
 
 @end
@@ -41,7 +46,8 @@
 #pragma mark - ChromeCoordinator
 
 - (void)start {
-  self.dispatcher = self.browser->GetCommandDispatcher();
+  self.handler = HandlerForProtocol(self.browser->GetCommandDispatcher(),
+                                    ApplicationCommands);
   self.viewController =
       [[PrivacyTableViewController alloc] initWithBrowser:self.browser];
 
@@ -59,14 +65,6 @@
   self.viewController = nil;
 }
 
-#pragma mark - PrivacyTableViewControllerPresentationDelegate
-
-- (void)privacyTableViewControllerViewControllerWasRemoved:
-    (PrivacyTableViewController*)controller {
-  DCHECK_EQ(self.viewController, controller);
-  [self.delegate privacyCoordinatorViewControllerWasRemoved:self];
-}
-
 #pragma mark - PrivacyNavigationCommands
 
 - (void)showHandoff {
@@ -83,8 +81,31 @@
       [[ClearBrowsingDataTableViewController alloc]
           initWithBrowser:self.browser];
   viewController.dispatcher = self.viewController.dispatcher;
+  viewController.delegate = self;
   [self.baseNavigationController pushViewController:viewController
                                            animated:YES];
 }
 
+#pragma mark - PrivacyTableViewControllerPresentationDelegate
+
+- (void)privacyTableViewControllerViewControllerWasRemoved:
+    (PrivacyTableViewController*)controller {
+  DCHECK_EQ(self.viewController, controller);
+  [self.delegate privacyCoordinatorViewControllerWasRemoved:self];
+}
+
+#pragma mark - ClearBrowsingDataUIDelegate
+
+- (void)openURL:(const GURL&)URL {
+  OpenNewTabCommand* command = [OpenNewTabCommand commandWithURLFromChrome:URL];
+  [self.handler closeSettingsUIAndOpenURL:command];
+}
+
+- (void)dismissClearBrowsingData {
+  SettingsNavigationController* navigationController =
+      base::mac::ObjCCastStrict<SettingsNavigationController>(
+          self.viewController.navigationController);
+  [navigationController closeSettings];
+}
+
 @end
diff --git a/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.mm b/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.mm
index ab4e34fc..f51f0bd9 100644
--- a/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.mm
@@ -15,10 +15,8 @@
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #include "ios/chrome/browser/browsing_data/browsing_data_features.h"
 #import "ios/chrome/browser/main/browser.h"
-#import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
 #import "ios/chrome/browser/ui/settings/cells/settings_switch_cell.h"
 #import "ios/chrome/browser/ui/settings/cells/settings_switch_item.h"
-#import "ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_ui_delegate.h"
 #import "ios/chrome/browser/ui/settings/privacy/privacy_navigation_commands.h"
 #import "ios/chrome/browser/ui/settings/settings_navigation_controller.h"
 #import "ios/chrome/browser/ui/settings/settings_table_view_controller_constants.h"
@@ -57,8 +55,7 @@
 
 }  // namespace
 
-@interface PrivacyTableViewController () <ClearBrowsingDataUIDelegate,
-                                          PrefObserverDelegate> {
+@interface PrivacyTableViewController () <PrefObserverDelegate> {
   ChromeBrowserState* _browserState;  // weak
 
   // Pref observer to track changes to prefs.
@@ -223,21 +220,6 @@
   [tableView deselectRowAtIndexPath:indexPath animated:YES];
 }
 
-#pragma mark - ClearBrowsingDataUIDelegate
-
-- (void)openURL:(const GURL&)URL {
-  DCHECK(self.dispatcher);
-  OpenNewTabCommand* command = [OpenNewTabCommand commandWithURLFromChrome:URL];
-  [self.dispatcher closeSettingsUIAndOpenURL:command];
-}
-
-- (void)dismissClearBrowsingData {
-  SettingsNavigationController* navigationController =
-      base::mac::ObjCCastStrict<SettingsNavigationController>(
-          self.navigationController);
-  [navigationController closeSettings];
-}
-
 #pragma mark - PrefObserverDelegate
 
 - (void)onPreferenceChanged:(const std::string&)preferenceName {
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index 636dec1c..b7f038b 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -565,10 +565,6 @@
 const base::Feature kDelayCopyNV12Textures{"DelayCopyNV12Textures",
                                            base::FEATURE_ENABLED_BY_DEFAULT};
 
-// Enables H264 HW encode acceleration using Media Foundation for Windows.
-const base::Feature kMediaFoundationH264Encoding{
-    "MediaFoundationH264Encoding", base::FEATURE_ENABLED_BY_DEFAULT};
-
 // Enables MediaFoundation based video capture
 const base::Feature kMediaFoundationVideoCapture{
     "MediaFoundationVideoCapture", base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/media/base/media_switches.h b/media/base/media_switches.h
index dfe622f6..a921e037 100644
--- a/media/base/media_switches.h
+++ b/media/base/media_switches.h
@@ -195,7 +195,6 @@
 
 #if defined(OS_WIN)
 MEDIA_EXPORT extern const base::Feature kDelayCopyNV12Textures;
-MEDIA_EXPORT extern const base::Feature kMediaFoundationH264Encoding;
 MEDIA_EXPORT extern const base::Feature kMediaFoundationVideoCapture;
 MEDIA_EXPORT extern const base::Feature kMediaFoundationVP8Decoding;
 MEDIA_EXPORT extern const base::Feature kDirectShowGetPhotoState;
diff --git a/media/gpu/gpu_video_encode_accelerator_factory.cc b/media/gpu/gpu_video_encode_accelerator_factory.cc
index cf43708..7a52f29 100644
--- a/media/gpu/gpu_video_encode_accelerator_factory.cc
+++ b/media/gpu/gpu_video_encode_accelerator_factory.cc
@@ -100,11 +100,9 @@
   vea_factory_functions.push_back(base::BindRepeating(&CreateVTVEA));
 #endif
 #if defined(OS_WIN)
-  if (base::FeatureList::IsEnabled(kMediaFoundationH264Encoding)) {
-    vea_factory_functions.push_back(base::BindRepeating(
-        &CreateMediaFoundationVEA,
-        gpu_preferences.enable_media_foundation_vea_on_windows7));
-  }
+  vea_factory_functions.push_back(base::BindRepeating(
+      &CreateMediaFoundationVEA,
+      gpu_preferences.enable_media_foundation_vea_on_windows7));
 #endif
   return vea_factory_functions;
 }
diff --git a/media/mojo/mojom/BUILD.gn b/media/mojo/mojom/BUILD.gn
index 10940dc..2e0718bc 100644
--- a/media/mojo/mojom/BUILD.gn
+++ b/media/mojo/mojom/BUILD.gn
@@ -115,7 +115,7 @@
     "audio_decoder_config_mojom_traits_unittest.cc",
     "cdm_key_information_mojom_traits_unittest.cc",
     "video_decoder_config_mojom_traits_unittest.cc",
-    "video_encoder_info_mojom_traits_unittest.cc",
+    "video_encode_accelerator_mojom_traits_unittest.cc",
     "video_frame_mojom_traits_unittest.cc",
   ]
 
diff --git a/media/mojo/mojom/video_encode_accelerator.mojom b/media/mojo/mojom/video_encode_accelerator.mojom
index b1baa0f..2bab244c 100644
--- a/media/mojo/mojom/video_encode_accelerator.mojom
+++ b/media/mojo/mojom/video_encode_accelerator.mojom
@@ -61,6 +61,17 @@
 };
 
 // This defines a mojo transport format for
+// media::VideoEncodeAccelerator::Config::SpatialLayer.
+struct SpatialLayer {
+  int32 width;
+  int32 height;
+  uint32 bitrate_bps;
+  uint32 framerate;
+  uint8 max_qp;
+  uint8 num_of_temporal_layers;
+};
+
+// This defines a mojo transport format for
 // media::VideoEncodeAccelerator::Config.
 struct VideoEncodeAcceleratorConfig {
   // See media::VideoEncodeAccelerator::Config::ContentType
@@ -88,6 +99,7 @@
   StorageType storage_type;
   bool has_storage_type;  // Whether or not config has storage type config
   ContentType content_type;
+  array<SpatialLayer> spatial_layers;
 };
 
 interface VideoEncodeAccelerator {
diff --git a/media/mojo/mojom/video_encode_accelerator.typemap b/media/mojo/mojom/video_encode_accelerator.typemap
index 0f1caea..be2bfda 100644
--- a/media/mojo/mojom/video_encode_accelerator.typemap
+++ b/media/mojo/mojom/video_encode_accelerator.typemap
@@ -31,6 +31,7 @@
   "media.mojom.VideoBitrateAllocation=::media::VideoBitrateAllocation",
   "media.mojom.VideoEncodeAccelerator.Error=::media::VideoEncodeAccelerator::Error",
   "media.mojom.VideoEncodeAcceleratorConfig=::media::VideoEncodeAccelerator::Config",
+  "media.mojom.SpatialLayer=::media::VideoEncodeAccelerator::Config::SpatialLayer",
   "media.mojom.VideoEncodeAcceleratorSupportedProfile=::media::VideoEncodeAccelerator::SupportedProfile",
   "media.mojom.Vp8Metadata=::media::Vp8Metadata",
 ]
diff --git a/media/mojo/mojom/video_encode_accelerator_mojom_traits.cc b/media/mojo/mojom/video_encode_accelerator_mojom_traits.cc
index 1d3b11b..f78682fa 100644
--- a/media/mojo/mojom/video_encode_accelerator_mojom_traits.cc
+++ b/media/mojo/mojom/video_encode_accelerator_mojom_traits.cc
@@ -196,6 +196,20 @@
 }
 
 // static
+bool StructTraits<media::mojom::SpatialLayerDataView,
+                  media::VideoEncodeAccelerator::Config::SpatialLayer>::
+    Read(media::mojom::SpatialLayerDataView input,
+         media::VideoEncodeAccelerator::Config::SpatialLayer* output) {
+  output->width = input.width();
+  output->height = input.height();
+  output->bitrate_bps = input.bitrate_bps();
+  output->framerate = input.framerate();
+  output->max_qp = input.max_qp();
+  output->num_of_temporal_layers = input.num_of_temporal_layers();
+  return true;
+}
+
+// static
 bool StructTraits<media::mojom::VideoEncodeAcceleratorConfigDataView,
                   media::VideoEncodeAccelerator::Config>::
     Read(media::mojom::VideoEncodeAcceleratorConfigDataView input,
@@ -235,10 +249,15 @@
   if (!input.ReadContentType(&content_type))
     return false;
 
+  std::vector<media::VideoEncodeAccelerator::Config::SpatialLayer>
+      spatial_layers;
+  if (!input.ReadSpatialLayers(&spatial_layers))
+    return false;
+
   *output = media::VideoEncodeAccelerator::Config(
       input_format, input_visible_size, output_profile, input.initial_bitrate(),
       initial_framerate, gop_length, h264_output_level, storage_type,
-      content_type);
+      content_type, spatial_layers);
   return true;
 }
 
diff --git a/media/mojo/mojom/video_encode_accelerator_mojom_traits.h b/media/mojo/mojom/video_encode_accelerator_mojom_traits.h
index 6b1a5546..eebc881 100644
--- a/media/mojo/mojom/video_encode_accelerator_mojom_traits.h
+++ b/media/mojo/mojom/video_encode_accelerator_mojom_traits.h
@@ -132,6 +132,43 @@
 };
 
 template <>
+struct StructTraits<media::mojom::SpatialLayerDataView,
+                    media::VideoEncodeAccelerator::Config::SpatialLayer> {
+  static int32_t width(
+      const media::VideoEncodeAccelerator::Config::SpatialLayer& input) {
+    return input.width;
+  }
+
+  static int32_t height(
+      const media::VideoEncodeAccelerator::Config::SpatialLayer& input) {
+    return input.height;
+  }
+
+  static uint32_t bitrate_bps(
+      const media::VideoEncodeAccelerator::Config::SpatialLayer& input) {
+    return input.bitrate_bps;
+  }
+
+  static uint32_t framerate(
+      const media::VideoEncodeAccelerator::Config::SpatialLayer& input) {
+    return input.framerate;
+  }
+
+  static uint8_t max_qp(
+      const media::VideoEncodeAccelerator::Config::SpatialLayer& input) {
+    return input.max_qp;
+  }
+
+  static uint8_t num_of_temporal_layers(
+      const media::VideoEncodeAccelerator::Config::SpatialLayer& input) {
+    return input.num_of_temporal_layers;
+  }
+
+  static bool Read(media::mojom::SpatialLayerDataView input,
+                   media::VideoEncodeAccelerator::Config::SpatialLayer* output);
+};
+
+template <>
 struct StructTraits<media::mojom::VideoEncodeAcceleratorConfigDataView,
                     media::VideoEncodeAccelerator::Config> {
   static media::VideoPixelFormat input_format(
@@ -200,6 +237,11 @@
     return input.content_type;
   }
 
+  static const std::vector<media::VideoEncodeAccelerator::Config::SpatialLayer>&
+  spatial_layers(const media::VideoEncodeAccelerator::Config& input) {
+    return input.spatial_layers;
+  }
+
   static bool Read(media::mojom::VideoEncodeAcceleratorConfigDataView input,
                    media::VideoEncodeAccelerator::Config* output);
 };
diff --git a/media/mojo/mojom/video_encode_accelerator_mojom_traits_unittest.cc b/media/mojo/mojom/video_encode_accelerator_mojom_traits_unittest.cc
new file mode 100644
index 0000000..7312aa8d
--- /dev/null
+++ b/media/mojo/mojom/video_encode_accelerator_mojom_traits_unittest.cc
@@ -0,0 +1,157 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/mojo/mojom/video_encode_accelerator_mojom_traits.h"
+#include "media/mojo/mojom/video_encoder_info_mojom_traits.h"
+
+#include "media/video/video_encode_accelerator.h"
+#include "media/video/video_encoder_info.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+// These binary operators are implemented here because they are used in this
+// unittest. They cannot be enclosed by anonymous namespace, because they must
+// be visible by gtest in linking.
+bool operator==(const ::media::ScalingSettings& l,
+                const ::media::ScalingSettings& r) {
+  return l.min_qp == r.min_qp && l.max_qp == r.max_qp;
+}
+
+bool operator!=(const ::media::ScalingSettings& l,
+                const ::media::ScalingSettings& r) {
+  return !(l == r);
+}
+
+bool operator==(const ::media::ResolutionBitrateLimit& l,
+                const ::media::ResolutionBitrateLimit& r) {
+  return (l.frame_size == r.frame_size &&
+          l.min_start_bitrate_bps == r.min_start_bitrate_bps &&
+          l.min_bitrate_bps == r.min_bitrate_bps &&
+          l.max_bitrate_bps == r.max_bitrate_bps);
+}
+
+bool operator!=(const ::media::ResolutionBitrateLimit& l,
+                const ::media::ResolutionBitrateLimit& r) {
+  return !(l == r);
+}
+
+bool operator==(const ::media::VideoEncoderInfo& l,
+                const ::media::VideoEncoderInfo& r) {
+  if (l.implementation_name != r.implementation_name)
+    return false;
+  if (l.supports_native_handle != r.supports_native_handle)
+    return false;
+  if (l.has_trusted_rate_controller != r.has_trusted_rate_controller)
+    return false;
+  if (l.is_hardware_accelerated != r.is_hardware_accelerated)
+    return false;
+  if (l.supports_simulcast != r.supports_simulcast)
+    return false;
+  if (l.scaling_settings != r.scaling_settings)
+    return false;
+  for (size_t i = 0; i < ::media::VideoEncoderInfo::kMaxSpatialLayers; ++i) {
+    if (l.fps_allocation[i] != r.fps_allocation[i])
+      return false;
+  }
+  if (l.resolution_bitrate_limits != r.resolution_bitrate_limits)
+    return false;
+  return true;
+}
+
+bool operator==(
+    const ::media::VideoEncodeAccelerator::Config::SpatialLayer& l,
+    const ::media::VideoEncodeAccelerator::Config::SpatialLayer& r) {
+  return l.width == r.width && l.height == r.height &&
+         l.bitrate_bps == r.bitrate_bps && l.framerate == r.framerate &&
+         l.max_qp == r.max_qp && l.num_of_temporal_layers &&
+         r.num_of_temporal_layers;
+}
+
+bool operator==(const ::media::VideoEncodeAccelerator::Config& l,
+                const ::media::VideoEncodeAccelerator::Config& r) {
+  return l.input_format == r.input_format &&
+         l.input_visible_size == r.input_visible_size &&
+         l.output_profile == r.output_profile &&
+         l.initial_bitrate == r.initial_bitrate &&
+         l.initial_framerate == r.initial_framerate &&
+         l.gop_length == r.gop_length &&
+         l.h264_output_level == r.h264_output_level &&
+         l.storage_type == r.storage_type && l.content_type == r.content_type &&
+         l.spatial_layers == r.spatial_layers;
+}
+
+TEST(VideoEncoderInfoStructTraitTest, RoundTrip) {
+  ::media::VideoEncoderInfo input;
+  input.implementation_name = "FakeVideoEncodeAccelerator";
+  // Scaling settings.
+  input.scaling_settings.min_qp = 12;
+  input.scaling_settings.max_qp = 123;
+  // FPS allocation.
+  for (size_t i = 0; i < ::media::VideoEncoderInfo::kMaxSpatialLayers; ++i)
+    input.fps_allocation[i] = {5, 5, 10};
+  // Resolution bitrate limits.
+  input.resolution_bitrate_limits.push_back(::media::ResolutionBitrateLimit(
+      gfx::Size(123, 456), 123456, 123456, 789012));
+  input.resolution_bitrate_limits.push_back(::media::ResolutionBitrateLimit(
+      gfx::Size(789, 1234), 1234567, 1234567, 7890123));
+  // Other bool values.
+  input.supports_native_handle = true;
+  input.has_trusted_rate_controller = true;
+  input.is_hardware_accelerated = true;
+  input.supports_simulcast = true;
+
+  ::media::VideoEncoderInfo output = input;
+  ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::VideoEncoderInfo>(
+      &input, &output));
+  EXPECT_EQ(input, output);
+}
+
+TEST(SpatialLayerStructTraitTest, RoundTrip) {
+  ::media::VideoEncodeAccelerator::Config::SpatialLayer input_spatial_layer;
+  input_spatial_layer.width = 320u;
+  input_spatial_layer.width = 180u;
+  input_spatial_layer.bitrate_bps = 12345678;
+  input_spatial_layer.framerate = 24;
+  input_spatial_layer.max_qp = 30;
+  input_spatial_layer.num_of_temporal_layers = 3;
+  ::media::VideoEncodeAccelerator::Config::SpatialLayer output_spatial_layer{};
+  ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::SpatialLayer>(
+      &input_spatial_layer, &output_spatial_layer));
+  EXPECT_EQ(input_spatial_layer, output_spatial_layer);
+}
+
+TEST(VideoEncodeAcceleratorConfigStructTraitTest, RoundTrip) {
+  std::vector<::media::VideoEncodeAccelerator::Config::SpatialLayer>
+      input_spatial_layers(3);
+  gfx::Size kBaseSize(320, 180);
+  uint32_t kBaseBitrateBps = 123456;
+  uint32_t kBaseFramerate = 24;
+  for (size_t i = 0; i < input_spatial_layers.size(); ++i) {
+    input_spatial_layers[i].width =
+        static_cast<uint32_t>(kBaseSize.width() * (i + 1));
+    input_spatial_layers[i].height =
+        static_cast<uint32_t>(kBaseSize.height() * (i + 1));
+    input_spatial_layers[i].bitrate_bps = kBaseBitrateBps * (i + 1) / 2;
+    input_spatial_layers[i].framerate = kBaseFramerate * 2 / (i + 1);
+    input_spatial_layers[i].max_qp = 30 * (i + 1) / 2;
+    input_spatial_layers[i].num_of_temporal_layers = 3 - i;
+  }
+  ::media::VideoEncodeAccelerator::Config input_config(
+      ::media::PIXEL_FORMAT_NV12, kBaseSize, ::media::VP9PROFILE_PROFILE0,
+      kBaseBitrateBps, kBaseFramerate, base::nullopt, base::nullopt,
+      ::media::VideoEncodeAccelerator::Config::StorageType::kDmabuf,
+      ::media::VideoEncodeAccelerator::Config::ContentType::kCamera,
+      input_spatial_layers);
+  DVLOG(4) << input_config.AsHumanReadableString();
+
+  ::media::VideoEncodeAccelerator::Config output_config{};
+  ASSERT_TRUE(
+      mojo::test::SerializeAndDeserialize<mojom::VideoEncodeAcceleratorConfig>(
+          &input_config, &output_config));
+  DVLOG(4) << output_config.AsHumanReadableString();
+  EXPECT_EQ(input_config, output_config);
+}
+}  // namespace media
diff --git a/media/mojo/mojom/video_encoder_info_mojom_traits_unittest.cc b/media/mojo/mojom/video_encoder_info_mojom_traits_unittest.cc
deleted file mode 100644
index 455ac3b..0000000
--- a/media/mojo/mojom/video_encoder_info_mojom_traits_unittest.cc
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "media/mojo/mojom/video_encoder_info_mojom_traits.h"
-
-#include "media/video/video_encoder_info.h"
-
-#include "mojo/public/cpp/test_support/test_utils.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-// These binary operators are implemented here because they are used in this
-// unittest. They cannot be enclosed by anonymous namespace, because they must
-// be visible by gtest in linking.
-bool operator==(const ::media::ScalingSettings& l,
-                const ::media::ScalingSettings& r) {
-  return l.min_qp == r.min_qp && l.max_qp == r.max_qp;
-}
-
-bool operator!=(const ::media::ScalingSettings& l,
-                const ::media::ScalingSettings& r) {
-  return !(l == r);
-}
-
-bool operator==(const ::media::ResolutionBitrateLimit& l,
-                const ::media::ResolutionBitrateLimit& r) {
-  return (l.frame_size == r.frame_size &&
-          l.min_start_bitrate_bps == r.min_start_bitrate_bps &&
-          l.min_bitrate_bps == r.min_bitrate_bps &&
-          l.max_bitrate_bps == r.max_bitrate_bps);
-}
-
-bool operator!=(const ::media::ResolutionBitrateLimit& l,
-                const ::media::ResolutionBitrateLimit& r) {
-  return !(l == r);
-}
-
-bool operator==(const ::media::VideoEncoderInfo& l,
-                const ::media::VideoEncoderInfo& r) {
-  if (l.implementation_name != r.implementation_name)
-    return false;
-  if (l.supports_native_handle != r.supports_native_handle)
-    return false;
-  if (l.has_trusted_rate_controller != r.has_trusted_rate_controller)
-    return false;
-  if (l.is_hardware_accelerated != r.is_hardware_accelerated)
-    return false;
-  if (l.supports_simulcast != r.supports_simulcast)
-    return false;
-  if (l.scaling_settings != r.scaling_settings)
-    return false;
-  for (size_t i = 0; i < ::media::VideoEncoderInfo::kMaxSpatialLayers; ++i) {
-    if (l.fps_allocation[i] != r.fps_allocation[i])
-      return false;
-  }
-  if (l.resolution_bitrate_limits != r.resolution_bitrate_limits)
-    return false;
-  return true;
-}
-
-TEST(VideoEncoderInfoStructTraitTest, RoundTrip) {
-  ::media::VideoEncoderInfo input;
-  input.implementation_name = "FakeVideoEncodeAccelerator";
-  // Scaling settings.
-  input.scaling_settings.min_qp = 12;
-  input.scaling_settings.max_qp = 123;
-  // FPS allocation.
-  for (size_t i = 0; i < ::media::VideoEncoderInfo::kMaxSpatialLayers; ++i)
-    input.fps_allocation[i] = {5, 5, 10};
-  // Resolution bitrate limits.
-  input.resolution_bitrate_limits.push_back(::media::ResolutionBitrateLimit(
-      gfx::Size(123, 456), 123456, 123456, 789012));
-  input.resolution_bitrate_limits.push_back(::media::ResolutionBitrateLimit(
-      gfx::Size(789, 1234), 1234567, 1234567, 7890123));
-  // Other bool values.
-  input.supports_native_handle = true;
-  input.has_trusted_rate_controller = true;
-  input.is_hardware_accelerated = true;
-  input.supports_simulcast = true;
-
-  ::media::VideoEncoderInfo output = input;
-  ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::VideoEncoderInfo>(
-      &input, &output));
-  EXPECT_EQ(input, output);
-}
-}  // namespace media
diff --git a/media/renderers/default_decoder_factory.cc b/media/renderers/default_decoder_factory.cc
index c3a41f45..f86855c 100644
--- a/media/renderers/default_decoder_factory.cc
+++ b/media/renderers/default_decoder_factory.cc
@@ -121,10 +121,22 @@
 #if defined(OS_FUCHSIA)
   if (gpu_factories) {
     auto* context_provider = gpu_factories->GetMediaContextProvider();
-    DCHECK(context_provider);
-    video_decoders->push_back(
-        CreateFuchsiaVideoDecoder(gpu_factories->SharedImageInterface(),
-                                  context_provider->ContextSupport()));
+
+    // GetMediaContextProvider() may return nullptr when the context was lost
+    // (e.g. after GPU process crash). To handle this case RenderThreadImpl
+    // creates a new GpuVideoAcceleratorFactories with a new ContextProvider
+    // instance, but there is no way to get it here. For now just don't add
+    // FuchsiaVideoDecoder in that scenario.
+    //
+    // TODO(crbug.com/580386): Handle context loss properly.
+    if (context_provider) {
+      video_decoders->push_back(
+          CreateFuchsiaVideoDecoder(gpu_factories->SharedImageInterface(),
+                                    context_provider->ContextSupport()));
+    } else {
+      DLOG(ERROR)
+          << "Can't created FuchsiaVideoDecoder due to GPU context loss.";
+    }
   }
 
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
diff --git a/media/video/video_encode_accelerator.cc b/media/video/video_encode_accelerator.cc
index 01ed620..fe6a103 100644
--- a/media/video/video_encode_accelerator.cc
+++ b/media/video/video_encode_accelerator.cc
@@ -41,7 +41,8 @@
     base::Optional<uint32_t> gop_length,
     base::Optional<uint8_t> h264_output_level,
     base::Optional<StorageType> storage_type,
-    ContentType content_type)
+    ContentType content_type,
+    const std::vector<SpatialLayer>& spatial_layers)
     : input_format(input_format),
       input_visible_size(input_visible_size),
       output_profile(output_profile),
@@ -51,7 +52,8 @@
       gop_length(gop_length),
       h264_output_level(h264_output_level),
       storage_type(storage_type),
-      content_type(content_type) {}
+      content_type(content_type),
+      spatial_layers(spatial_layers) {}
 
 VideoEncodeAccelerator::Config::~Config() = default;
 
@@ -74,6 +76,16 @@
     str += base::StringPrintf(", h264_output_level: %u",
                               h264_output_level.value());
   }
+
+  for (size_t i = 0; i < spatial_layers.size(); ++i) {
+    const auto& sl = spatial_layers[i];
+    str += base::StringPrintf(
+        "\nSL#%zu: width=%d, height=%d, bitrate_bps=%u"
+        ", framerate=%u, max_qp=%u"
+        ", num_of_temporal_layers=%u",
+        i, sl.width, sl.height, sl.bitrate_bps, sl.framerate, sl.max_qp,
+        sl.num_of_temporal_layers);
+  }
   return str;
 }
 
diff --git a/media/video/video_encode_accelerator.h b/media/video/video_encode_accelerator.h
index 4ed0582..114a8cf2a 100644
--- a/media/video/video_encode_accelerator.h
+++ b/media/video/video_encode_accelerator.h
@@ -110,6 +110,22 @@
     // kDmabuf if a video frame is referred by dmabuf.
     enum class StorageType { kShmem, kDmabuf };
 
+    struct MEDIA_EXPORT SpatialLayer {
+      // The encoder dimension of the spatial layer.
+      int32_t width;
+      int32_t height;
+      // The bitrate of encoded output stream of the spatial layer in bits per
+      // second.
+      uint32_t bitrate_bps;
+      uint32_t framerate;
+      // The recommended maximum qp value of the spatial layer. VEA can ignore
+      // this value.
+      uint8_t max_qp;
+      // The number of temporal layers of the spatial layer. The detail of
+      // the temporal layer structure is up to VideoEncodeAccelerator.
+      uint8_t num_of_temporal_layers;
+    };
+
     Config();
     Config(const Config& config);
 
@@ -121,7 +137,8 @@
            base::Optional<uint32_t> gop_length = base::nullopt,
            base::Optional<uint8_t> h264_output_level = base::nullopt,
            base::Optional<StorageType> storage_type = base::nullopt,
-           ContentType content_type = ContentType::kCamera);
+           ContentType content_type = ContentType::kCamera,
+           const std::vector<SpatialLayer>& spatial_layers = {});
 
     ~Config();
 
@@ -168,6 +185,12 @@
     // bright colors. With this content hint the encoder may choose to optimize
     // for the given use case.
     ContentType content_type;
+
+    // The configuration for spatial layers. This is not empty if and only if
+    // either spatial or temporal layer encoding is configured. When this is not
+    // empty, VideoEncodeAccelerator should refer the width, height, bitrate and
+    // etc. of |spatial_layers|.
+    std::vector<SpatialLayer> spatial_layers;
   };
 
   // Interface for clients that use VideoEncodeAccelerator. These callbacks will
diff --git a/net/base/features.cc b/net/base/features.cc
index 46b4562..4b96302 100644
--- a/net/base/features.cc
+++ b/net/base/features.cc
@@ -11,6 +11,21 @@
 const base::Feature kAcceptLanguageHeader{"AcceptLanguageHeader",
                                           base::FEATURE_ENABLED_BY_DEFAULT};
 
+const base::Feature kDnsHttpssvc{"DnsHttpssvc",
+                                 base::FEATURE_DISABLED_BY_DEFAULT};
+
+const base::FeatureParam<bool> kDnsHttpssvcUseHttpssvc{
+    &kDnsHttpssvc, "DnsHttpssvcUseHttpssvc", false};
+
+const base::FeatureParam<bool> kDnsHttpssvcUseIntegrity{
+    &kDnsHttpssvc, "DnsHttpssvcUseIntegrity", false};
+
+const base::FeatureParam<int> kDnsHttpssvcExtraTimeMs{
+    &kDnsHttpssvc, "DnsHttpssvcExtraTimeMs", 10};
+
+const base::FeatureParam<int> kDnsHttpssvcExtraTimePercent{
+    &kDnsHttpssvc, "DnsHttpssvcExtraTimePercent", 5};
+
 const base::Feature kEnableTLS13EarlyData{"EnableTLS13EarlyData",
                                           base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/net/base/features.h b/net/base/features.h
index 2214b65..b6b2a4cc 100644
--- a/net/base/features.h
+++ b/net/base/features.h
@@ -20,6 +20,27 @@
 // Enables TLS 1.3 early data.
 NET_EXPORT extern const base::Feature kEnableTLS13EarlyData;
 
+// Enables DNS queries for HTTPSSVC or INTEGRITY records, depending on feature
+// parameters. These queries will only be made over DoH. HTTPSSVC responses may
+// cause us to upgrade the URL to HTTPS and/or to attempt QUIC.
+NET_EXPORT extern const base::Feature kDnsHttpssvc;
+
+// Determine which kind of record should be queried: HTTPSSVC or INTEGRITY. No
+// more than one of these feature parameters should be enabled at once. In the
+// event that both are enabled, |kDnsHttpssvcUseIntegrity| takes priority, and
+// |kDnsHttpssvcUseHttpssvc| will be ignored.
+NET_EXPORT extern const base::FeatureParam<bool> kDnsHttpssvcUseHttpssvc;
+NET_EXPORT extern const base::FeatureParam<bool> kDnsHttpssvcUseIntegrity;
+
+// If we are still waiting for an HTTPSSVC or INTEGRITY query after all the
+// other queries in a DnsTask have completed, we will compute a timeout for the
+// remaining query. The timeout will be the min of:
+//   (a) |kDnsHttpssvcExtraTimeMs.Get()|
+//   (b) |kDnsHttpssvcExtraTimePercent.Get() / 100 * t|, where |t| is the
+//       number of milliseconds since the first query began.
+NET_EXPORT extern const base::FeatureParam<int> kDnsHttpssvcExtraTimeMs;
+NET_EXPORT extern const base::FeatureParam<int> kDnsHttpssvcExtraTimePercent;
+
 // Enables optimizing the network quality estimation algorithms in network
 // quality estimator (NQE).
 NET_EXPORT extern const base::Feature kNetworkQualityEstimator;
diff --git a/net/disk_cache/blockfile/backend_impl.cc b/net/disk_cache/blockfile/backend_impl.cc
index ef6535bf..951607c 100644
--- a/net/disk_cache/blockfile/backend_impl.cc
+++ b/net/disk_cache/blockfile/backend_impl.cc
@@ -110,8 +110,10 @@
 }
 
 // A callback to perform final cleanup on the background thread.
-void FinalCleanupCallback(disk_cache::BackendImpl* backend) {
+void FinalCleanupCallback(disk_cache::BackendImpl* backend,
+                          base::WaitableEvent* done) {
   backend->CleanupCache();
+  done->Signal();
 }
 
 class CacheThread : public base::Thread {
@@ -159,9 +161,7 @@
       block_files_(path),
       mask_(0),
       user_flags_(0),
-      net_log_(net_log),
-      done_(base::WaitableEvent::ResetPolicy::MANUAL,
-            base::WaitableEvent::InitialState::NOT_SIGNALED) {
+      net_log_(net_log) {
   TRACE_EVENT0("disk_cache", "BackendImpl::BackendImpl");
 }
 
@@ -177,9 +177,7 @@
       block_files_(path),
       mask_(mask),
       user_flags_(kMask),
-      net_log_(net_log),
-      done_(base::WaitableEvent::ResetPolicy::MANUAL,
-            base::WaitableEvent::InitialState::NOT_SIGNALED) {
+      net_log_(net_log) {
   TRACE_EVENT0("disk_cache", "BackendImpl::BackendImpl");
 }
 
@@ -199,12 +197,15 @@
     // Unit tests may use the same sequence for everything.
     CleanupCache();
   } else {
+    // Signals the end of background work.
+    base::WaitableEvent done;
+
     background_queue_.background_thread()->PostTask(
-        FROM_HERE,
-        base::BindOnce(&FinalCleanupCallback, base::Unretained(this)));
+        FROM_HERE, base::BindOnce(&FinalCleanupCallback, base::Unretained(this),
+                                  base::Unretained(&done)));
     // http://crbug.com/74623
     base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow_wait;
-    done_.Wait();
+    done.Wait();
   }
 }
 
@@ -348,7 +349,6 @@
   FlushIndex();
   index_ = nullptr;
   ptr_factory_.InvalidateWeakPtrs();
-  done_.Signal();
 }
 
 // ------------------------------------------------------------------------
diff --git a/net/disk_cache/blockfile/backend_impl.h b/net/disk_cache/blockfile/backend_impl.h
index cefe8bb..db2a43f 100644
--- a/net/disk_cache/blockfile/backend_impl.h
+++ b/net/disk_cache/blockfile/backend_impl.h
@@ -426,7 +426,6 @@
 
   Stats stats_;  // Usage statistics.
   std::unique_ptr<base::RepeatingTimer> timer_;  // Usage timer.
-  base::WaitableEvent done_;  // Signals the end of background work.
   base::WeakPtrFactory<BackendImpl> ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(BackendImpl);
diff --git a/pdf/test/data/landscape_rectangles.in b/pdf/test/data/landscape_rectangles.in
new file mode 100644
index 0000000..9965b98
--- /dev/null
+++ b/pdf/test/data/landscape_rectangles.in
@@ -0,0 +1,44 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [ 0 0 800 500 ]
+  /CropBox [ 200 100 800 500 ]
+  /Count 1
+  /Kids [ 3 0 R ]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+0 0 0 rg
+200 100 20 20 re B*
+220 250 130 100 re B*
+1 0 1 rg
+780 480 20 20 re B*
+650 250 130 100 re B*
+1 1 0 rg
+780 100 20 20 re B*
+450 120 100 130 re B*
+0 1 1 rg
+200 480 20 20 re B*
+450 350 100 130 re B*
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/pdf/test/data/landscape_rectangles.pdf b/pdf/test/data/landscape_rectangles.pdf
new file mode 100644
index 0000000..0bae9e87
--- /dev/null
+++ b/pdf/test/data/landscape_rectangles.pdf
@@ -0,0 +1,55 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /MediaBox [ 0 0 800 500 ]
+  /CropBox [ 200 100 800 500 ]
+  /Count 1
+  /Kids [ 3 0 R ]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+>>
+endobj
+4 0 obj <<
+  /Length 208
+>>
+stream
+q
+0 0 0 rg
+200 100 20 20 re B*
+220 250 130 100 re B*
+1 0 1 rg
+780 480 20 20 re B*
+650 250 130 100 re B*
+1 1 0 rg
+780 100 20 20 re B*
+450 120 100 130 re B*
+0 1 1 rg
+200 480 20 20 re B*
+450 350 100 130 re B*
+Q
+endstream
+endobj
+xref
+0 5
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000192 00000 n 
+0000000261 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 5
+>>
+startxref
+521
+%%EOF
diff --git a/printing/pdf_metafile_cg_mac_unittest.cc b/printing/pdf_metafile_cg_mac_unittest.cc
index 62720a2..197dba8 100644
--- a/printing/pdf_metafile_cg_mac_unittest.cc
+++ b/printing/pdf_metafile_cg_mac_unittest.cc
@@ -212,11 +212,51 @@
   }
 }
 
-TEST(PdfMetafileCgTest, RenderPageBasic) {
+TEST(PdfMetafileCgTest, RenderPortraitRectangles) {
   constexpr gfx::Rect kPageBounds(200, 300);
   constexpr gfx::Size kDestinationSize(200, 300);
   TestRenderPage("rectangles.pdf", /*page_number=*/1, kPageBounds,
-                 "rectangles_cg_expected.pdf.0.png", kDestinationSize);
+                 "render_portrait_rectangles_expected.0.png", kDestinationSize);
+}
+
+TEST(PdfMetafileCgTest, RenderLargePortraitRectangles) {
+  constexpr gfx::Rect kPageBounds(200, 300);
+  constexpr gfx::Size kDestinationSize(100, 120);
+  TestRenderPage("rectangles.pdf", /*page_number=*/1, kPageBounds,
+                 "render_large_portrait_rectangles_expected.0.png",
+                 kDestinationSize);
+}
+
+TEST(PdfMetafileCgTest, RenderSmallPortraitRectangles) {
+  constexpr gfx::Rect kPageBounds(200, 300);
+  constexpr gfx::Size kDestinationSize(300, 450);
+  TestRenderPage("rectangles.pdf", /*page_number=*/1, kPageBounds,
+                 "render_small_portrait_rectangles_expected.0.png",
+                 kDestinationSize);
+}
+
+TEST(PdfMetafileCgTest, RenderLandscapeRectangles) {
+  constexpr gfx::Rect kPageBounds(800, 500);
+  constexpr gfx::Size kDestinationSize(400, 600);
+  TestRenderPage("landscape_rectangles.pdf", /*page_number=*/1, kPageBounds,
+                 "render_landscape_rectangles_expected.0.png",
+                 kDestinationSize);
+}
+
+TEST(PdfMetafileCgTest, RenderLargeLandscapeRectangles) {
+  constexpr gfx::Rect kPageBounds(800, 500);
+  constexpr gfx::Size kDestinationSize(200, 300);
+  TestRenderPage("landscape_rectangles.pdf", /*page_number=*/1, kPageBounds,
+                 "render_large_landscape_rectangles_expected.0.png",
+                 kDestinationSize);
+}
+
+TEST(PdfMetafileCgTest, RenderSmallLandscapeRectangles) {
+  constexpr gfx::Rect kPageBounds(800, 500);
+  constexpr gfx::Size kDestinationSize(600, 900);
+  TestRenderPage("landscape_rectangles.pdf", /*page_number=*/1, kPageBounds,
+                 "render_small_landscape_rectangles_expected.0.png",
+                 kDestinationSize);
 }
 
 }  // namespace printing
diff --git a/printing/test/data/pdf_cg/README.md b/printing/test/data/pdf_cg/README.md
index 5f2fdb0d..79e28d0 100644
--- a/printing/test/data/pdf_cg/README.md
+++ b/printing/test/data/pdf_cg/README.md
@@ -3,4 +3,5 @@
 The PNG files in this directory are the CoreGraphics rendering outputs for PDFs
 in //pdf/test/data/. They are generated from raw bitmaps using
 gfx::PNGCodec::Encode() using the |gfx::PNGCodec::FORMAT_BGRA| format. The PNGs
-are further optimized with optipng.
+are further optimized with optipng. Each PNG file is named after the
+PdfMetafileCgTest instance that uses it.
diff --git a/printing/test/data/pdf_cg/render_landscape_rectangles_expected.0.png b/printing/test/data/pdf_cg/render_landscape_rectangles_expected.0.png
new file mode 100644
index 0000000..1a12ef9
--- /dev/null
+++ b/printing/test/data/pdf_cg/render_landscape_rectangles_expected.0.png
Binary files differ
diff --git a/printing/test/data/pdf_cg/render_large_landscape_rectangles_expected.0.png b/printing/test/data/pdf_cg/render_large_landscape_rectangles_expected.0.png
new file mode 100644
index 0000000..20a70b2
--- /dev/null
+++ b/printing/test/data/pdf_cg/render_large_landscape_rectangles_expected.0.png
Binary files differ
diff --git a/printing/test/data/pdf_cg/render_large_portrait_rectangles_expected.0.png b/printing/test/data/pdf_cg/render_large_portrait_rectangles_expected.0.png
new file mode 100644
index 0000000..acaa3b5d
--- /dev/null
+++ b/printing/test/data/pdf_cg/render_large_portrait_rectangles_expected.0.png
Binary files differ
diff --git a/printing/test/data/pdf_cg/rectangles_cg_expected.pdf.0.png b/printing/test/data/pdf_cg/render_portrait_rectangles_expected.0.png
similarity index 100%
rename from printing/test/data/pdf_cg/rectangles_cg_expected.pdf.0.png
rename to printing/test/data/pdf_cg/render_portrait_rectangles_expected.0.png
Binary files differ
diff --git a/printing/test/data/pdf_cg/render_small_landscape_rectangles_expected.0.png b/printing/test/data/pdf_cg/render_small_landscape_rectangles_expected.0.png
new file mode 100644
index 0000000..d638aad
--- /dev/null
+++ b/printing/test/data/pdf_cg/render_small_landscape_rectangles_expected.0.png
Binary files differ
diff --git a/printing/test/data/pdf_cg/render_small_portrait_rectangles_expected.0.png b/printing/test/data/pdf_cg/render_small_portrait_rectangles_expected.0.png
new file mode 100644
index 0000000..e535f29
--- /dev/null
+++ b/printing/test/data/pdf_cg/render_small_portrait_rectangles_expected.0.png
Binary files differ
diff --git a/services/device/usb/usb_service_win.cc b/services/device/usb/usb_service_win.cc
index 5bb83392..58339ef 100644
--- a/services/device/usb/usb_service_win.cc
+++ b/services/device/usb/usb_service_win.cc
@@ -42,56 +42,32 @@
 
 using ScopedDevInfo = base::ScopedGeneric<HDEVINFO, DevInfoScopedTraits>;
 
-bool GetDeviceUint32Property(HDEVINFO dev_info,
-                             SP_DEVINFO_DATA* dev_info_data,
-                             const DEVPROPKEY& property,
-                             uint32_t* property_buffer) {
+base::Optional<uint32_t> GetDeviceUint32Property(HDEVINFO dev_info,
+                                                 SP_DEVINFO_DATA* dev_info_data,
+                                                 const DEVPROPKEY& property) {
   DEVPROPTYPE property_type;
-  if (!SetupDiGetDeviceProperty(dev_info, dev_info_data, &property,
-                                &property_type,
-                                reinterpret_cast<PBYTE>(property_buffer),
-                                sizeof(*property_buffer), nullptr, 0) ||
+  uint32_t buffer;
+  if (!SetupDiGetDeviceProperty(
+          dev_info, dev_info_data, &property, &property_type,
+          reinterpret_cast<PBYTE>(&buffer), sizeof(buffer), nullptr, 0) ||
       property_type != DEVPROP_TYPE_UINT32) {
-    return false;
+    return base::nullopt;
   }
 
-  return true;
+  return buffer;
 }
 
-bool GetDeviceStringProperty(HDEVINFO dev_info,
-                             SP_DEVINFO_DATA* dev_info_data,
-                             const DEVPROPKEY& property,
-                             base::string16* buffer) {
+base::Optional<base::string16> GetDeviceStringProperty(
+    HDEVINFO dev_info,
+    SP_DEVINFO_DATA* dev_info_data,
+    const DEVPROPKEY& property) {
   DEVPROPTYPE property_type;
   DWORD required_size;
   if (SetupDiGetDeviceProperty(dev_info, dev_info_data, &property,
                                &property_type, nullptr, 0, &required_size, 0) ||
       GetLastError() != ERROR_INSUFFICIENT_BUFFER ||
       property_type != DEVPROP_TYPE_STRING) {
-    return false;
-  }
-
-  if (!SetupDiGetDeviceProperty(
-          dev_info, dev_info_data, &property, &property_type,
-          reinterpret_cast<PBYTE>(base::WriteInto(buffer, required_size)),
-          required_size, nullptr, 0)) {
-    return false;
-  }
-
-  return true;
-}
-
-bool GetDeviceStringListProperty(HDEVINFO dev_info,
-                                 SP_DEVINFO_DATA* dev_info_data,
-                                 const DEVPROPKEY& property,
-                                 std::vector<base::string16>* result) {
-  DEVPROPTYPE property_type;
-  DWORD required_size;
-  if (SetupDiGetDeviceProperty(dev_info, dev_info_data, &property,
-                               &property_type, nullptr, 0, &required_size, 0) ||
-      GetLastError() != ERROR_INSUFFICIENT_BUFFER ||
-      property_type != DEVPROP_TYPE_STRING_LIST) {
-    return false;
+    return base::nullopt;
   }
 
   base::string16 buffer;
@@ -99,30 +75,50 @@
           dev_info, dev_info_data, &property, &property_type,
           reinterpret_cast<PBYTE>(base::WriteInto(&buffer, required_size)),
           required_size, nullptr, 0)) {
-    return false;
+    return base::nullopt;
+  }
+
+  return buffer;
+}
+
+base::Optional<std::vector<base::string16>> GetDeviceStringListProperty(
+    HDEVINFO dev_info,
+    SP_DEVINFO_DATA* dev_info_data,
+    const DEVPROPKEY& property) {
+  DEVPROPTYPE property_type;
+  DWORD required_size;
+  if (SetupDiGetDeviceProperty(dev_info, dev_info_data, &property,
+                               &property_type, nullptr, 0, &required_size, 0) ||
+      GetLastError() != ERROR_INSUFFICIENT_BUFFER ||
+      property_type != DEVPROP_TYPE_STRING_LIST) {
+    return base::nullopt;
+  }
+
+  base::string16 buffer;
+  if (!SetupDiGetDeviceProperty(
+          dev_info, dev_info_data, &property, &property_type,
+          reinterpret_cast<PBYTE>(base::WriteInto(&buffer, required_size)),
+          required_size, nullptr, 0)) {
+    return base::nullopt;
   }
 
   // Windows string list properties use a NUL character as the delimiter.
-  *result = base::SplitString(buffer, base::StringPiece16(L"\0", 1),
-                              base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
-  return true;
+  return base::SplitString(buffer, base::StringPiece16(L"\0", 1),
+                           base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
 }
 
-bool GetServiceName(HDEVINFO dev_info,
-                    SP_DEVINFO_DATA* dev_info_data,
-                    base::string16* service_name) {
-  base::string16 buffer;
-  if (!GetDeviceStringProperty(dev_info, dev_info_data, DEVPKEY_Device_Service,
-                               &buffer)) {
-    return false;
-  }
+base::string16 GetServiceName(HDEVINFO dev_info,
+                              SP_DEVINFO_DATA* dev_info_data) {
+  base::Optional<base::string16> property =
+      GetDeviceStringProperty(dev_info, dev_info_data, DEVPKEY_Device_Service);
+  if (!property.has_value())
+    return base::string16();
 
   // Windows pads this string with a variable number of NUL bytes for no
   // discernible reason.
-  *service_name = base::TrimString(buffer, base::StringPiece16(L"\0", 1),
-                                   base::TRIM_TRAILING)
-                      .as_string();
-  return true;
+  return base::TrimString(*property, base::StringPiece16(L"\0", 1),
+                          base::TRIM_TRAILING)
+      .as_string();
 }
 
 bool GetDeviceInterfaceDetails(HDEVINFO dev_info,
@@ -155,46 +151,56 @@
     return false;
   }
 
-  if (device_path) {
+  if (device_path)
     *device_path = base::string16(device_interface_detail_data->DevicePath);
-  }
 
   if (bus_number) {
-    if (!GetDeviceUint32Property(dev_info, &dev_info_data,
-                                 DEVPKEY_Device_BusNumber, bus_number)) {
+    auto result = GetDeviceUint32Property(dev_info, &dev_info_data,
+                                          DEVPKEY_Device_BusNumber);
+    if (!result.has_value()) {
       USB_PLOG(ERROR) << "Failed to get device bus number";
       return false;
     }
+    *bus_number = result.value();
   }
 
   if (port_number) {
-    if (!GetDeviceUint32Property(dev_info, &dev_info_data,
-                                 DEVPKEY_Device_Address, port_number)) {
+    auto result = GetDeviceUint32Property(dev_info, &dev_info_data,
+                                          DEVPKEY_Device_Address);
+    if (!result.has_value()) {
       USB_PLOG(ERROR) << "Failed to get device address";
       return false;
     }
+    *port_number = result.value();
   }
 
   if (parent_instance_id) {
-    if (!GetDeviceStringProperty(dev_info, &dev_info_data,
-                                 DEVPKEY_Device_Parent, parent_instance_id)) {
+    auto result = GetDeviceStringProperty(dev_info, &dev_info_data,
+                                          DEVPKEY_Device_Parent);
+    if (!result.has_value()) {
       USB_PLOG(ERROR) << "Failed to get the device parent";
       return false;
     }
+    *parent_instance_id = std::move(result.value());
   }
 
   if (child_instance_ids) {
-    if (!GetDeviceStringListProperty(dev_info, &dev_info_data,
-                                     DEVPKEY_Device_Children,
-                                     child_instance_ids) &&
-        GetLastError() != ERROR_NOT_FOUND) {
-      USB_PLOG(ERROR) << "Failed to get device children";
-      return false;
+    auto result = GetDeviceStringListProperty(dev_info, &dev_info_data,
+                                              DEVPKEY_Device_Children);
+    if (!result.has_value()) {
+      if (GetLastError() != ERROR_NOT_FOUND) {
+        result.emplace();
+      } else {
+        USB_PLOG(ERROR) << "Failed to get device children";
+        return false;
+      }
     }
+    *child_instance_ids = std::move(result.value());
   }
 
   if (service_name) {
-    if (!GetServiceName(dev_info, &dev_info_data, service_name)) {
+    *service_name = GetServiceName(dev_info, &dev_info_data);
+    if (service_name->empty()) {
       USB_PLOG(ERROR) << "Failed to get device driver name";
       return false;
     }
@@ -203,15 +209,14 @@
   return true;
 }
 
-bool GetDevicePath(const base::string16& instance_id,
-                   const GUID& device_interface_guid,
-                   base::string16* device_path) {
+base::string16 GetDevicePath(const base::string16& instance_id,
+                             const GUID& device_interface_guid) {
   ScopedDevInfo dev_info(
       SetupDiGetClassDevs(&device_interface_guid, instance_id.c_str(), 0,
                           DIGCF_DEVICEINTERFACE | DIGCF_PRESENT));
   if (!dev_info.is_valid()) {
     USB_PLOG(ERROR) << "SetupDiGetClassDevs";
-    return false;
+    return base::string16();
   }
 
   SP_DEVICE_INTERFACE_DATA device_interface_data = {};
@@ -220,20 +225,24 @@
                                    &device_interface_guid, 0,
                                    &device_interface_data)) {
     USB_PLOG(ERROR) << "SetupDiEnumDeviceInterfaces";
-    return false;
+    return base::string16();
   }
 
-  return GetDeviceInterfaceDetails(dev_info.get(), &device_interface_data,
-                                   device_path, nullptr, nullptr, nullptr,
-                                   nullptr, nullptr);
+  base::string16 device_path;
+  if (!GetDeviceInterfaceDetails(dev_info.get(), &device_interface_data,
+                                 &device_path, nullptr, nullptr, nullptr,
+                                 nullptr, nullptr)) {
+    return base::string16();
+  }
+
+  return device_path;
 }
 
-bool GetWinUsbDevicePath(const base::string16& instance_id,
-                         base::string16* device_path) {
+base::string16 GetWinUsbDevicePath(const base::string16& instance_id) {
   ScopedDevInfo dev_info(SetupDiCreateDeviceInfoList(nullptr, nullptr));
   if (!dev_info.is_valid()) {
     USB_PLOG(ERROR) << "SetupDiCreateDeviceInfoList";
-    return false;
+    return base::string16();
   }
 
   SP_DEVINFO_DATA dev_info_data = {};
@@ -241,17 +250,17 @@
   if (!SetupDiOpenDeviceInfo(dev_info.get(), instance_id.c_str(), nullptr, 0,
                              &dev_info_data)) {
     USB_PLOG(ERROR) << "SetupDiOpenDeviceInfo";
-    return false;
+    return base::string16();
   }
 
-  base::string16 service_name;
-  if (!GetServiceName(dev_info.get(), &dev_info_data, &service_name)) {
+  base::string16 service_name = GetServiceName(dev_info.get(), &dev_info_data);
+  if (service_name.empty()) {
     USB_PLOG(ERROR) << "Could not get child device's service name";
-    return false;
+    return base::string16();
   }
 
   if (!base::EqualsCaseInsensitiveASCII(service_name, L"winusb"))
-    return false;
+    return base::string16();
 
   // There is no standard device interface GUID for USB functions and so we
   // must discover the set of GUIDs that have been set in the registry by
@@ -261,7 +270,7 @@
                                   DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
   if (key == INVALID_HANDLE_VALUE) {
     USB_PLOG(ERROR) << "Could not open device registry key";
-    return false;
+    return base::string16();
   }
   base::win::RegKey scoped_key(key);
 
@@ -271,7 +280,7 @@
   if (result != ERROR_SUCCESS) {
     USB_LOG(ERROR) << "Could not read device interface GUIDs: "
                    << logging::SystemErrorCodeToString(result);
-    return false;
+    return base::string16();
   }
 
   for (const auto& guid_string : device_interface_guids) {
@@ -282,11 +291,12 @@
       continue;
     }
 
-    if (GetDevicePath(instance_id, guid, device_path))
-      return true;
+    base::string16 device_path = GetDevicePath(instance_id, guid);
+    if (!device_path.empty())
+      return device_path;
   }
 
-  return false;
+  return base::string16();
 }
 
 }  // namespace
@@ -374,20 +384,17 @@
     std::vector<base::string16> child_device_paths;
     if (base::EqualsCaseInsensitiveASCII(service_name, L"usbccgp")) {
       for (const base::string16& instance_id : child_instance_ids) {
-        base::string16 child_device_path;
-        if (GetWinUsbDevicePath(instance_id, &child_device_path))
+        base::string16 child_device_path = GetWinUsbDevicePath(instance_id);
+        if (!child_device_path.empty())
           child_device_paths.push_back(std::move(child_device_path));
       }
     }
 
     base::string16& hub_path = hub_paths_[parent_instance_id];
     if (hub_path.empty()) {
-      base::string16 parent_path;
-      if (!GetDevicePath(parent_instance_id, GUID_DEVINTERFACE_USB_HUB,
-                         &parent_path)) {
+      hub_path = GetDevicePath(parent_instance_id, GUID_DEVINTERFACE_USB_HUB);
+      if (hub_path.empty())
         return;
-      }
-      hub_path = parent_path;
     }
 
     service_task_runner_->PostTask(
diff --git a/services/network/trust_tokens/suitable_trust_token_origin.h b/services/network/trust_tokens/suitable_trust_token_origin.h
index 79c6ce37..3077597 100644
--- a/services/network/trust_tokens/suitable_trust_token_origin.h
+++ b/services/network/trust_tokens/suitable_trust_token_origin.h
@@ -37,6 +37,13 @@
   std::string Serialize() const;
   const url::Origin& origin() const { return origin_; }
 
+  // This implicit "widening" conversion is allowed to ease drop-in use of
+  // SuitableTrustTokenOrigin in places currently requiring url::Origins with
+  // guaranteed preconditions. The intended use is creating a
+  // SuitableTrustTokenOrigin to confirm the preconditions, then directly
+  // passing the SuitableTrustTokenOrigin to url::Origin-accepting callsite.
+  operator const url::Origin&() const { return origin_; }  // NOLINT
+
   // Constructs a SuitableTrustTokenOrigin from the given origin. Public only as
   // an implementation detail; clients should use |Create|.
   SuitableTrustTokenOrigin(util::PassKey<SuitableTrustTokenOrigin>,
diff --git a/services/network/trust_tokens/trust_token_request_issuance_helper.cc b/services/network/trust_tokens/trust_token_request_issuance_helper.cc
index f03393d..67101a81 100644
--- a/services/network/trust_tokens/trust_token_request_issuance_helper.cc
+++ b/services/network/trust_tokens/trust_token_request_issuance_helper.cc
@@ -14,6 +14,7 @@
 #include "services/network/public/mojom/trust_tokens.mojom.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
 #include "services/network/trust_tokens/proto/public.pb.h"
+#include "services/network/trust_tokens/suitable_trust_token_origin.h"
 #include "services/network/trust_tokens/trust_token_http_headers.h"
 #include "services/network/trust_tokens/trust_token_parameterization.h"
 #include "services/network/trust_tokens/trust_token_store.h"
@@ -23,19 +24,14 @@
 namespace network {
 
 TrustTokenRequestIssuanceHelper::TrustTokenRequestIssuanceHelper(
-    const url::Origin& top_level_origin,
+    SuitableTrustTokenOrigin top_level_origin,
     TrustTokenStore* token_store,
     std::unique_ptr<TrustTokenKeyCommitmentGetter> key_commitment_getter,
     std::unique_ptr<Cryptographer> cryptographer)
-    : top_level_origin_(top_level_origin),
+    : top_level_origin_(std::move(top_level_origin)),
       token_store_(token_store),
       key_commitment_getter_(std::move(key_commitment_getter)),
       cryptographer_(std::move(cryptographer)) {
-  DCHECK(top_level_origin.scheme() == url::kHttpsScheme ||
-         (top_level_origin.scheme() == url::kHttpScheme &&
-          IsOriginPotentiallyTrustworthy(top_level_origin)))
-      << top_level_origin;
-
   DCHECK(token_store_);
   DCHECK(key_commitment_getter_);
   DCHECK(cryptographer_);
@@ -51,29 +47,30 @@
 void TrustTokenRequestIssuanceHelper::Begin(
     net::URLRequest* request,
     base::OnceCallback<void(mojom::TrustTokenOperationStatus)> done) {
-  DCHECK(request->url().SchemeIsHTTPOrHTTPS() &&
-         IsUrlPotentiallyTrustworthy(request->url()))
-      << request->url();
-  DCHECK(request->initiator() &&
-             request->initiator()->scheme() == url::kHttpsScheme ||
-         (request->initiator()->scheme() == url::kHttpScheme &&
-          IsOriginPotentiallyTrustworthy(*request->initiator())))
-      << (request->initiator() ? request->initiator()->Serialize()
-                               : "(missing)");
+  DCHECK(request);
+  DCHECK(!request->initiator() ||
+         IsOriginPotentiallyTrustworthy(*request->initiator()))
+      << *request->initiator();
 
-  issuer_ = url::Origin::Create(request->url());
-  if (!token_store_->SetAssociation(issuer_, top_level_origin_)) {
+  issuer_ = SuitableTrustTokenOrigin::Create(request->url());
+  if (!issuer_) {
+    std::move(done).Run(mojom::TrustTokenOperationStatus::kInvalidArgument);
+    return;
+  }
+
+  if (!token_store_->SetAssociation(*issuer_, top_level_origin_)) {
     std::move(done).Run(mojom::TrustTokenOperationStatus::kResourceExhausted);
     return;
   }
 
-  if (token_store_->CountTokens(issuer_) == kTrustTokenPerIssuerTokenCapacity) {
+  if (token_store_->CountTokens(*issuer_) ==
+      kTrustTokenPerIssuerTokenCapacity) {
     std::move(done).Run(mojom::TrustTokenOperationStatus::kResourceExhausted);
     return;
   }
 
   key_commitment_getter_->Get(
-      issuer_,
+      *issuer_,
       base::BindOnce(&TrustTokenRequestIssuanceHelper::OnGotKeyCommitment,
                      weak_ptr_factory_.GetWeakPtr(), request, std::move(done)));
 }
@@ -98,7 +95,7 @@
 
   // Evict tokens signed with keys other than those from the issuer's most
   // recent commitments.
-  token_store_->PruneStaleIssuerState(issuer_, commitment_result->keys);
+  token_store_->PruneStaleIssuerState(*issuer_, commitment_result->keys);
 
   int batch_size = commitment_result->batch_size
                        ? std::min(commitment_result->batch_size->value,
@@ -154,7 +151,7 @@
     return mojom::TrustTokenOperationStatus::kBadResponse;
   }
 
-  token_store_->AddTokens(issuer_, base::make_span(maybe_tokens->tokens),
+  token_store_->AddTokens(*issuer_, base::make_span(maybe_tokens->tokens),
                           maybe_tokens->body_of_verifying_key);
 
   return mojom::TrustTokenOperationStatus::kOk;
diff --git a/services/network/trust_tokens/trust_token_request_issuance_helper.h b/services/network/trust_tokens/trust_token_request_issuance_helper.h
index a2c4134..a55f0c0 100644
--- a/services/network/trust_tokens/trust_token_request_issuance_helper.h
+++ b/services/network/trust_tokens/trust_token_request_issuance_helper.h
@@ -14,6 +14,7 @@
 #include "base/strings/string_piece_forward.h"
 #include "services/network/public/mojom/trust_tokens.mojom-shared.h"
 #include "services/network/trust_tokens/proto/public.pb.h"
+#include "services/network/trust_tokens/suitable_trust_token_origin.h"
 #include "services/network/trust_tokens/trust_token_key_commitment_getter.h"
 #include "services/network/trust_tokens/trust_token_request_helper.h"
 #include "url/origin.h"
@@ -95,7 +96,7 @@
   // REQUIRES: |token_store|, |key_commitment_getter|, and |cryptographer| must
   // be non-null.
   TrustTokenRequestIssuanceHelper(
-      const url::Origin& top_level_origin,
+      SuitableTrustTokenOrigin top_level_origin,
       TrustTokenStore* token_store,
       std::unique_ptr<TrustTokenKeyCommitmentGetter> key_commitment_getter,
       std::unique_ptr<Cryptographer> cryptographer);
@@ -155,8 +156,11 @@
       base::OnceCallback<void(mojom::TrustTokenOperationStatus)> done,
       mojom::TrustTokenKeyCommitmentResultPtr commitment_result);
 
-  url::Origin issuer_;
-  const url::Origin top_level_origin_;
+  // |issuer_| needs to be a nullable type because it is initialized in |Begin|,
+  // but, once initialized, it will never be empty over the course of the
+  // operation's execution.
+  base::Optional<SuitableTrustTokenOrigin> issuer_;
+  const SuitableTrustTokenOrigin top_level_origin_;
   TrustTokenStore* const token_store_;
   const std::unique_ptr<TrustTokenKeyCommitmentGetter> key_commitment_getter_;
   const std::unique_ptr<Cryptographer> cryptographer_;
diff --git a/services/network/trust_tokens/trust_token_request_issuance_helper_unittest.cc b/services/network/trust_tokens/trust_token_request_issuance_helper_unittest.cc
index 67c2658..099e9fa 100644
--- a/services/network/trust_tokens/trust_token_request_issuance_helper_unittest.cc
+++ b/services/network/trust_tokens/trust_token_request_issuance_helper_unittest.cc
@@ -83,7 +83,8 @@
   std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateInMemory();
 
   auto issuer = url::Origin::Create(GURL("https://issuer.com/"));
-  auto toplevel = url::Origin::Create(GURL("https://toplevel.com/"));
+  auto toplevel =
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/"));
 
   // Associate the toplevel with the cap's worth of issuers different from
   // |issuer|. (The cap is guaranteed to be quite small because of privacy
@@ -119,8 +120,8 @@
                    /*issuing_key=*/"");
 
   TrustTokenRequestIssuanceHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")), store.get(),
-      std::make_unique<FixedKeyCommitmentGetter>(),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
+      store.get(), std::make_unique<FixedKeyCommitmentGetter>(),
       std::make_unique<MockCryptographer>());
 
   auto request = MakeURLRequest("https://issuer.com/");
@@ -138,8 +139,8 @@
   // Have the key commitment getter return nullptr, denoting that the key
   // commitment fetch failed.
   TrustTokenRequestIssuanceHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")), store.get(),
-      std::make_unique<FixedKeyCommitmentGetter>(issuer, nullptr),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
+      store.get(), std::make_unique<FixedKeyCommitmentGetter>(issuer, nullptr),
       std::make_unique<MockCryptographer>());
 
   auto request = MakeURLRequest("https://issuer.com/");
@@ -165,8 +166,8 @@
   EXPECT_CALL(*cryptographer, AddKey(_)).WillOnce(Return(false));
 
   TrustTokenRequestIssuanceHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")), store.get(),
-      std::move(getter), std::move(cryptographer));
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
+      store.get(), std::move(getter), std::move(cryptographer));
 
   auto request = MakeURLRequest("https://issuer.com/");
   request->set_initiator(issuer);
@@ -195,8 +196,8 @@
   EXPECT_CALL(*cryptographer, BeginIssuance(_)).WillOnce(Return(base::nullopt));
 
   TrustTokenRequestIssuanceHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")), store.get(),
-      std::move(getter), std::move(cryptographer));
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
+      store.get(), std::move(getter), std::move(cryptographer));
 
   auto request = MakeURLRequest("https://issuer.com/");
   request->set_initiator(issuer);
@@ -230,8 +231,8 @@
           Return(std::string("this string contains some blinded tokens")));
 
   TrustTokenRequestIssuanceHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")), store.get(),
-      std::move(getter), std::move(cryptographer));
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
+      store.get(), std::move(getter), std::move(cryptographer));
 
   auto request = MakeURLRequest("https://issuer.com/");
   request->set_initiator(issuer);
@@ -267,8 +268,8 @@
           Return(std::string("this string contains some blinded tokens")));
 
   TrustTokenRequestIssuanceHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")), store.get(),
-      std::move(getter), std::move(cryptographer));
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
+      store.get(), std::move(getter), std::move(cryptographer));
 
   auto request = MakeURLRequest("https://issuer.com/");
   request->set_initiator(issuer);
@@ -298,8 +299,8 @@
           Return(std::string("this string contains some blinded tokens")));
 
   TrustTokenRequestIssuanceHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")), store.get(),
-      std::move(getter), std::move(cryptographer));
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
+      store.get(), std::move(getter), std::move(cryptographer));
 
   auto request = MakeURLRequest("https://issuer.com/");
   request->set_initiator(issuer);
@@ -337,8 +338,8 @@
   EXPECT_CALL(*cryptographer, ConfirmIssuance(_)).WillOnce(ReturnNull());
 
   TrustTokenRequestIssuanceHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")), store.get(),
-      std::move(getter), std::move(cryptographer));
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
+      store.get(), std::move(getter), std::move(cryptographer));
 
   auto request = MakeURLRequest("https://issuer.com/");
   request->set_initiator(issuer);
@@ -383,8 +384,8 @@
       .WillOnce(Return(ByMove(std::make_unique<UnblindedTokens>())));
 
   TrustTokenRequestIssuanceHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")), store.get(),
-      std::move(getter), std::move(cryptographer));
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
+      store.get(), std::move(getter), std::move(cryptographer));
 
   auto request = MakeURLRequest("https://issuer.com/");
   request->set_initiator(issuer);
@@ -427,8 +428,8 @@
           Return(std::string("this string contains some blinded tokens")));
 
   TrustTokenRequestIssuanceHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")), store.get(),
-      std::move(getter), std::move(cryptographer));
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
+      store.get(), std::move(getter), std::move(cryptographer));
 
   auto request = MakeURLRequest("https://issuer.com/");
   request->set_initiator(issuer);
@@ -438,8 +439,8 @@
 
   // After the operation has successfully begun, the issuer and the toplevel
   // should be associated.
-  EXPECT_TRUE(store->IsAssociated(
-      issuer, url::Origin::Create(GURL("https://toplevel.com/"))));
+  EXPECT_TRUE(store->IsAssociated(issuer, *SuitableTrustTokenOrigin::Create(
+                                              GURL("https://toplevel.com/"))));
 }
 
 // Check that a successful end-to-end Begin/Finalize flow stores the obtained
@@ -471,8 +472,8 @@
       .WillOnce(Return(ByMove(std::move((unblinded_tokens)))));
 
   TrustTokenRequestIssuanceHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")), store.get(),
-      std::move(getter), std::move(cryptographer));
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
+      store.get(), std::move(getter), std::move(cryptographer));
 
   auto request = MakeURLRequest("https://issuer.com/");
   request->set_initiator(issuer);
@@ -498,4 +499,32 @@
       store->RetrieveMatchingTokens(issuer, std::move(match_all_keys)),
       ElementsAre(Property(&TrustToken::body, "a signed, unblinded token")));
 }
+
+TEST_F(TrustTokenRequestIssuanceHelperTest, RejectsUnsuitableInsecureIssuer) {
+  auto store = TrustTokenStore::CreateInMemory();
+  TrustTokenRequestIssuanceHelper helper(
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
+      store.get(), std::make_unique<FixedKeyCommitmentGetter>(),
+      std::make_unique<MockCryptographer>());
+
+  auto request = MakeURLRequest("http://insecure-issuer.com/");
+
+  EXPECT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
+            mojom::TrustTokenOperationStatus::kInvalidArgument);
+}
+
+TEST_F(TrustTokenRequestIssuanceHelperTest,
+       RejectsUnsuitableNonHttpNonHttpsIssuer) {
+  auto store = TrustTokenStore::CreateInMemory();
+  TrustTokenRequestIssuanceHelper helper(
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
+      store.get(), std::make_unique<FixedKeyCommitmentGetter>(),
+      std::make_unique<MockCryptographer>());
+
+  auto request = MakeURLRequest("file:///non-https-issuer.txt");
+
+  EXPECT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
+            mojom::TrustTokenOperationStatus::kInvalidArgument);
+}
+
 }  // namespace network
diff --git a/services/network/trust_tokens/trust_token_request_redemption_helper.cc b/services/network/trust_tokens/trust_token_request_redemption_helper.cc
index 1ba31b8..57239fd 100644
--- a/services/network/trust_tokens/trust_token_request_redemption_helper.cc
+++ b/services/network/trust_tokens/trust_token_request_redemption_helper.cc
@@ -24,7 +24,7 @@
 namespace network {
 
 TrustTokenRequestRedemptionHelper::TrustTokenRequestRedemptionHelper(
-    const url::Origin& top_level_origin,
+    SuitableTrustTokenOrigin top_level_origin,
     mojom::TrustTokenRefreshPolicy refresh_policy,
     TrustTokenStore* token_store,
     std::unique_ptr<TrustTokenKeyCommitmentGetter> key_commitment_getter,
@@ -36,11 +36,6 @@
       key_commitment_getter_(std::move(key_commitment_getter)),
       key_pair_generator_(std::move(key_pair_generator)),
       cryptographer_(std::move(cryptographer)) {
-  DCHECK(top_level_origin.scheme() == url::kHttpsScheme ||
-         (top_level_origin.scheme() == url::kHttpScheme &&
-          IsOriginPotentiallyTrustworthy(top_level_origin)))
-      << top_level_origin;
-
   DCHECK(token_store_);
   DCHECK(key_commitment_getter_);
   DCHECK(key_pair_generator_);
@@ -53,37 +48,38 @@
 void TrustTokenRequestRedemptionHelper::Begin(
     net::URLRequest* request,
     base::OnceCallback<void(mojom::TrustTokenOperationStatus)> done) {
-  DCHECK(request->url().SchemeIsHTTPOrHTTPS() &&
-         IsUrlPotentiallyTrustworthy(request->url()))
-      << request->url();
-  DCHECK(request->initiator() &&
-             request->initiator()->scheme() == url::kHttpsScheme ||
-         (request->initiator()->scheme() == url::kHttpScheme &&
-          IsOriginPotentiallyTrustworthy(*request->initiator())))
-      << (request->initiator() ? request->initiator()->Serialize() : "(none)");
+  DCHECK(request);
+  DCHECK(!request->initiator() ||
+         IsOriginPotentiallyTrustworthy(*request->initiator()))
+      << *request->initiator();
 
-  issuer_ = url::Origin::Create(request->url());
+  issuer_ = SuitableTrustTokenOrigin::Create(request->url());
+  if (!issuer_) {
+    std::move(done).Run(mojom::TrustTokenOperationStatus::kInvalidArgument);
+    return;
+  }
 
   if (refresh_policy_ == mojom::TrustTokenRefreshPolicy::kRefresh &&
-      !request->initiator()->IsSameOriginWith(issuer_)) {
+      (!request->initiator() ||
+       !request->initiator()->IsSameOriginWith(*issuer_))) {
     std::move(done).Run(mojom::TrustTokenOperationStatus::kFailedPrecondition);
     return;
   }
 
-  if (!token_store_->SetAssociation(issuer_, top_level_origin_)) {
+  if (!token_store_->SetAssociation(*issuer_, top_level_origin_)) {
     std::move(done).Run(mojom::TrustTokenOperationStatus::kResourceExhausted);
     return;
   }
 
   if (refresh_policy_ == mojom::TrustTokenRefreshPolicy::kUseCached &&
-      token_store_->RetrieveNonstaleRedemptionRecord(issuer_,
+      token_store_->RetrieveNonstaleRedemptionRecord(*issuer_,
                                                      top_level_origin_)) {
     std::move(done).Run(mojom::TrustTokenOperationStatus::kAlreadyExists);
     return;
   }
 
   key_commitment_getter_->Get(
-      issuer_,
+      *issuer_,
       base::BindOnce(&TrustTokenRequestRedemptionHelper::OnGotKeyCommitment,
                      weak_factory_.GetWeakPtr(), request, std::move(done)));
 }
@@ -99,7 +95,7 @@
 
   // Evict tokens signed with keys other than those from the issuer's most
   // recent commitments.
-  token_store_->PruneStaleIssuerState(issuer_, commitment_result->keys);
+  token_store_->PruneStaleIssuerState(*issuer_, commitment_result->keys);
 
   base::Optional<TrustToken> maybe_token_to_redeem = RetrieveSingleToken();
   if (!maybe_token_to_redeem) {
@@ -132,7 +128,7 @@
   // settings.
   request->SetLoadFlags(request->load_flags() | net::LOAD_BYPASS_CACHE);
 
-  token_store_->DeleteToken(issuer_, *maybe_token_to_redeem);
+  token_store_->DeleteToken(*issuer_, *maybe_token_to_redeem);
 
   std::move(done).Run(mojom::TrustTokenOperationStatus::kOk);
 }
@@ -181,7 +177,7 @@
   record_to_store.set_body(std::move(*maybe_signed_redemption_record));
   record_to_store.set_signing_key(std::move(signing_key_));
   record_to_store.set_public_key(std::move(verification_key_));
-  token_store_->SetRedemptionRecord(issuer_, top_level_origin_,
+  token_store_->SetRedemptionRecord(*issuer_, top_level_origin_,
                                     std::move(record_to_store));
 
   return mojom::TrustTokenOperationStatus::kOk;
@@ -197,7 +193,7 @@
       base::BindRepeating([](const std::string&) { return true; });
 
   std::vector<TrustToken> matching_tokens =
-      token_store_->RetrieveMatchingTokens(issuer_, key_matcher);
+      token_store_->RetrieveMatchingTokens(*issuer_, key_matcher);
 
   if (matching_tokens.empty())
     return base::nullopt;
diff --git a/services/network/trust_tokens/trust_token_request_redemption_helper.h b/services/network/trust_tokens/trust_token_request_redemption_helper.h
index c8e976b..e500317 100644
--- a/services/network/trust_tokens/trust_token_request_redemption_helper.h
+++ b/services/network/trust_tokens/trust_token_request_redemption_helper.h
@@ -14,6 +14,7 @@
 #include "base/strings/string_piece_forward.h"
 #include "services/network/public/mojom/trust_tokens.mojom.h"
 #include "services/network/trust_tokens/proto/public.pb.h"
+#include "services/network/trust_tokens/suitable_trust_token_origin.h"
 #include "services/network/trust_tokens/trust_token_key_commitment_getter.h"
 #include "services/network/trust_tokens/trust_token_request_helper.h"
 #include "url/origin.h"
@@ -98,7 +99,7 @@
   // |cryptographer| are delegates that help execute the protocol; see
   // their class comments.
   TrustTokenRequestRedemptionHelper(
-      const url::Origin& top_level_origin,
+      SuitableTrustTokenOrigin top_level_origin,
       mojom::TrustTokenRefreshPolicy refresh_policy,
       TrustTokenStore* token_store,
       std::unique_ptr<TrustTokenKeyCommitmentGetter> key_commitment_getter,
@@ -161,7 +162,11 @@
 
   // |issuer_|, |top_level_origin_|, and |refresh_policy_| are parameters
   // determining the scope and control flow of the redemption operation.
-  url::Origin issuer_;
+  //
+  // |issuer_| needs to be a nullable type because it is initialized in |Begin|,
+  // but, once initialized, it will never be empty over the course of the
+  // operation's execution.
+  base::Optional<SuitableTrustTokenOrigin> issuer_;
   const url::Origin top_level_origin_;
   const mojom::TrustTokenRefreshPolicy refresh_policy_;
 
diff --git a/services/network/trust_tokens/trust_token_request_redemption_helper_unittest.cc b/services/network/trust_tokens/trust_token_request_redemption_helper_unittest.cc
index 62df2ded..a9e527c 100644
--- a/services/network/trust_tokens/trust_token_request_redemption_helper_unittest.cc
+++ b/services/network/trust_tokens/trust_token_request_redemption_helper_unittest.cc
@@ -109,7 +109,8 @@
   std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateInMemory();
 
   auto issuer = url::Origin::Create(GURL("https://issuer.com/"));
-  auto toplevel = url::Origin::Create(GURL("https://toplevel.com/"));
+  auto toplevel =
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/"));
 
   // Associate the toplevel with the cap's worth of issuers different from
   // |issuer|. (The cap is guaranteed to be quite small because of privacy
@@ -122,7 +123,7 @@
   }
 
   TrustTokenRequestRedemptionHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
       mojom::TrustTokenRefreshPolicy::kUseCached, store.get(),
       std::make_unique<FixedKeyCommitmentGetter>(),
       std::make_unique<FakeKeyPairGenerator>(),
@@ -144,7 +145,7 @@
   // Have the key commitment getter return nullptr, denoting that the key
   // commitment fetch failed.
   TrustTokenRequestRedemptionHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
       mojom::TrustTokenRefreshPolicy::kUseCached, store.get(),
       std::make_unique<FixedKeyCommitmentGetter>(
           url::Origin::Create(GURL("https://issuer.com/")), nullptr),
@@ -173,7 +174,7 @@
       mojom::TrustTokenKeyCommitmentResult::New());
 
   TrustTokenRequestRedemptionHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
       mojom::TrustTokenRefreshPolicy::kUseCached, store.get(),
       std::move(getter), std::make_unique<FakeKeyPairGenerator>(),
       std::make_unique<MockCryptographer>());
@@ -216,7 +217,7 @@
       .WillOnce(Return(base::nullopt));
 
   TrustTokenRequestRedemptionHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
       mojom::TrustTokenRefreshPolicy::kUseCached, store.get(),
       std::move(getter), std::make_unique<FakeKeyPairGenerator>(),
       std::move(cryptographer));
@@ -255,7 +256,7 @@
   // Provide |helper| a FailingKeyPairGenerator to ensure that key pair
   // generation does not succeed.
   TrustTokenRequestRedemptionHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
       mojom::TrustTokenRefreshPolicy::kUseCached, store.get(),
       std::move(getter), std::make_unique<FailingKeyPairGenerator>(),
       std::make_unique<MockCryptographer>());
@@ -304,7 +305,7 @@
             Return(std::string("this string contains a redemption request")));
 
     TrustTokenRequestRedemptionHelper helper(
-        url::Origin::Create(GURL("https://toplevel.com/")),
+        *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
         mojom::TrustTokenRefreshPolicy::kUseCached, store.get(),
         std::move(getter), std::make_unique<FakeKeyPairGenerator>(),
         std::move(cryptographer));
@@ -366,7 +367,7 @@
           Return(std::string("this string contains a redemption request")));
 
   TrustTokenRequestRedemptionHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
       mojom::TrustTokenRefreshPolicy::kUseCached, store.get(),
       std::move(getter), std::make_unique<FakeKeyPairGenerator>(),
       std::move(cryptographer));
@@ -424,7 +425,7 @@
       .WillOnce(Return(base::nullopt));
 
   TrustTokenRequestRedemptionHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
       mojom::TrustTokenRefreshPolicy::kUseCached, store.get(),
       std::move(getter), std::make_unique<FakeKeyPairGenerator>(),
       std::move(cryptographer));
@@ -486,7 +487,7 @@
       .WillOnce(Return("a successfully-extracted SRR"));
 
   TrustTokenRequestRedemptionHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
       mojom::TrustTokenRefreshPolicy::kUseCached, store.get(),
       std::move(getter), std::make_unique<FakeKeyPairGenerator>(),
       std::move(cryptographer));
@@ -547,7 +548,7 @@
       .WillOnce(Return("well-formed redemption request"));
 
   TrustTokenRequestRedemptionHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
       mojom::TrustTokenRefreshPolicy::kUseCached, store.get(),
       std::move(getter), std::make_unique<FakeKeyPairGenerator>(),
       std::move(cryptographer));
@@ -565,9 +566,9 @@
 
   // After the operation has successfully begun, the issuer and the toplevel
   // should be associated.
-  EXPECT_TRUE(
-      store->IsAssociated(url::Origin::Create(GURL("https://issuer.com/")),
-                          url::Origin::Create(GURL("https://toplevel.com/"))));
+  EXPECT_TRUE(store->IsAssociated(
+      url::Origin::Create(GURL("https://issuer.com/")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/"))));
 }
 
 // Check that a successful end-to-end Begin/Finalize flow stores the obtained
@@ -599,7 +600,7 @@
       .WillOnce(Return("a successfully-extracted SRR"));
 
   TrustTokenRequestRedemptionHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
       mojom::TrustTokenRefreshPolicy::kUseCached, store.get(),
       std::move(getter),
       std::make_unique<MockKeyPairGenerator>("signing key", "verification key"),
@@ -624,7 +625,7 @@
   EXPECT_THAT(
       store->RetrieveNonstaleRedemptionRecord(
           url::Origin::Create(GURL("https://issuer.com/")),
-          url::Origin::Create(GURL("https://toplevel.com/"))),
+          *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/"))),
       Optional(AllOf(Property(&SignedTrustTokenRedemptionRecord::body,
                               "a successfully-extracted SRR"),
                      Property(&SignedTrustTokenRedemptionRecord::public_key,
@@ -640,7 +641,7 @@
   std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateInMemory();
 
   TrustTokenRequestRedemptionHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
       mojom::TrustTokenRefreshPolicy::kRefresh, store.get(),
       std::make_unique<FixedKeyCommitmentGetter>(),
       std::make_unique<FakeKeyPairGenerator>(),
@@ -662,12 +663,13 @@
 // return early with kAlreadyExists.
 TEST_F(TrustTokenRequestRedemptionHelperTest, RedemptionRecordCacheHit) {
   std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateInMemory();
-  store->SetRedemptionRecord(url::Origin::Create(GURL("https://issuer.com")),
-                             url::Origin::Create(GURL("https://toplevel.com")),
-                             SignedTrustTokenRedemptionRecord());
+  store->SetRedemptionRecord(
+      url::Origin::Create(GURL("https://issuer.com")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com")),
+      SignedTrustTokenRedemptionRecord());
 
   TrustTokenRequestRedemptionHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
       mojom::TrustTokenRefreshPolicy::kUseCached, store.get(),
       std::make_unique<FixedKeyCommitmentGetter>(),
       std::make_unique<FakeKeyPairGenerator>(),
@@ -697,9 +699,10 @@
   // commitment's key so that it does not get evicted from storage after the key
   // commitment is updated to reflect the key commitment result).
   std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateInMemory();
-  store->SetRedemptionRecord(url::Origin::Create(GURL("https://issuer.com")),
-                             url::Origin::Create(GURL("https://toplevel.com")),
-                             SignedTrustTokenRedemptionRecord());
+  store->SetRedemptionRecord(
+      url::Origin::Create(GURL("https://issuer.com")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com")),
+      SignedTrustTokenRedemptionRecord());
   store->AddTokens(url::Origin::Create(GURL("https://issuer.com/")),
                    std::vector<std::string>{"a token"},
                    /*key=*/"");
@@ -718,7 +721,7 @@
       .WillOnce(Return("a successfully-extracted SRR"));
 
   TrustTokenRequestRedemptionHelper helper(
-      url::Origin::Create(GURL("https://toplevel.com/")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
       mojom::TrustTokenRefreshPolicy::kRefresh, store.get(), std::move(getter),
       std::make_unique<MockKeyPairGenerator>("signing key", "verification key"),
       std::move(cryptographer));
@@ -747,7 +750,7 @@
   EXPECT_THAT(
       store->RetrieveNonstaleRedemptionRecord(
           url::Origin::Create(GURL("https://issuer.com/")),
-          url::Origin::Create(GURL("https://toplevel.com/"))),
+          *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/"))),
       Optional(AllOf(Property(&SignedTrustTokenRedemptionRecord::body,
                               "a successfully-extracted SRR"),
                      Property(&SignedTrustTokenRedemptionRecord::public_key,
@@ -756,4 +759,54 @@
                               "signing key"))));
 }
 
+TEST_F(TrustTokenRequestRedemptionHelperTest, RejectsUnsuitableInsecureIssuer) {
+  auto store = TrustTokenStore::CreateInMemory();
+  TrustTokenRequestRedemptionHelper helper(
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
+      mojom::TrustTokenRefreshPolicy::kUseCached, store.get(),
+      std::make_unique<FixedKeyCommitmentGetter>(),
+      std::make_unique<FakeKeyPairGenerator>(),
+      std::make_unique<MockCryptographer>());
+
+  auto request = MakeURLRequest("http://insecure-issuer.com/");
+
+  EXPECT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
+            mojom::TrustTokenOperationStatus::kInvalidArgument);
+}
+
+TEST_F(TrustTokenRequestRedemptionHelperTest,
+       RejectsUnsuitableNonHttpNonHttpsIssuer) {
+  auto store = TrustTokenStore::CreateInMemory();
+  TrustTokenRequestRedemptionHelper helper(
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
+      mojom::TrustTokenRefreshPolicy::kUseCached, store.get(),
+      std::make_unique<FixedKeyCommitmentGetter>(),
+      std::make_unique<FakeKeyPairGenerator>(),
+      std::make_unique<MockCryptographer>());
+
+  auto request = MakeURLRequest("file:///non-https-issuer.txt");
+
+  EXPECT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
+            mojom::TrustTokenOperationStatus::kInvalidArgument);
+}
+
+TEST_F(TrustTokenRequestRedemptionHelperTest, RequiresInitiatorForSrrRefresh) {
+  // Refresh mode "refresh" requires that the request's initiator to
+  // be same-origin with the request's issuer. Test that, in this case, the
+  // redemption helper requires that the request have an initiator.
+  auto store = TrustTokenStore::CreateInMemory();
+  TrustTokenRequestRedemptionHelper helper(
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
+      mojom::TrustTokenRefreshPolicy::kRefresh, store.get(),
+      std::make_unique<FixedKeyCommitmentGetter>(),
+      std::make_unique<FakeKeyPairGenerator>(),
+      std::make_unique<MockCryptographer>());
+
+  auto request = MakeURLRequest("https://issuer.example");
+  request->set_initiator(base::nullopt);
+
+  EXPECT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
+            mojom::TrustTokenOperationStatus::kFailedPrecondition);
+}
+
 }  // namespace network
diff --git a/services/network/trust_tokens/trust_token_request_signing_helper.cc b/services/network/trust_tokens/trust_token_request_signing_helper.cc
index 9ad4b27d..fa467275 100644
--- a/services/network/trust_tokens/trust_token_request_signing_helper.cc
+++ b/services/network/trust_tokens/trust_token_request_signing_helper.cc
@@ -180,36 +180,31 @@
     std::unique_ptr<Signer> signer,
     std::unique_ptr<TrustTokenRequestCanonicalizer> canonicalizer)
     : token_store_(token_store),
-      params_(params),
+      params_(std::move(params)),
       signer_(std::move(signer)),
-      canonicalizer_(std::move(canonicalizer)) {
-  DCHECK(params_.issuer.scheme() == url::kHttpsScheme ||
-         (params_.issuer.scheme() == url::kHttpScheme &&
-          IsOriginPotentiallyTrustworthy(params_.issuer)));
-  DCHECK(params_.toplevel.scheme() == url::kHttpsScheme ||
-         (params_.toplevel.scheme() == url::kHttpScheme &&
-          IsOriginPotentiallyTrustworthy(params_.toplevel)));
-}
+      canonicalizer_(std::move(canonicalizer)) {}
 
 TrustTokenRequestSigningHelper::~TrustTokenRequestSigningHelper() = default;
 
-Params::Params() = default;
+Params::Params(SuitableTrustTokenOrigin issuer,
+               SuitableTrustTokenOrigin toplevel)
+    : issuer(std::move(issuer)), toplevel(std::move(toplevel)) {}
 Params::~Params() = default;
 Params::Params(const Params&) = default;
 // The type alias causes a linter false positive.
 // NOLINTNEXTLINE(misc-unconventional-assign-operator)
 Params& Params::operator=(const Params&) = default;
+Params::Params(Params&&) = default;
+// NOLINTNEXTLINE(misc-unconventional-assign-operator)
+Params& Params::operator=(Params&&) = default;
 
 void TrustTokenRequestSigningHelper::Begin(
     net::URLRequest* request,
     base::OnceCallback<void(mojom::TrustTokenOperationStatus)> done) {
   DCHECK(request);
-  DCHECK(request->url().SchemeIsHTTPOrHTTPS() &&
-         IsUrlPotentiallyTrustworthy(request->url()));
-  DCHECK(request->initiator() &&
-             request->initiator()->scheme() == url::kHttpsScheme ||
-         (request->initiator()->scheme() == url::kHttpScheme &&
-          IsOriginPotentiallyTrustworthy(*request->initiator())));
+  DCHECK(!request->initiator() ||
+         IsOriginPotentiallyTrustworthy(*request->initiator()))
+      << *request->initiator();
 
   // This class is responsible for adding these headers; callers should not add
   // them.
diff --git a/services/network/trust_tokens/trust_token_request_signing_helper.h b/services/network/trust_tokens/trust_token_request_signing_helper.h
index b34ea46..463e05869 100644
--- a/services/network/trust_tokens/trust_token_request_signing_helper.h
+++ b/services/network/trust_tokens/trust_token_request_signing_helper.h
@@ -15,6 +15,7 @@
 #include "net/http/http_request_headers.h"
 #include "services/network/public/mojom/trust_tokens.mojom-shared.h"
 #include "services/network/public/mojom/url_response_head.mojom-forward.h"
+#include "services/network/trust_tokens/suitable_trust_token_origin.h"
 #include "services/network/trust_tokens/trust_token_request_helper.h"
 #include "url/origin.h"
 
@@ -69,11 +70,13 @@
       'T', 'r', 'u', 's', 't', ' ', 'T', 'o', 'k', 'e', 'n', ' ', 'v', '0'};
 
   struct Params {
-    Params();
+    Params(SuitableTrustTokenOrigin issuer, SuitableTrustTokenOrigin toplevel);
     ~Params();
 
     Params(const Params&);
     Params& operator=(const Params&);
+    Params(Params&&);
+    Params& operator=(Params&&);
 
     // |issuer| is the Trust Tokens issuer origin for which to retrieve a Signed
     // Redemption Record and matching signing key. This must be both (1) HTTP or
@@ -82,11 +85,11 @@
     //   1. HTTP or HTTPS so that the scheme serializes in a sensible manner in
     //   order to serve as a key for persisting state.
     //   2. potentially trustworthy origin to satisfy Web security requirements.
-    url::Origin issuer;
+    SuitableTrustTokenOrigin issuer;
 
     // |toplevel| is the top-level origin of the initiating request. This must
     // satisfy the same preconditions as |issuer|.
-    url::Origin toplevel;
+    SuitableTrustTokenOrigin toplevel;
 
     // |additional_headers_to_sign| is a list of headers to sign, in addition to
     // those specified by the request's Signed-Headers header. If these are not
diff --git a/services/network/trust_tokens/trust_token_request_signing_helper_unittest.cc b/services/network/trust_tokens/trust_token_request_signing_helper_unittest.cc
index 2f889602..40567a9 100644
--- a/services/network/trust_tokens/trust_token_request_signing_helper_unittest.cc
+++ b/services/network/trust_tokens/trust_token_request_signing_helper_unittest.cc
@@ -233,10 +233,10 @@
 TEST_F(TrustTokenRequestSigningHelperTest, WontSignIfNoRedemptionRecord) {
   std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateInMemory();
 
-  TrustTokenRequestSigningHelper::Params params;
+  TrustTokenRequestSigningHelper::Params params(
+      *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com")));
   params.sign_request_data = mojom::TrustTokenSignRequestData::kHeadersOnly;
-  params.issuer = url::Origin::Create(GURL("https://issuer.com"));
-  params.toplevel = url::Origin::Create(GURL("https://toplevel.com"));
 
   TrustTokenRequestSigningHelper helper(
       store.get(), std::move(params), std::make_unique<FakeSigner>(),
@@ -261,10 +261,10 @@
 
   std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateInMemory();
 
-  TrustTokenRequestSigningHelper::Params params;
+  TrustTokenRequestSigningHelper::Params params(
+      *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com")));
   params.sign_request_data = mojom::TrustTokenSignRequestData::kHeadersOnly;
-  params.issuer = url::Origin::Create(GURL("https://issuer.com"));
-  params.toplevel = url::Origin::Create(GURL("https://toplevel.com"));
   params.additional_headers_to_sign = std::vector<std::string>{"Sec-Time"};
 
   SignedTrustTokenRedemptionRecord my_record;
@@ -306,10 +306,10 @@
 
   std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateInMemory();
 
-  TrustTokenRequestSigningHelper::Params params;
+  TrustTokenRequestSigningHelper::Params params(
+      *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com")));
   params.sign_request_data = mojom::TrustTokenSignRequestData::kHeadersOnly;
-  params.issuer = url::Origin::Create(GURL("https://issuer.com"));
-  params.toplevel = url::Origin::Create(GURL("https://toplevel.com"));
 
   SignedTrustTokenRedemptionRecord my_record;
   my_record.set_public_key("key");
@@ -345,10 +345,10 @@
 
   std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateInMemory();
 
-  TrustTokenRequestSigningHelper::Params params;
+  TrustTokenRequestSigningHelper::Params params(
+      *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com")));
   params.sign_request_data = mojom::TrustTokenSignRequestData::kHeadersOnly;
-  params.issuer = url::Origin::Create(GURL("https://issuer.com"));
-  params.toplevel = url::Origin::Create(GURL("https://toplevel.com"));
   params.additional_headers_to_sign = std::vector<std::string>{
       "this header name is definitely not in "
       "TrustTokenRequestSigningHelper::kSignableRequestHeaders"};
@@ -383,11 +383,11 @@
 TEST_F(TrustTokenRequestSigningHelperTestWithMockTime, ProvidesTimeHeader) {
   std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateInMemory();
 
-  TrustTokenRequestSigningHelper::Params params;
+  TrustTokenRequestSigningHelper::Params params(
+      *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com")));
   params.sign_request_data = mojom::TrustTokenSignRequestData::kHeadersOnly;
   params.should_add_timestamp = true;
-  params.issuer = url::Origin::Create(GURL("https://issuer.com"));
-  params.toplevel = url::Origin::Create(GURL("https://toplevel.com"));
 
   SignedTrustTokenRedemptionRecord my_record;
   my_record.set_public_key("key");
@@ -413,10 +413,9 @@
        RedemptionRecordAttachmentWithoutSigning) {
   std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateInMemory();
 
-  TrustTokenRequestSigningHelper::Params params;
-  params.sign_request_data = mojom::TrustTokenSignRequestData::kHeadersOnly;
-  params.issuer = url::Origin::Create(GURL("https://issuer.com"));
-  params.toplevel = url::Origin::Create(GURL("https://toplevel.com"));
+  TrustTokenRequestSigningHelper::Params params(
+      *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com")));
   params.should_add_timestamp = true;
   params.sign_request_data = mojom::TrustTokenSignRequestData::kOmit;
 
@@ -446,10 +445,10 @@
 TEST_F(TrustTokenRequestSigningHelperTest, SignAndVerifyMinimal) {
   std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateInMemory();
 
-  TrustTokenRequestSigningHelper::Params params;
+  TrustTokenRequestSigningHelper::Params params(
+      *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com")));
   params.sign_request_data = mojom::TrustTokenSignRequestData::kHeadersOnly;
-  params.issuer = url::Origin::Create(GURL("https://issuer.com"));
-  params.toplevel = url::Origin::Create(GURL("https://toplevel.com"));
 
   SignedTrustTokenRedemptionRecord my_record;
   my_record.set_public_key("key");
@@ -482,10 +481,10 @@
 TEST_F(TrustTokenRequestSigningHelperTest, SignAndVerifyWithHeaders) {
   std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateInMemory();
 
-  TrustTokenRequestSigningHelper::Params params;
+  TrustTokenRequestSigningHelper::Params params(
+      *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com")));
   params.sign_request_data = mojom::TrustTokenSignRequestData::kHeadersOnly;
-  params.issuer = url::Origin::Create(GURL("https://issuer.com"));
-  params.toplevel = url::Origin::Create(GURL("https://toplevel.com"));
   SignedTrustTokenRedemptionRecord record;
   record.set_body("I am a signed token redemption record");
   record.set_public_key("key");
@@ -515,10 +514,10 @@
 TEST_F(TrustTokenRequestSigningHelperTest, SignAndVerifyTimestampHeader) {
   std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateInMemory();
 
-  TrustTokenRequestSigningHelper::Params params;
+  TrustTokenRequestSigningHelper::Params params(
+      *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com")));
   params.sign_request_data = mojom::TrustTokenSignRequestData::kHeadersOnly;
-  params.issuer = url::Origin::Create(GURL("https://issuer.com"));
-  params.toplevel = url::Origin::Create(GURL("https://toplevel.com"));
   params.additional_headers_to_sign = std::vector<std::string>{"sec-time"};
   params.should_add_timestamp = true;
 
@@ -558,9 +557,9 @@
        SignAndVerifyWithHeadersAndDestinationUrl) {
   std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateInMemory();
 
-  TrustTokenRequestSigningHelper::Params params;
-  params.issuer = url::Origin::Create(GURL("https://issuer.com"));
-  params.toplevel = url::Origin::Create(GURL("https://toplevel.com"));
+  TrustTokenRequestSigningHelper::Params params(
+      *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com")));
   params.sign_request_data = mojom::TrustTokenSignRequestData::kInclude;
 
   SignedTrustTokenRedemptionRecord record;
@@ -608,10 +607,10 @@
 TEST_F(TrustTokenRequestSigningHelperTest, CatchesSignatureFailure) {
   std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateInMemory();
 
-  TrustTokenRequestSigningHelper::Params params;
+  TrustTokenRequestSigningHelper::Params params(
+      *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
+      *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com")));
   params.sign_request_data = mojom::TrustTokenSignRequestData::kHeadersOnly;
-  params.issuer = url::Origin::Create(GURL("https://issuer.com"));
-  params.toplevel = url::Origin::Create(GURL("https://toplevel.com"));
 
   SignedTrustTokenRedemptionRecord my_record;
   my_record.set_public_key("key");
diff --git a/services/viz/public/cpp/compositing/quads_mojom_traits.h b/services/viz/public/cpp/compositing/quads_mojom_traits.h
index 8054def..ea4d147 100644
--- a/services/viz/public/cpp/compositing/quads_mojom_traits.h
+++ b/services/viz/public/cpp/compositing/quads_mojom_traits.h
@@ -544,13 +544,11 @@
 template <>
 struct StructTraits<viz::mojom::DrawQuadDataView, DrawQuadWithSharedQuadState> {
   static const gfx::Rect& rect(const DrawQuadWithSharedQuadState& input) {
-    DCHECK(input.quad->rect.size().GetCheckedArea().IsValid());
     return input.quad->rect;
   }
 
   static const gfx::Rect& visible_rect(
       const DrawQuadWithSharedQuadState& input) {
-    DCHECK(input.quad->rect.Contains(input.quad->visible_rect));
     return input.quad->visible_rect;
   }
 
diff --git a/testing/buildbot/chromium.perf.fyi.json b/testing/buildbot/chromium.perf.fyi.json
index 8e5cf9a..709fdad5 100644
--- a/testing/buildbot/chromium.perf.fyi.json
+++ b/testing/buildbot/chromium.perf.fyi.json
@@ -203,6 +203,9 @@
         ],
         "isolate_name": "performance_test_suite",
         "merge": {
+          "args": [
+            "--skip-perf"
+          ],
           "script": "//tools/perf/process_perf_results.py"
         },
         "name": "performance_test_suite",
diff --git a/testing/buildbot/filters/android.emulator.chrome_public_test_apk.filter b/testing/buildbot/filters/android.emulator.chrome_public_test_apk.filter
index 0673a30..eafdee9 100644
--- a/testing/buildbot/filters/android.emulator.chrome_public_test_apk.filter
+++ b/testing/buildbot/filters/android.emulator.chrome_public_test_apk.filter
@@ -98,7 +98,7 @@
 # crbug.com/1062838
 -org.chromium.chrome.browser.offlinepages.prefetch.PrefetchFeedFlowTest.testPrefetchForbiddenByServer_FullBrowser
 -org.chromium.chrome.browser.offlinepages.prefetch.PrefetchFeedFlowTest.testPrefetchSinglePageSuccess_FullBrowser
-
+-org.chromium.chrome.browser.offlinepages.prefetch.PrefetchFeedFlowTest.testPrefetchPageReadyLater_FullBrowser
 
 # crbug.com/1062843
 -org.chromium.chrome.browser.banners.AppBannerManagerTest.testAppInstalledModalNativeAppBannerCustomTab
@@ -127,3 +127,6 @@
 
 # crbug.com/1064058
 -org.chromium.chrome.features.start_surface.StartSurfaceLayoutTest.testRecycling_aspectRatioPoint75
+
+# crbug.com/1067673
+-org.chromium.chrome.browser.ntp.cards.NewTabPageRecyclerViewTest.testDismissArticleWithContextMenu
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index fe3e39da..96389e9 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -2186,6 +2186,7 @@
                     "params": {
                         "editdistance": "false",
                         "editdistance_siteengagement": "true",
+                        "targetembedding": "false",
                         "topsites": "true"
                     },
                     "enable_features": [
diff --git a/third_party/blink/PRESUBMIT.py b/third_party/blink/PRESUBMIT.py
index 288b60c..55af482 100644
--- a/third_party/blink/PRESUBMIT.py
+++ b/third_party/blink/PRESUBMIT.py
@@ -30,6 +30,7 @@
     r'^third_party[\\/]blink[\\/]tools[\\/]blinkpy[\\/]third_party[\\/]wpt[\\/]wpt[\\/].*',
     r'^third_party[\\/]blink[\\/]web_tests[\\/]external[\\/]wpt[\\/]tools[\\/].*',
     r'^third_party[\\/]blink[\\/]web_tests[\\/]external[\\/]wpt[\\/]resources[\\/]webidl2[\\/].*',
+    r'^third_party[\\/]blink[\\/]web_tests[\\/]resources[\\/]webidl2.js',
 )
 
 
diff --git a/third_party/blink/renderer/core/animation/compositor_animations_test.cc b/third_party/blink/renderer/core/animation/compositor_animations_test.cc
index 28975a1c..b9e87ddf 100644
--- a/third_party/blink/renderer/core/animation/compositor_animations_test.cc
+++ b/third_party/blink/renderer/core/animation/compositor_animations_test.cc
@@ -2021,7 +2021,7 @@
   const cc::PictureLayer* layer =
       composited_layer_mapping->MainGraphicsLayer()->CcLayer();
   ASSERT_NE(nullptr, layer);
-  EXPECT_TRUE(layer->double_sided());
+  EXPECT_FALSE(layer->should_check_backface_visibility());
 
   // Change the backface visibility, while the compositor animation is
   // happening.
@@ -2030,7 +2030,7 @@
   // Make sure the setting made it to both blink and all the way to CC.
   EXPECT_EQ(transform->GetBackfaceVisibilityForTesting(),
             TransformPaintPropertyNode::BackfaceVisibility::kHidden);
-  EXPECT_FALSE(layer->double_sided())
+  EXPECT_TRUE(layer->should_check_backface_visibility())
       << "Change to hidden did not get propagated to CC";
   // Make sure the animation state is initialized in paint properties after
   // blink pushing new paint properties without animation state change.
diff --git a/third_party/blink/renderer/core/animation/css_interpolation_environment.cc b/third_party/blink/renderer/core/animation/css_interpolation_environment.cc
index bb8bb38f..237c8b1a 100644
--- a/third_party/blink/renderer/core/animation/css_interpolation_environment.cc
+++ b/third_party/blink/renderer/core/animation/css_interpolation_environment.cc
@@ -19,7 +19,7 @@
   if (!value)
     return value;
   return cascade_->Resolve(property.GetCSSPropertyName(), *value,
-                           *cascade_resolver_);
+                           CascadeOrigin::kAnimation, *cascade_resolver_);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/css_custom_property_declaration.h b/third_party/blink/renderer/core/css/css_custom_property_declaration.h
index b870b5b..6a92125 100644
--- a/third_party/blink/renderer/core/css/css_custom_property_declaration.h
+++ b/third_party/blink/renderer/core/css/css_custom_property_declaration.h
@@ -43,6 +43,7 @@
     return value_id_ == CSSValueID::kInitial ||
            (!is_inherited_property && value_id_ == CSSValueID::kUnset);
   }
+  bool IsRevert() const { return value_id_ == CSSValueID::kRevert; }
 
   String CustomCSSText() const;
 
diff --git a/third_party/blink/renderer/core/css/properties/longhands/custom_property.cc b/third_party/blink/renderer/core/css/properties/longhands/custom_property.cc
index 8cb1be64..bf022a5a 100644
--- a/third_party/blink/renderer/core/css/properties/longhands/custom_property.cc
+++ b/third_party/blink/renderer/core/css/properties/longhands/custom_property.cc
@@ -79,6 +79,7 @@
 
   const auto& declaration = To<CSSCustomPropertyDeclaration>(value);
 
+  DCHECK(!value.IsRevertValue());
   bool is_inherited_property = IsInherited();
   bool initial = declaration.IsInitial(is_inherited_property);
   bool inherit = declaration.IsInherit(is_inherited_property);
diff --git a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
index 700ce58f..14fba58f 100644
--- a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
+++ b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
@@ -7931,6 +7931,7 @@
         case CSSValueID::kDefault:
         case CSSValueID::kInitial:
         case CSSValueID::kInherit:
+        case CSSValueID::kRevert:
           return nullptr;
         case CSSValueID::kContents:
         case CSSValueID::kScrollPosition:
diff --git a/third_party/blink/renderer/core/css/resolver/cascade_map.cc b/third_party/blink/renderer/core/css/resolver/cascade_map.cc
index bfe2770..c13a8c5 100644
--- a/third_party/blink/renderer/core/css/resolver/cascade_map.cc
+++ b/third_party/blink/renderer/core/css/resolver/cascade_map.cc
@@ -7,39 +7,116 @@
 
 namespace blink {
 
-CascadePriority CascadeMap::At(const CSSPropertyName& name) const {
-  if (name.IsCustomProperty())
-    return custom_properties_.at(name);
+static_assert(
+    std::is_trivially_destructible<CascadePriority>::value,
+    "~CascadePriority is never called on CascadePriority objects created here");
+
+namespace {
+
+inline void AddCustom(const CSSPropertyName& name,
+                      CascadePriority priority,
+                      CascadeMap::CustomMap& map) {
+  auto result = map.insert(name, priority);
+  if (result.is_new_entry || result.stored_value->value < priority)
+    result.stored_value->value = priority;
+}
+
+inline void AddNative(size_t index,
+                      CascadePriority priority,
+                      CascadeMap::NativeMap& map) {
+  CascadePriority* p = map.Buffer() + index;
+  if (!map.Bits().test(index) || *p < priority) {
+    map.Bits().set(index);
+    new (p) CascadePriority(priority);
+  }
+}
+
+inline CascadePriority* FindCustom(const CSSPropertyName& name,
+                                   CascadeMap::CustomMap& map) {
+  auto iter = map.find(name);
+  if (iter != map.end())
+    return &iter->value;
+  return nullptr;
+}
+
+inline CascadePriority* FindNative(const CSSPropertyName& name,
+                                   CascadeMap::NativeMap& map) {
   size_t index = static_cast<size_t>(name.Id());
   DCHECK_LT(index, static_cast<size_t>(numCSSProperties));
-  return native_property_bits_.test(index)
-             ? reinterpret_cast<const CascadePriority*>(
-                   native_properties_)[index]
-             : CascadePriority();
+  return map.Bits().test(index) ? (map.Buffer() + index) : nullptr;
+}
+
+inline CascadePriority AtCustom(const CSSPropertyName& name,
+                                const CascadeMap::CustomMap& map) {
+  return map.at(name);
+}
+
+inline CascadePriority AtNative(const CSSPropertyName& name,
+                                const CascadeMap::NativeMap& map) {
+  size_t index = static_cast<size_t>(name.Id());
+  DCHECK_LT(index, static_cast<size_t>(numCSSProperties));
+  return map.Bits().test(index) ? map.Buffer()[index] : CascadePriority();
+}
+
+}  // namespace
+
+CascadePriority CascadeMap::At(const CSSPropertyName& name) const {
+  if (name.IsCustomProperty())
+    return AtCustom(name, custom_properties_);
+  return AtNative(name, native_properties_);
+}
+
+CascadePriority CascadeMap::At(const CSSPropertyName& name,
+                               CascadeOrigin origin) const {
+  if (name.IsCustomProperty()) {
+    if (origin <= CascadeOrigin::kUserAgent)
+      return CascadePriority();
+    if (origin <= CascadeOrigin::kUser)
+      return AtCustom(name, custom_user_properties_);
+    return AtCustom(name, custom_properties_);
+  }
+
+  if (origin <= CascadeOrigin::kUserAgent)
+    return AtNative(name, native_ua_properties_);
+  if (origin <= CascadeOrigin::kUser)
+    return AtNative(name, native_user_properties_);
+  return AtNative(name, native_properties_);
 }
 
 CascadePriority* CascadeMap::Find(const CSSPropertyName& name) {
+  if (name.IsCustomProperty())
+    return FindCustom(name, custom_properties_);
+  return FindNative(name, native_properties_);
+}
+
+CascadePriority* CascadeMap::Find(const CSSPropertyName& name,
+                                  CascadeOrigin origin) {
   if (name.IsCustomProperty()) {
-    auto iter = custom_properties_.find(name);
-    if (iter != custom_properties_.end())
-      return &iter->value;
-    return nullptr;
+    if (origin <= CascadeOrigin::kUserAgent)
+      return nullptr;
+    if (origin <= CascadeOrigin::kUser)
+      return FindCustom(name, custom_user_properties_);
+    return FindCustom(name, custom_properties_);
   }
-  size_t index = static_cast<size_t>(name.Id());
-  DCHECK_LT(index, static_cast<size_t>(numCSSProperties));
-  if (!native_property_bits_.test(index))
-    return nullptr;
-  return reinterpret_cast<CascadePriority*>(native_properties_) + index;
+
+  if (origin <= CascadeOrigin::kUserAgent)
+    return FindNative(name, native_ua_properties_);
+  if (origin <= CascadeOrigin::kUser)
+    return FindNative(name, native_user_properties_);
+  return FindNative(name, native_properties_);
 }
 
 void CascadeMap::Add(const CSSPropertyName& name, CascadePriority priority) {
+  CascadeOrigin origin = priority.GetOrigin();
+
   if (name.IsCustomProperty()) {
-    DCHECK_NE(CascadeOrigin::kUserAgent, priority.GetOrigin());
-    auto result = custom_properties_.insert(name, priority);
-    if (result.is_new_entry || result.stored_value->value < priority)
-      result.stored_value->value = priority;
+    DCHECK_NE(CascadeOrigin::kUserAgent, origin);
+    if (origin <= CascadeOrigin::kUser)
+      AddCustom(name, priority, custom_user_properties_);
+    AddCustom(name, priority, custom_properties_);
     return;
   }
+
   CSSPropertyID id = name.Id();
   size_t index = static_cast<size_t>(id);
   DCHECK_LT(index, static_cast<size_t>(numCSSProperties));
@@ -50,21 +127,21 @@
                 "CascadeMap supports at most 63 high-priority properties");
   if (HighPriority::PropertyHasPriority(id))
     high_priority_ |= (1ull << index);
-  CascadePriority* p =
-      reinterpret_cast<CascadePriority*>(native_properties_) + index;
-  if (!native_property_bits_.test(index) || *p < priority) {
-    native_property_bits_.set(index);
-    static_assert(
-        std::is_trivially_destructible<CascadePriority>::value,
-        "~CascadePriority is never called on these CascadePriority objects");
-    new (p) CascadePriority(priority);
-  }
+
+  if (origin <= CascadeOrigin::kUserAgent)
+    AddNative(index, priority, native_ua_properties_);
+  if (origin <= CascadeOrigin::kUser)
+    AddNative(index, priority, native_user_properties_);
+  AddNative(index, priority, native_properties_);
 }
 
 void CascadeMap::Reset() {
   high_priority_ = 0;
-  native_property_bits_.reset();
+  native_properties_.Bits().reset();
+  native_ua_properties_.Bits().reset();
+  native_user_properties_.Bits().reset();
   custom_properties_.clear();
+  custom_user_properties_.clear();
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/resolver/cascade_map.h b/third_party/blink/renderer/core/css/resolver/cascade_map.h
index 249ebcb..edf8e52 100644
--- a/third_party/blink/renderer/core/css/resolver/cascade_map.h
+++ b/third_party/blink/renderer/core/css/resolver/cascade_map.h
@@ -23,15 +23,19 @@
 
  public:
   // Get the CascadePriority for the given CSSPropertyName. If there is no
-  // entry for the given name, CascadePriority() is returned.
+  // entry for the given name, CascadePriority() is returned. If a CascadeOrigin
+  // is provided, returns the CascadePriority for that origin.
   CascadePriority At(const CSSPropertyName&) const;
+  CascadePriority At(const CSSPropertyName&, CascadeOrigin) const;
   // Find the CascadePriority location for a given name, if present. If there
-  // is no entry for the given name, nullptr is returned.
+  // is no entry for the given name, nullptr is returned. If a CascadeOrigin
+  // is provided, returns the CascadePriority for that origin.
   //
   // Note that the returned pointer may accessed to change the stored value.
   //
   // Note also that calling Add() invalidates the pointer.
   CascadePriority* Find(const CSSPropertyName&);
+  CascadePriority* Find(const CSSPropertyName&, CascadeOrigin);
   // Adds an an entry to the map if the incoming priority is greater than or
   // equal to the current priority for the same name.
   void Add(const CSSPropertyName&, CascadePriority);
@@ -42,15 +46,38 @@
   // Remove all properties (both native and custom) from the CascadeMap.
   void Reset();
 
+  class NativeMap {
+    STACK_ALLOCATED();
+
+   public:
+    std::bitset<numCSSProperties>& Bits() { return bits_; }
+    const std::bitset<numCSSProperties>& Bits() const { return bits_; }
+
+    CascadePriority* Buffer() {
+      return reinterpret_cast<CascadePriority*>(properties_);
+    }
+    const CascadePriority* Buffer() const {
+      return reinterpret_cast<const CascadePriority*>(properties_);
+    }
+
+   private:
+    // For performance reasons, a char-array is used to prevent construction of
+    // CascadePriority objects. A companion std::bitset keeps track of which
+    // properties are initialized.
+    std::bitset<numCSSProperties> bits_;
+    alignas(CascadePriority) char properties_[numCSSProperties *
+                                              sizeof(CascadePriority)];
+  };
+
+  using CustomMap = HashMap<CSSPropertyName, CascadePriority>;
+
  private:
   uint64_t high_priority_ = 0;
-  // For performance reasons, a char-array is used to prevent construction of
-  // CascadePriority objects. A companion std::bitset keeps track of which
-  // properties are initialized.
-  std::bitset<numCSSProperties> native_property_bits_;
-  alignas(CascadePriority) char native_properties_[numCSSProperties *
-                                                   sizeof(CascadePriority)];
-  HashMap<CSSPropertyName, CascadePriority> custom_properties_;
+  NativeMap native_properties_;
+  NativeMap native_ua_properties_;
+  NativeMap native_user_properties_;
+  CustomMap custom_properties_;
+  CustomMap custom_user_properties_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/resolver/cascade_map_test.cc b/third_party/blink/renderer/core/css/resolver/cascade_map_test.cc
index 0266bf9..4a7176f 100644
--- a/third_party/blink/renderer/core/css/resolver/cascade_map_test.cc
+++ b/third_party/blink/renderer/core/css/resolver/cascade_map_test.cc
@@ -12,6 +12,15 @@
 namespace blink {
 
 namespace {
+CascadePriority UaPriority(size_t position) {
+  return CascadePriority(CascadeOrigin::kUserAgent, false, 0, position);
+}
+CascadePriority UserPriority(size_t position) {
+  return CascadePriority(CascadeOrigin::kUser, false, 0, position);
+}
+CascadePriority AuthorPriority(size_t position) {
+  return CascadePriority(CascadeOrigin::kAuthor, false, 0, position);
+}
 
 bool AddTo(CascadeMap& map,
            const CSSPropertyName& name,
@@ -227,4 +236,53 @@
   EXPECT_FALSE(map.HighPriorityBits());
 }
 
+TEST(CascadeMapTest, FindOrigin) {
+  CascadeMap map;
+
+  CSSPropertyName color(CSSPropertyID::kColor);
+  CSSPropertyName display(CSSPropertyID::kDisplay);
+  CSSPropertyName top(CSSPropertyID::kTop);
+  CSSPropertyName left(CSSPropertyID::kLeft);
+  CSSPropertyName right(CSSPropertyID::kRight);
+  CSSPropertyName bottom(CSSPropertyID::kBottom);
+
+  map.Add(color, UaPriority(1));
+  map.Add(display, UaPriority(2));
+  map.Add(top, UaPriority(3));
+  map.Add(left, UaPriority(4));
+  map.Add(right, UaPriority(5));
+
+  map.Add(display, UserPriority(10));
+  map.Add(right, UserPriority(11));
+
+  map.Add(color, AuthorPriority(20));
+  map.Add(display, AuthorPriority(21));
+  map.Add(top, AuthorPriority(22));
+  map.Add(bottom, AuthorPriority(23));
+
+  // Final result of the cascade:
+  EXPECT_EQ(AuthorPriority(20), *map.Find(color));
+  EXPECT_EQ(AuthorPriority(21), *map.Find(display));
+  EXPECT_EQ(AuthorPriority(22), *map.Find(top));
+  EXPECT_EQ(UaPriority(4), *map.Find(left));
+  EXPECT_EQ(UserPriority(11), *map.Find(right));
+  EXPECT_EQ(AuthorPriority(23), *map.Find(bottom));
+
+  // Final result up to and including kUser:
+  EXPECT_EQ(UaPriority(1), *map.Find(color, CascadeOrigin::kUser));
+  EXPECT_EQ(UserPriority(10), *map.Find(display, CascadeOrigin::kUser));
+  EXPECT_EQ(UaPriority(3), *map.Find(top, CascadeOrigin::kUser));
+  EXPECT_EQ(UaPriority(4), *map.Find(left, CascadeOrigin::kUser));
+  EXPECT_EQ(UserPriority(11), *map.Find(right, CascadeOrigin::kUser));
+  EXPECT_FALSE(map.Find(bottom, CascadeOrigin::kUser));
+
+  // Final result up to and including kUserAgent:
+  EXPECT_EQ(UaPriority(1), *map.Find(color, CascadeOrigin::kUserAgent));
+  EXPECT_EQ(UaPriority(2), *map.Find(display, CascadeOrigin::kUserAgent));
+  EXPECT_EQ(UaPriority(3), *map.Find(top, CascadeOrigin::kUserAgent));
+  EXPECT_EQ(UaPriority(4), *map.Find(left, CascadeOrigin::kUserAgent));
+  EXPECT_EQ(UaPriority(5), *map.Find(right, CascadeOrigin::kUserAgent));
+  EXPECT_FALSE(map.Find(bottom, CascadeOrigin::kUserAgent));
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/resolver/style_cascade.cc b/third_party/blink/renderer/core/css/resolver/style_cascade.cc
index 8b06d79c..dcb942b 100644
--- a/third_party/blink/renderer/core/css/resolver/style_cascade.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_cascade.cc
@@ -95,6 +95,23 @@
   return PropertyHandle(property, DecodeIsPresentationAttribute(position));
 }
 
+// https://drafts.csswg.org/css-cascade-4/#default
+CascadeOrigin TargetOriginForRevert(CascadeOrigin origin) {
+  switch (origin) {
+    case CascadeOrigin::kNone:
+    case CascadeOrigin::kTransition:
+      NOTREACHED();
+      return CascadeOrigin::kNone;
+    case CascadeOrigin::kUserAgent:
+      return CascadeOrigin::kNone;
+    case CascadeOrigin::kUser:
+      return CascadeOrigin::kUserAgent;
+    case CascadeOrigin::kAuthor:
+    case CascadeOrigin::kAnimation:
+      return CascadeOrigin::kUser;
+  }
+}
+
 }  // namespace
 
 MatchResult& StyleCascade::MutableMatchResult() {
@@ -146,10 +163,12 @@
 
 const CSSValue* StyleCascade::Resolve(const CSSPropertyName& name,
                                       const CSSValue& value,
+                                      CascadeOrigin origin,
                                       CascadeResolver& resolver) {
   CSSPropertyRef ref(name, state_.GetDocument());
 
-  const CSSValue* resolved = Resolve(ref.GetProperty(), value, resolver);
+  const CSSValue* resolved =
+      Resolve(ref.GetProperty(), value, origin, resolver);
 
   DCHECK(resolved);
 
@@ -272,7 +291,8 @@
         ApplySurrogate(property, priority, resolver);
         continue;
       }
-      const CSSValue* value = Resolve(property, e.Value(), resolver);
+      CascadeOrigin origin = priority.GetOrigin();
+      const CSSValue* value = Resolve(property, e.Value(), origin, resolver);
       StyleBuilder::ApplyProperty(property, state_, *value);
     }
   }
@@ -426,7 +446,7 @@
   DCHECK(priority.GetOrigin() < CascadeOrigin::kAnimation);
   const CSSValue* value = ValueAt(match_result_, priority.GetPosition());
   DCHECK(value);
-  value = Resolve(property, *value, resolver);
+  value = Resolve(property, *value, priority.GetOrigin(), resolver);
   DCHECK(!value->IsVariableReferenceValue());
   DCHECK(!value->IsPendingSubstitutionValue());
   StyleBuilder::ApplyProperty(property, state_, *value);
@@ -494,30 +514,34 @@
 
 const CSSValue* StyleCascade::Resolve(const CSSProperty& property,
                                       const CSSValue& value,
+                                      CascadeOrigin origin,
                                       CascadeResolver& resolver) {
   if (const auto* v = DynamicTo<CSSCustomPropertyDeclaration>(value))
-    return ResolveCustomProperty(property, *v, resolver);
+    return ResolveCustomProperty(property, *v, origin, resolver);
   if (const auto* v = DynamicTo<CSSVariableReferenceValue>(value))
     return ResolveVariableReference(property, *v, resolver);
   if (const auto* v = DynamicTo<cssvalue::CSSPendingSubstitutionValue>(value))
     return ResolvePendingSubstitution(property, *v, resolver);
-  // TODO(andruud): Actually revert instead of treating as unset.
   if (value.IsRevertValue())
-    return cssvalue::CSSUnsetValue::Create();
+    return ResolveRevert(property, origin, resolver);
   return &value;
 }
 
 const CSSValue* StyleCascade::ResolveCustomProperty(
     const CSSProperty& property,
     const CSSCustomPropertyDeclaration& decl,
+    CascadeOrigin origin,
     CascadeResolver& resolver) {
+  // TODO(andruud): Don't transport css-wide keywords in this value.
+  if (!decl.Value()) {
+    if (decl.IsRevert())
+      return ResolveRevert(property, origin, resolver);
+    return &decl;
+  }
+
   DCHECK(!resolver.IsLocked(property));
   CascadeResolver::AutoLock lock(property, resolver);
 
-  // TODO(andruud): Don't transport css-wide keywords in this value.
-  if (!decl.Value())
-    return &decl;
-
   scoped_refptr<CSSVariableData> data = decl.Value();
 
   if (data->NeedsVariableResolution())
@@ -628,6 +652,29 @@
   return cssvalue::CSSUnsetValue::Create();
 }
 
+const CSSValue* StyleCascade::ResolveRevert(const CSSProperty& property,
+                                            CascadeOrigin origin,
+                                            CascadeResolver& resolver) {
+  CascadeOrigin target_origin = TargetOriginForRevert(origin);
+
+  switch (target_origin) {
+    case CascadeOrigin::kTransition:
+    case CascadeOrigin::kNone:
+      return cssvalue::CSSUnsetValue::Create();
+    case CascadeOrigin::kUserAgent:
+    case CascadeOrigin::kUser:
+    case CascadeOrigin::kAuthor:
+    case CascadeOrigin::kAnimation: {
+      CascadePriority* p =
+          map_.Find(property.GetCSSPropertyName(), target_origin);
+      if (!p)
+        return cssvalue::CSSUnsetValue::Create();
+      return Resolve(property, *ValueAt(match_result_, p->GetPosition()),
+                     target_origin, resolver);
+    }
+  }
+}
+
 scoped_refptr<CSSVariableData> StyleCascade::ResolveVariableData(
     CSSVariableData* data,
     CascadeResolver& resolver) {
diff --git a/third_party/blink/renderer/core/css/resolver/style_cascade.h b/third_party/blink/renderer/core/css/resolver/style_cascade.h
index 5b4fe46..9a5ab349 100644
--- a/third_party/blink/renderer/core/css/resolver/style_cascade.h
+++ b/third_party/blink/renderer/core/css/resolver/style_cascade.h
@@ -102,6 +102,7 @@
   // See documentation the other Resolve* functions for what resolve means.
   const CSSValue* Resolve(const CSSPropertyName&,
                           const CSSValue&,
+                          CascadeOrigin,
                           CascadeResolver&);
 
  private:
@@ -245,9 +246,11 @@
 
   const CSSValue* Resolve(const CSSProperty&,
                           const CSSValue&,
+                          CascadeOrigin,
                           CascadeResolver&);
   const CSSValue* ResolveCustomProperty(const CSSProperty&,
                                         const CSSCustomPropertyDeclaration&,
+                                        CascadeOrigin,
                                         CascadeResolver&);
   const CSSValue* ResolveVariableReference(const CSSProperty&,
                                            const CSSVariableReferenceValue&,
@@ -255,6 +258,9 @@
   const CSSValue* ResolvePendingSubstitution(const CSSProperty&,
                                              const CSSPendingSubstitutionValue&,
                                              CascadeResolver&);
+  const CSSValue* ResolveRevert(const CSSProperty&,
+                                CascadeOrigin,
+                                CascadeResolver&);
 
   scoped_refptr<CSSVariableData> ResolveVariableData(CSSVariableData*,
                                                      CascadeResolver&);
diff --git a/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc b/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc
index 87269c3..a0f5268 100644
--- a/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc
@@ -245,9 +245,12 @@
   CascadeResolver::AutoLock lock_;
 };
 
-class StyleCascadeTest : public PageTestBase, private ScopedCSSCascadeForTest {
+class StyleCascadeTest : public PageTestBase,
+                         private ScopedCSSCascadeForTest,
+                         private ScopedCSSRevertForTest {
  public:
-  StyleCascadeTest() : ScopedCSSCascadeForTest(true) {}
+  StyleCascadeTest()
+      : ScopedCSSCascadeForTest(true), ScopedCSSRevertForTest(true) {}
 
   CSSStyleSheet* CreateSheet(const String& css_text) {
     auto* init = MakeGarbageCollected<CSSStyleSheetInit>();
@@ -1194,6 +1197,153 @@
   EXPECT_EQ("foo", cascade.ComputedValue("--x"));
 }
 
+TEST_F(StyleCascadeTest, RevertUA) {
+  TestCascade cascade(GetDocument());
+  cascade.Add("display:block", CascadeOrigin::kUserAgent);
+  cascade.Add("display:revert", CascadeOrigin::kUserAgent);
+
+  cascade.Add("display:block", CascadeOrigin::kUser);
+  cascade.Add("display:revert", CascadeOrigin::kUser);
+
+  cascade.Add("display:block", CascadeOrigin::kAuthor);
+  cascade.Add("display:revert", CascadeOrigin::kAuthor);
+
+  cascade.Apply();
+
+  EXPECT_EQ("inline", cascade.ComputedValue("display"));
+}
+
+TEST_F(StyleCascadeTest, RevertStandardProperty) {
+  TestCascade cascade(GetDocument());
+  cascade.Add("left:10px", CascadeOrigin::kUserAgent);
+  cascade.Add("right:10px", CascadeOrigin::kUserAgent);
+
+  cascade.Add("right:20px", CascadeOrigin::kUser);
+  cascade.Add("right:revert", CascadeOrigin::kUser);
+  cascade.Add("top:20px", CascadeOrigin::kUser);
+  cascade.Add("bottom:20px", CascadeOrigin::kUser);
+
+  cascade.Add("bottom:30px", CascadeOrigin::kAuthor);
+  cascade.Add("bottom:revert", CascadeOrigin::kAuthor);
+  cascade.Add("left:30px", CascadeOrigin::kAuthor);
+  cascade.Add("left:revert", CascadeOrigin::kAuthor);
+  cascade.Add("right:revert", CascadeOrigin::kAuthor);
+  cascade.Apply();
+
+  EXPECT_EQ("20px", cascade.ComputedValue("top"));
+  EXPECT_EQ("10px", cascade.ComputedValue("right"));
+  EXPECT_EQ("20px", cascade.ComputedValue("bottom"));
+  EXPECT_EQ("10px", cascade.ComputedValue("left"));
+}
+
+TEST_F(StyleCascadeTest, RevertCustomProperty) {
+  TestCascade cascade(GetDocument());
+  cascade.Add("--x:10px", CascadeOrigin::kUser);
+
+  cascade.Add("--y:fail", CascadeOrigin::kAuthor);
+
+  cascade.Add("--x:revert", CascadeOrigin::kAuthor);
+  cascade.Add("--y:revert", CascadeOrigin::kAuthor);
+
+  cascade.Apply();
+
+  EXPECT_EQ("10px", cascade.ComputedValue("--x"));
+  EXPECT_FALSE(cascade.ComputedValue("--y"));
+}
+
+TEST_F(StyleCascadeTest, RevertChain) {
+  TestCascade cascade(GetDocument());
+  cascade.Add("width:10px", CascadeOrigin::kUserAgent);
+
+  cascade.Add("width:revert", CascadeOrigin::kUser);
+  cascade.Add("--x:revert", CascadeOrigin::kUser);
+
+  cascade.Add("width:revert", CascadeOrigin::kAuthor);
+  cascade.Add("--x:revert", CascadeOrigin::kAuthor);
+  cascade.Apply();
+
+  EXPECT_EQ("10px", cascade.ComputedValue("width"));
+  EXPECT_FALSE(cascade.ComputedValue("--x"));
+}
+
+TEST_F(StyleCascadeTest, RevertFromAuthorToUA) {
+  TestCascade cascade(GetDocument());
+  cascade.Add("width:10px", CascadeOrigin::kUserAgent);
+  cascade.Add("height:10px", CascadeOrigin::kUserAgent);
+
+  cascade.Add("width:20px", CascadeOrigin::kAuthor);
+  cascade.Add("height:20px", CascadeOrigin::kAuthor);
+  cascade.Add("width:revert", CascadeOrigin::kAuthor);
+  cascade.Add("height:revert", CascadeOrigin::kAuthor);
+  cascade.Apply();
+
+  EXPECT_EQ("10px", cascade.ComputedValue("width"));
+  EXPECT_EQ("10px", cascade.ComputedValue("height"));
+}
+
+TEST_F(StyleCascadeTest, RevertInitialFallback) {
+  TestCascade cascade(GetDocument());
+  cascade.Add("width:20px", CascadeOrigin::kAuthor);
+  cascade.Add("width:revert", CascadeOrigin::kAuthor);
+  cascade.Apply();
+
+  EXPECT_EQ("auto", cascade.ComputedValue("width"));
+}
+
+TEST_F(StyleCascadeTest, RevertInheritedFallback) {
+  TestCascade parent(GetDocument());
+  parent.Add("color", "red");
+  parent.Apply();
+
+  TestCascade cascade(GetDocument());
+  cascade.InheritFrom(parent.TakeStyle());
+  EXPECT_EQ("rgb(255, 0, 0)", cascade.ComputedValue("color"));
+
+  cascade.Add("color:black", CascadeOrigin::kAuthor);
+  cascade.Add("color:revert", CascadeOrigin::kAuthor);
+  cascade.Apply();
+  EXPECT_EQ("rgb(255, 0, 0)", cascade.ComputedValue("color"));
+}
+
+TEST_F(StyleCascadeTest, RevertRegistered) {
+  RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
+
+  TestCascade cascade(GetDocument());
+  cascade.Add("--x:20px", CascadeOrigin::kUser);
+  cascade.Add("--x:100px", CascadeOrigin::kAuthor);
+  cascade.Add("--x:revert", CascadeOrigin::kAuthor);
+  cascade.Apply();
+
+  EXPECT_EQ("20px", cascade.ComputedValue("--x"));
+}
+
+TEST_F(StyleCascadeTest, RevertRegisteredInitialFallback) {
+  RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
+
+  TestCascade cascade(GetDocument());
+  cascade.Add("--x:20px", CascadeOrigin::kAuthor);
+  cascade.Add("--x:revert", CascadeOrigin::kAuthor);
+  cascade.Apply();
+
+  EXPECT_EQ("0px", cascade.ComputedValue("--x"));
+}
+
+TEST_F(StyleCascadeTest, RevertRegisteredInheritedFallback) {
+  RegisterProperty(GetDocument(), "--x", "<length>", "0px", true);
+
+  TestCascade parent(GetDocument());
+  parent.Add("--x", "1px");
+  parent.Apply();
+
+  TestCascade cascade(GetDocument());
+  cascade.InheritFrom(parent.TakeStyle());
+  EXPECT_EQ("1px", cascade.ComputedValue("--x"));
+
+  cascade.Add("--x:100px", CascadeOrigin::kAuthor);
+  cascade.Add("--x:revert", CascadeOrigin::kAuthor);
+  cascade.Apply();
+  EXPECT_EQ("1px", cascade.ComputedValue("--x"));
+}
 TEST_F(StyleCascadeTest, RegisteredInitial) {
   RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
 
diff --git a/third_party/blink/renderer/core/layout/BUILD.gn b/third_party/blink/renderer/core/layout/BUILD.gn
index e9cb636..6f4035bb 100644
--- a/third_party/blink/renderer/core/layout/BUILD.gn
+++ b/third_party/blink/renderer/core/layout/BUILD.gn
@@ -396,6 +396,8 @@
     "ng/layout_ng_fieldset.h",
     "ng/layout_ng_flexible_box.cc",
     "ng/layout_ng_flexible_box.h",
+    "ng/layout_ng_grid.cc",
+    "ng/layout_ng_grid.h",
     "ng/layout_ng_mixin.cc",
     "ng/layout_ng_mixin.h",
     "ng/layout_ng_progress.cc",
@@ -471,6 +473,8 @@
     "ng/ng_fragment_child_iterator.h",
     "ng/ng_fragmentation_utils.cc",
     "ng/ng_fragmentation_utils.h",
+    "ng/ng_grid_layout_algorithm.cc",
+    "ng/ng_grid_layout_algorithm.h",
     "ng/ng_ink_overflow.cc",
     "ng/ng_ink_overflow.h",
     "ng/ng_layout_algorithm.h",
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index 9d6d6f71..68cba10 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -278,7 +278,7 @@
     case EDisplay::kGrid:
     case EDisplay::kInlineGrid:
       UseCounter::Count(element->GetDocument(), WebFeature::kCSSGridLayout);
-      return new LayoutGrid(element);
+      return LayoutObjectFactory::CreateGrid(*element, style, legacy);
     case EDisplay::kLayoutCustom:
     case EDisplay::kInlineLayoutCustom:
       DCHECK(RuntimeEnabledFeatures::LayoutNGEnabled());
diff --git a/third_party/blink/renderer/core/layout/layout_object.h b/third_party/blink/renderer/core/layout/layout_object.h
index 3e65542..95e0362 100644
--- a/third_party/blink/renderer/core/layout/layout_object.h
+++ b/third_party/blink/renderer/core/layout/layout_object.h
@@ -2553,6 +2553,7 @@
     kLayoutObjectNGBlockFlow,
     kLayoutObjectNGFieldset,
     kLayoutObjectNGFlexibleBox,
+    kLayoutObjectNGGrid,
     kLayoutObjectNGMixin,
     kLayoutObjectNGListItem,
     kLayoutObjectNGInsideListMarker,
diff --git a/third_party/blink/renderer/core/layout/layout_object_factory.cc b/third_party/blink/renderer/core/layout/layout_object_factory.cc
index 5ec6031e..7d46b322 100644
--- a/third_party/blink/renderer/core/layout/layout_object_factory.cc
+++ b/third_party/blink/renderer/core/layout/layout_object_factory.cc
@@ -11,6 +11,7 @@
 #include "third_party/blink/renderer/core/layout/layout_fieldset.h"
 #include "third_party/blink/renderer/core/layout/layout_file_upload_control.h"
 #include "third_party/blink/renderer/core/layout/layout_flexible_box.h"
+#include "third_party/blink/renderer/core/layout/layout_grid.h"
 #include "third_party/blink/renderer/core/layout/layout_inside_list_marker.h"
 #include "third_party/blink/renderer/core/layout/layout_list_item.h"
 #include "third_party/blink/renderer/core/layout/layout_outside_list_marker.h"
@@ -24,6 +25,7 @@
 #include "third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h"
 #include "third_party/blink/renderer/core/layout/ng/layout_ng_fieldset.h"
 #include "third_party/blink/renderer/core/layout/ng/layout_ng_flexible_box.h"
+#include "third_party/blink/renderer/core/layout/ng/layout_ng_grid.h"
 #include "third_party/blink/renderer/core/layout/ng/layout_ng_progress.h"
 #include "third_party/blink/renderer/core/layout/ng/layout_ng_table_caption.h"
 #include "third_party/blink/renderer/core/layout/ng/layout_ng_table_cell.h"
@@ -107,6 +109,14 @@
       node, style, legacy, disable_ng_for_type);
 }
 
+LayoutBlock* LayoutObjectFactory::CreateGrid(Node& node,
+                                             const ComputedStyle& style,
+                                             LegacyLayout legacy) {
+  bool disable_ng_for_type = !RuntimeEnabledFeatures::LayoutNGGridEnabled();
+  return CreateObject<LayoutBlock, LayoutNGGrid, LayoutGrid>(
+      node, style, legacy, disable_ng_for_type);
+}
+
 LayoutObject* LayoutObjectFactory::CreateListMarker(Node& node,
                                                     const ComputedStyle& style,
                                                     LegacyLayout legacy) {
diff --git a/third_party/blink/renderer/core/layout/layout_object_factory.h b/third_party/blink/renderer/core/layout/layout_object_factory.h
index 37428424..2307e220a 100644
--- a/third_party/blink/renderer/core/layout/layout_object_factory.h
+++ b/third_party/blink/renderer/core/layout/layout_object_factory.h
@@ -44,6 +44,7 @@
   static LayoutBlock* CreateFlexibleBox(Node&,
                                         const ComputedStyle&,
                                         LegacyLayout);
+  static LayoutBlock* CreateGrid(Node&, const ComputedStyle&, LegacyLayout);
   static LayoutObject* CreateListMarker(Node&,
                                         const ComputedStyle&,
                                         LegacyLayout);
diff --git a/third_party/blink/renderer/core/layout/layout_theme.cc b/third_party/blink/renderer/core/layout/layout_theme.cc
index b1cbf53..a2a5f8b 100644
--- a/third_party/blink/renderer/core/layout/layout_theme.cc
+++ b/third_party/blink/renderer/core/layout/layout_theme.cc
@@ -866,10 +866,6 @@
   has_custom_focus_ring_color_ = true;
 }
 
-bool LayoutTheme::IsFocusRingOutset() const {
-  return false;
-}
-
 Color LayoutTheme::FocusRingColor() const {
   return has_custom_focus_ring_color_ ? custom_focus_ring_color_
                                       : GetTheme().PlatformFocusRingColor();
diff --git a/third_party/blink/renderer/core/layout/layout_theme.h b/third_party/blink/renderer/core/layout/layout_theme.h
index 9ffb65c..6701b24 100644
--- a/third_party/blink/renderer/core/layout/layout_theme.h
+++ b/third_party/blink/renderer/core/layout/layout_theme.h
@@ -169,7 +169,6 @@
                                 bool in_forced_colors_mode,
                                 WebColorScheme color_scheme) const;
 
-  virtual bool IsFocusRingOutset() const;
   virtual Color FocusRingColor() const;
   virtual Color PlatformFocusRingColor() const { return Color(0, 0, 0); }
   void SetCustomFocusRingColor(const Color&);
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_grid.cc b/third_party/blink/renderer/core/layout/ng/layout_ng_grid.cc
new file mode 100644
index 0000000..672f1a4
--- /dev/null
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_grid.cc
@@ -0,0 +1,23 @@
+// Copyright 2020 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/core/layout/ng/layout_ng_grid.h"
+
+#include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
+
+namespace blink {
+
+LayoutNGGrid::LayoutNGGrid(Element* element)
+    : LayoutNGMixin<LayoutBlock>(element) {}
+
+void LayoutNGGrid::UpdateBlockLayout(bool relayout_children) {
+  if (IsOutOfFlowPositioned()) {
+    UpdateOutOfFlowBlockLayout();
+    return;
+  }
+
+  UpdateInFlowBlockLayout();
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_grid.h b/third_party/blink/renderer/core/layout/ng/layout_ng_grid.h
new file mode 100644
index 0000000..0608359
--- /dev/null
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_grid.h
@@ -0,0 +1,31 @@
+// Copyright 2020 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_CORE_LAYOUT_NG_LAYOUT_NG_GRID_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LAYOUT_NG_GRID_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/layout/layout_block.h"
+#include "third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h"
+
+namespace blink {
+
+class CORE_EXPORT LayoutNGGrid : public LayoutNGMixin<LayoutBlock> {
+ public:
+  explicit LayoutNGGrid(Element*);
+
+  void UpdateBlockLayout(bool relayout_children) override;
+
+  const char* GetName() const override { return "LayoutNGGrid"; }
+
+ protected:
+  bool IsOfType(LayoutObjectType type) const override {
+    return type == kLayoutObjectNGGrid ||
+           LayoutNGMixin<LayoutBlock>::IsOfType(type);
+  }
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LAYOUT_NG_GRID_H_
diff --git a/third_party/blink/renderer/core/layout/ng/mathml/ng_math_fraction_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/mathml/ng_math_fraction_layout_algorithm.cc
index 9deb2e3c..b0b8105 100644
--- a/third_party/blink/renderer/core/layout/ng/mathml/ng_math_fraction_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/mathml/ng_math_fraction_layout_algorithm.cc
@@ -192,10 +192,6 @@
       ConstraintSpace().GetWritingMode(), ConstraintSpace().Direction(),
       To<NGPhysicalBoxFragment>(denominator_layout_result->PhysicalFragment()));
 
-  LayoutUnit content_inline_size = std::max(
-      numerator_fragment.InlineSize() + numerator_margins.InlineSum(),
-      denominator_fragment.InlineSize() + denominator_margins.InlineSum());
-
   LayoutUnit numerator_ascent =
       numerator_margins.block_start +
       numerator_fragment.Baseline().value_or(numerator_fragment.BlockSize());
@@ -252,13 +248,13 @@
   LogicalOffset denominator_offset;
   numerator_offset.inline_offset =
       border_scrollbar_padding_.inline_start + numerator_margins.inline_start +
-      (content_inline_size -
+      (child_available_size.inline_size -
        (numerator_fragment.InlineSize() + numerator_margins.InlineSum())) /
           2;
   denominator_offset.inline_offset =
       border_scrollbar_padding_.inline_start +
       denominator_margins.inline_start +
-      (content_inline_size -
+      (child_available_size.inline_size -
        (denominator_fragment.InlineSize() + denominator_margins.InlineSum())) /
           2;
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc
index 02c3b80..a99e66a7 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc
@@ -34,7 +34,6 @@
   borders_ = container_builder_.Borders();
   padding_ = container_builder_.Padding();
   border_box_size_ = container_builder_.InitialBorderBoxSize();
-  block_start_padding_edge_ = borders_.block_start;
 
   // Leading border and padding should only apply to the first fragment. We
   // don't adjust the value of border_padding_ itself so that it can be used
@@ -69,15 +68,21 @@
       container_builder_.SetIsInitialColumnBalancingPass();
   }
 
+  // TODO(almaher): Instead of setting this to 0 when resuming layout, this
+  // should equal the amount of the border block-start that is remaining from
+  // the previous fragment(s).
+  if (!IsResumingLayout(BreakToken()))
+    intrinsic_block_size_ = borders_.block_start;
+
   NGBreakStatus break_status = LayoutChildren();
   if (break_status == NGBreakStatus::kNeedsEarlierBreak) {
     // We need to abort the layout. No fragment will be generated.
     return container_builder_.Abort(NGLayoutResult::kNeedsEarlierBreak);
   }
 
-  intrinsic_block_size_ =
-      ClampIntrinsicBlockSize(ConstraintSpace(), Node(),
-                              adjusted_border_padding_, intrinsic_block_size_);
+  intrinsic_block_size_ = ClampIntrinsicBlockSize(
+      ConstraintSpace(), Node(), adjusted_border_padding_,
+      intrinsic_block_size_ + borders_.block_end);
 
   // Recompute the block-axis size now that we know our content size.
   border_box_size_.block_size =
@@ -113,7 +118,7 @@
     container_builder_.SetBlockSize(block_size);
   }
 
-  NGOutOfFlowLayoutPart(Node(), ConstraintSpace(), borders_with_legend_,
+  NGOutOfFlowLayoutPart(Node(), ConstraintSpace(), borders_,
                         &container_builder_)
       .Run();
 
@@ -154,62 +159,53 @@
     NGBreakStatus break_status = LayoutLegend(legend, legend_break_token);
     if (break_status != NGBreakStatus::kContinue)
       return break_status;
-  }
 
-  borders_with_legend_ = borders_;
-  borders_with_legend_.block_start = block_start_padding_edge_;
-
-  // The legend may eat from the available content box block size. If the
-  // border_box_size_ is expanded to encompass the legend, then update the
-  // border_box_size_ here, as well, to ensure the fieldset content gets the
-  // correct size.
-  if (!Node().ShouldApplySizeContainment() && legend_needs_layout) {
-    minimum_border_box_block_size_ =
-        borders_with_legend_.BlockSum() + padding_.BlockSum();
-    if (border_box_size_.block_size != kIndefiniteSize) {
-      border_box_size_.block_size =
-          std::max(border_box_size_.block_size, minimum_border_box_block_size_);
+    // The legend may eat from the available content box block size. Calculate
+    // the minimum block size needed to encompass the legend.
+    if (!Node().ShouldApplySizeContainment()) {
+      minimum_border_box_block_size_ =
+          intrinsic_block_size_ + padding_.BlockSum() + borders_.block_end;
     }
   }
 
+  NGBoxStrut borders_with_legend = borders_;
+  borders_with_legend.block_start = intrinsic_block_size_;
   LogicalSize adjusted_padding_box_size =
-      ShrinkAvailableSize(border_box_size_, borders_with_legend_);
+      ShrinkAvailableSize(border_box_size_, borders_with_legend);
 
-  // If the legend has been laid out in previous fragments,
-  // adjusted_padding_box_size will need to be adjusted further to account for
-  // block size taken up by the legend.
-  if (legend && adjusted_padding_box_size.block_size != kIndefiniteSize) {
-    LayoutUnit content_consumed_block_size =
-        content_break_token ? content_break_token->ConsumedBlockSize()
-                            : LayoutUnit();
-    LayoutUnit legend_block_size =
-        consumed_block_size_ - content_consumed_block_size;
-    adjusted_padding_box_size.block_size =
-        std::max(padding_.BlockSum(),
-                 adjusted_padding_box_size.block_size - legend_block_size);
-  }
+  if (adjusted_padding_box_size.block_size != kIndefiniteSize) {
+    // If intrinsic_block_size_ does not include the border block-start, exclude
+    // it from adjusted_padding_box_size, as well.
+    if (intrinsic_block_size_ == LayoutUnit())
+      adjusted_padding_box_size.block_size -= borders_.block_start;
 
-  if ((IsResumingLayout(content_break_token.get())) ||
-      (!block_start_padding_edge_adjusted_ && IsResumingLayout(BreakToken()))) {
-    borders_with_legend_.block_start = LayoutUnit();
+    // If the legend has been laid out in previous fragments,
+    // adjusted_padding_box_size will need to be adjusted further to account for
+    // block size taken up by the legend.
+    if (legend) {
+      LayoutUnit content_consumed_block_size =
+          content_break_token ? content_break_token->ConsumedBlockSize()
+                              : LayoutUnit();
+      LayoutUnit legend_block_size =
+          consumed_block_size_ - content_consumed_block_size;
+      adjusted_padding_box_size.block_size =
+          std::max(padding_.BlockSum(),
+                   adjusted_padding_box_size.block_size - legend_block_size);
+    }
   }
-  intrinsic_block_size_ = borders_with_legend_.BlockSum();
 
   // Proceed with normal fieldset children (excluding the rendered legend). They
   // all live inside an anonymous child box of the fieldset container.
   auto fieldset_content = Node().GetFieldsetContent();
   if (fieldset_content && (content_break_token || !has_seen_all_children)) {
-    LayoutUnit fragmentainer_block_offset;
-    if (ConstraintSpace().HasBlockFragmentation()) {
-      fragmentainer_block_offset =
-          ConstraintSpace().FragmentainerOffsetAtBfc() + intrinsic_block_size_;
-      if (legend_broke_ &&
-          IsFragmentainerOutOfSpace(fragmentainer_block_offset))
-        return NGBreakStatus::kContinue;
-    }
-    NGBreakStatus break_status = LayoutFieldsetContent(
-        fieldset_content, content_break_token, adjusted_padding_box_size,
-        fragmentainer_block_offset, !!legend);
+    if (ConstraintSpace().HasBlockFragmentation() && legend_broke_ &&
+        IsFragmentainerOutOfSpace(ConstraintSpace().FragmentainerOffsetAtBfc() +
+                                  intrinsic_block_size_))
+      return NGBreakStatus::kContinue;
+
+    NGBreakStatus break_status =
+        LayoutFieldsetContent(fieldset_content, content_break_token,
+                              adjusted_padding_box_size, !!legend);
     if (break_status == NGBreakStatus::kNeedsEarlierBreak)
       return break_status;
   }
@@ -284,11 +280,18 @@
         NGFragment(writing_mode_, physical_fragment).BlockSize() +
         legend_margins.BlockSum();
     LayoutUnit space_left = borders_.block_start - legend_margin_box_block_size;
-    if (space_left > LayoutUnit()) {
-      // Don't adjust the block_offset if the legend broke.
-      if (legend_break_token || legend_broke_)
-        break;
 
+    // If the border is smaller, intrinsic_block_size_ should now be based on
+    // the size of the legend instead of the border.
+    if (space_left <= LayoutUnit())
+      intrinsic_block_size_ = legend_margin_box_block_size;
+
+    // Don't adjust the block-start offset of the legend or fragment border if
+    // the legend broke.
+    if (legend_break_token || legend_broke_)
+      break;
+
+    if (space_left > LayoutUnit()) {
       // If the border is the larger one, though, it will stay put at the
       // border-box block-start edge of the fieldset. Then it's the legend
       // that needs to be pushed. We'll center the margin box in this case, to
@@ -306,8 +309,7 @@
       // border, the actual padding edge of the fieldset will be moved
       // accordingly. This will be the block-start offset for the fieldset
       // contents anonymous box.
-      block_start_padding_edge_ = legend_margin_box_block_size;
-      block_start_padding_edge_adjusted_ = true;
+      borders_.block_start = legend_margin_box_block_size;
     }
     break;
   } while (true);
@@ -330,11 +332,9 @@
     NGBlockNode& fieldset_content,
     scoped_refptr<const NGBlockBreakToken> content_break_token,
     LogicalSize adjusted_padding_box_size,
-    LayoutUnit fragmentainer_block_offset,
     bool has_legend) {
   auto child_space = CreateConstraintSpaceForFieldsetContent(
-      fieldset_content, adjusted_padding_box_size,
-      borders_with_legend_.block_start);
+      fieldset_content, adjusted_padding_box_size, intrinsic_block_size_);
   auto result = fieldset_content.Layout(child_space, content_break_token.get());
 
   // TODO(layout-dev): Handle abortions caused by block fragmentation.
@@ -345,7 +345,7 @@
     // TODO(almaher): The legend should be treated as out-of-flow.
     break_status = BreakBeforeChildIfNeeded(
         ConstraintSpace(), fieldset_content, *result.get(),
-        fragmentainer_block_offset,
+        ConstraintSpace().FragmentainerOffsetAtBfc() + intrinsic_block_size_,
         /*has_container_separation*/ has_legend, &container_builder_);
     EBreakBetween break_after = JoinFragmentainerBreakValues(
         result->FinalBreakAfter(), fieldset_content.Style().BreakAfter());
@@ -353,7 +353,8 @@
   }
 
   if (break_status == NGBreakStatus::kContinue) {
-    container_builder_.AddResult(*result, borders_with_legend_.StartOffset());
+    LogicalOffset offset(borders_.inline_start, intrinsic_block_size_);
+    container_builder_.AddResult(*result, offset);
     intrinsic_block_size_ +=
         NGFragment(writing_mode_, result->PhysicalFragment()).BlockSize();
     container_builder_.SetHasSeenAllChildren();
diff --git a/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.h b/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.h
index c375081..b687d73 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.h
@@ -37,7 +37,6 @@
       NGBlockNode& fieldset_content,
       scoped_refptr<const NGBlockBreakToken> content_break_token,
       LogicalSize adjusted_padding_box_size,
-      LayoutUnit fragmentainer_block_offset,
       bool has_legend);
 
   const NGConstraintSpace CreateConstraintSpaceForLegend(
@@ -62,10 +61,6 @@
   // and padding are only applied to the first fragment.
   NGBoxStrut adjusted_border_padding_;
 
-  // The result of borders_ after positioning the fieldset's legend element.
-  NGBoxStrut borders_with_legend_;
-
-  LayoutUnit block_start_padding_edge_;
   LayoutUnit intrinsic_block_size_;
   const LayoutUnit consumed_block_size_;
   LogicalSize border_box_size_;
@@ -75,10 +70,6 @@
   // the legend.
   LayoutUnit minimum_border_box_block_size_;
 
-  // If true, this indicates the block_start_padding_edge_ had changed from its
-  // initial value during the current layout pass.
-  bool block_start_padding_edge_adjusted_ = false;
-
   // If true, this indicates that the legend broke during the current layout
   // pass.
   bool legend_broke_ = false;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm_test.cc b/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm_test.cc
index 36789003..052e708 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm_test.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm_test.cc
@@ -1811,10 +1811,12 @@
       node, space, fragment->BreakToken());
   ASSERT_TRUE(fragment->BreakToken());
 
+  // TODO(almaher): The fieldset content should start at offset 60,20.
   dump = DumpFragmentTree(fragment.get());
   expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::.
   offset:unplaced size:220x40
     offset:60,0 size:10x10
+    offset:60,0 size:100x0
 )DUMP";
   EXPECT_EQ(expectation, dump);
 
@@ -1830,9 +1832,7 @@
 
   fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(
       node, space, fragment->BreakToken());
-  // TODO(almaher): There should be no break token here. In this case the bottom
-  // border never reduces in size, causing fragmentation to continue infinitely.
-  ASSERT_TRUE(fragment->BreakToken());
+  ASSERT_FALSE(fragment->BreakToken());
 
   dump = DumpFragmentTree(fragment.get());
   expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::.
@@ -1879,9 +1879,11 @@
       node, space, fragment->BreakToken());
   ASSERT_TRUE(fragment->BreakToken());
 
+  // TODO(almaher): The fieldset content should start at offset 60,20.
   dump = DumpFragmentTree(fragment.get());
   expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::.
   offset:unplaced size:220x40
+    offset:60,0 size:100x0
 )DUMP";
   EXPECT_EQ(expectation, dump);
 
@@ -1897,9 +1899,7 @@
 
   fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(
       node, space, fragment->BreakToken());
-  // TODO(almaher): There should be no break token here. In this case the bottom
-  // border never reduces in size, causing fragmentation to continue infinitely.
-  ASSERT_TRUE(fragment->BreakToken());
+  ASSERT_FALSE(fragment->BreakToken());
 
   dump = DumpFragmentTree(fragment.get());
   expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::.
@@ -1946,9 +1946,11 @@
       node, space, fragment->BreakToken());
   ASSERT_TRUE(fragment->BreakToken());
 
+  // TODO(almaher): The fieldset content should start at offset 60,20.
   dump = DumpFragmentTree(fragment.get());
   expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::.
   offset:unplaced size:220x40
+    offset:60,0 size:100x0
 )DUMP";
   EXPECT_EQ(expectation, dump);
 
@@ -1964,9 +1966,7 @@
 
   fragment = NGBaseLayoutAlgorithmTest::RunFieldsetLayoutAlgorithm(
       node, space, fragment->BreakToken());
-  // TODO(almaher): There should be no break token here. In this case the bottom
-  // border never reduces in size, causing fragmentation to continue infinitely.
-  ASSERT_TRUE(fragment->BreakToken());
+  ASSERT_FALSE(fragment->BreakToken());
 
   dump = DumpFragmentTree(fragment.get());
   expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::.
diff --git a/third_party/blink/renderer/core/layout/ng/ng_grid_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_grid_layout_algorithm.cc
new file mode 100644
index 0000000..1e25706
--- /dev/null
+++ b/third_party/blink/renderer/core/layout/ng/ng_grid_layout_algorithm.cc
@@ -0,0 +1,28 @@
+// Copyright 2020 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/core/layout/ng/ng_grid_layout_algorithm.h"
+
+namespace blink {
+
+NGGridLayoutAlgorithm::NGGridLayoutAlgorithm(
+    const NGLayoutAlgorithmParams& params)
+    : NGLayoutAlgorithm(params) {
+  DCHECK(params.space.IsNewFormattingContext());
+  DCHECK(!params.break_token);
+  container_builder_.SetIsNewFormattingContext(true);
+  container_builder_.SetInitialFragmentGeometry(params.fragment_geometry);
+}
+
+scoped_refptr<const NGLayoutResult> NGGridLayoutAlgorithm::Layout() {
+  return container_builder_.ToBoxFragment();
+}
+
+base::Optional<MinMaxSizes> NGGridLayoutAlgorithm::ComputeMinMaxSizes(
+    const MinMaxSizesInput& input) const {
+  base::Optional<MinMaxSizes> sizes;
+  return sizes;
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/ng_grid_layout_algorithm.h b/third_party/blink/renderer/core/layout/ng/ng_grid_layout_algorithm.h
new file mode 100644
index 0000000..f07c006
--- /dev/null
+++ b/third_party/blink/renderer/core/layout/ng/ng_grid_layout_algorithm.h
@@ -0,0 +1,29 @@
+// Copyright 2020 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_CORE_LAYOUT_NG_NG_GRID_LAYOUT_ALGORITHM_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_GRID_LAYOUT_ALGORITHM_H_
+
+#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_layout_algorithm.h"
+
+namespace blink {
+
+class CORE_EXPORT NGGridLayoutAlgorithm
+    : public NGLayoutAlgorithm<NGBlockNode,
+                               NGBoxFragmentBuilder,
+                               NGBlockBreakToken> {
+ public:
+  explicit NGGridLayoutAlgorithm(const NGLayoutAlgorithmParams& params);
+
+  scoped_refptr<const NGLayoutResult> Layout() override;
+
+  base::Optional<MinMaxSizes> ComputeMinMaxSizes(
+      const MinMaxSizesInput&) const override;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_GRID_LAYOUT_ALGORITHM_H_
diff --git a/third_party/blink/renderer/core/loader/image_loader.cc b/third_party/blink/renderer/core/loader/image_loader.cc
index a5f02639..c50a68b 100644
--- a/third_party/blink/renderer/core/loader/image_loader.cc
+++ b/third_party/blink/renderer/core/loader/image_loader.cc
@@ -109,14 +109,12 @@
 class ImageLoader::Task {
  public:
   Task(ImageLoader* loader,
-       const KURL& request_url,
        UpdateFromElementBehavior update_behavior,
        network::mojom::ReferrerPolicy referrer_policy)
       : loader_(loader),
         should_bypass_main_world_csp_(ShouldBypassMainWorldCSP(loader)),
         update_behavior_(update_behavior),
-        referrer_policy_(referrer_policy),
-        request_url_(request_url) {
+        referrer_policy_(referrer_policy) {
     ExecutionContext* context =
         loader_->GetElement()->GetDocument().ToExecutionContext();
     probe::AsyncTaskScheduled(context, "Image", &async_task_id_);
@@ -142,13 +140,11 @@
     if (script_state_ && script_state_->ContextIsValid()) {
       ScriptState::Scope scope(script_state_);
       loader_->DoUpdateFromElement(should_bypass_main_world_csp_,
-                                   update_behavior_, request_url_,
-                                   referrer_policy_);
+                                   update_behavior_, referrer_policy_);
     } else {
       // This call does not access v8::Context internally.
       loader_->DoUpdateFromElement(should_bypass_main_world_csp_,
-                                   update_behavior_, request_url_,
-                                   referrer_policy_);
+                                   update_behavior_, referrer_policy_);
     }
   }
 
@@ -165,7 +161,7 @@
   UpdateFromElementBehavior update_behavior_;
   WeakPersistent<ScriptState> script_state_;
   network::mojom::ReferrerPolicy referrer_policy_;
-  KURL request_url_;
+
   probe::AsyncTaskId async_task_id_;
   base::WeakPtrFactory<Task> weak_factory_{this};
 };
@@ -416,11 +412,9 @@
 }
 
 inline void ImageLoader::EnqueueImageLoadingMicroTask(
-    const KURL& request_url,
     UpdateFromElementBehavior update_behavior,
     network::mojom::ReferrerPolicy referrer_policy) {
-  auto task = std::make_unique<Task>(this, request_url, update_behavior,
-                                     referrer_policy);
+  auto task = std::make_unique<Task>(this, update_behavior, referrer_policy);
   pending_task_ = task->GetWeakPtr();
   Microtask::EnqueueMicrotask(
       WTF::Bind(&Task::Run, WTF::Passed(std::move(task))));
@@ -448,7 +442,6 @@
 void ImageLoader::DoUpdateFromElement(
     BypassMainWorldBehavior bypass_behavior,
     UpdateFromElementBehavior update_behavior,
-    const KURL& url,
     network::mojom::ReferrerPolicy referrer_policy,
     UpdateType update_type) {
   // FIXME: According to
@@ -469,6 +462,7 @@
     return;
 
   AtomicString image_source_url = element_->ImageSourceURL();
+  const KURL url = ImageSourceToKURL(image_source_url);
   ImageResourceContent* new_image_content = nullptr;
   if (!url.IsNull() && !url.IsEmpty()) {
     // Unlike raw <img>, we block mixed content inside of <picture> or
@@ -677,10 +671,8 @@
     delay_until_do_update_from_element_ = nullptr;
   }
 
-  KURL url = ImageSourceToKURL(image_source_url);
-
-  if (ShouldLoadImmediately(url)) {
-    DoUpdateFromElement(kDoNotBypassMainWorldCSP, update_behavior, url,
+  if (ShouldLoadImmediately(ImageSourceToKURL(image_source_url))) {
+    DoUpdateFromElement(kDoNotBypassMainWorldCSP, update_behavior,
                         referrer_policy, UpdateType::kSync);
     return;
   }
@@ -705,7 +697,7 @@
   // images we don't intend to display.
   Document& document = element_->GetDocument();
   if (!document.IsContextDestroyed() && document.IsActive())
-    EnqueueImageLoadingMicroTask(url, update_behavior, referrer_policy);
+    EnqueueImageLoadingMicroTask(update_behavior, referrer_policy);
 }
 
 KURL ImageLoader::ImageSourceToKURL(AtomicString image_source_url) const {
diff --git a/third_party/blink/renderer/core/loader/image_loader.h b/third_party/blink/renderer/core/loader/image_loader.h
index 181069a5..2ca1ee3 100644
--- a/third_party/blink/renderer/core/loader/image_loader.h
+++ b/third_party/blink/renderer/core/loader/image_loader.h
@@ -173,7 +173,6 @@
   void DoUpdateFromElement(
       BypassMainWorldBehavior,
       UpdateFromElementBehavior,
-      const KURL&,
       network::mojom::ReferrerPolicy = network::mojom::ReferrerPolicy::kDefault,
       UpdateType = UpdateType::kAsync);
 
@@ -197,8 +196,7 @@
   void ClearFailedLoadURL();
   void DispatchErrorEvent();
   void CrossSiteOrCSPViolationOccurred(AtomicString);
-  void EnqueueImageLoadingMicroTask(const KURL&,
-                                    UpdateFromElementBehavior,
+  void EnqueueImageLoadingMicroTask(UpdateFromElementBehavior,
                                     network::mojom::ReferrerPolicy);
 
   KURL ImageSourceToKURL(AtomicString) const;
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_test.cc b/third_party/blink/renderer/core/paint/compositing/compositing_test.cc
index cefe3499..65bb5cf 100644
--- a/third_party/blink/renderer/core/paint/compositing/compositing_test.cc
+++ b/third_party/blink/renderer/core/paint/compositing/compositing_test.cc
@@ -903,7 +903,7 @@
   Compositor().BeginFrame();
 
   auto* inner_element_layer = CcLayerByDOMElementId("inner");
-  EXPECT_FALSE(inner_element_layer->double_sided());
+  EXPECT_TRUE(inner_element_layer->should_check_backface_visibility());
 
   // Initially, no layer should have |subtree_property_changed| set.
   EXPECT_FALSE(inner_element_layer->subtree_property_changed());
@@ -915,7 +915,7 @@
   UpdateAllLifecyclePhases();
 
   inner_element_layer = CcLayerByDOMElementId("inner");
-  EXPECT_FALSE(inner_element_layer->double_sided());
+  EXPECT_TRUE(inner_element_layer->should_check_backface_visibility());
   EXPECT_TRUE(inner_element_layer->subtree_property_changed());
 
   // After a frame the |subtree_property_changed| value should be reset.
diff --git a/third_party/blink/renderer/core/paint/object_painter_base.cc b/third_party/blink/renderer/core/paint/object_painter_base.cc
index 096fd29..3660bc3e 100644
--- a/third_party/blink/renderer/core/paint/object_painter_base.cc
+++ b/third_party/blink/renderer/core/paint/object_painter_base.cc
@@ -551,8 +551,7 @@
     paint_info.context.DrawFocusRing(
         pixel_snapped_outline_rects, style.GetOutlineStrokeWidthForFocusRing(),
         style.OutlineOffset(), style.GetDefaultOffsetForFocusRing(),
-        border_radius, min_border_width, color,
-        LayoutTheme::GetTheme().IsFocusRingOutset());
+        border_radius, min_border_width, color);
     return;
   }
 
diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc
index 0d52eb1..5736b22c 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -221,10 +221,8 @@
 
   // Child layers will be deleted by their corresponding layout objects, so
   // we don't need to delete them ourselves.
-  {
-    DisableCompositingQueryAsserts disabler;
+  if (HasCompositedLayerMapping())
     ClearCompositedLayerMapping(true);
-  }
 
   // Reset this flag before disposing scrollable_area_ to prevent
   // PaintLayerScrollableArea::WillRemoveScrollbar() from dirtying the z-order
@@ -2762,26 +2760,24 @@
 }
 
 void PaintLayer::ClearCompositedLayerMapping(bool layer_being_destroyed) {
-  if (!HasCompositedLayerMapping())
-    return;
+  DCHECK(HasCompositedLayerMapping());
+  DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled());
 
+  DisableCompositingQueryAsserts disabler;
   if (layer_being_destroyed) {
-    if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
-      // The visual rects will be in a different coordinate space after losing
-      // their compositing container. Clear them before prepaint to avoid
-      // spurious layout shift reports from LayoutShiftTracker.
-      // If the PaintLayer were not being destroyed, this would happen during
-      // the compositing update (PaintLayerCompositor::UpdateIfNeeded).
-      // TODO: LayoutShiftTracker's reliance on having visual rects cleared
-      // before prepaint in the case of compositing changes is not ideal, and
-      // will not work with CompositeAfterPaint. Some transform tree changes may
-      // still produce incorrect behavior from LayoutShiftTracker (see
-      // discussion on review thread of http://crrev.com/c/1636403).
-      if (Compositor()) {
-        Compositor()
-            ->ForceRecomputeVisualRectsIncludingNonCompositingDescendants(
-                layout_object_);
-      }
+    // The visual rects will be in a different coordinate space after losing
+    // their compositing container. Clear them before prepaint to avoid
+    // spurious layout shift reports from LayoutShiftTracker.
+    // If the PaintLayer were not being destroyed, this would happen during
+    // the compositing update (PaintLayerCompositor::UpdateIfNeeded).
+    // TODO: LayoutShiftTracker's reliance on having visual rects cleared
+    // before prepaint in the case of compositing changes is not ideal, and
+    // will not work with CompositeAfterPaint. Some transform tree changes may
+    // still produce incorrect behavior from LayoutShiftTracker (see
+    // discussion on review thread of http://crrev.com/c/1636403).
+    if (Compositor()) {
+      Compositor()->ForceRecomputeVisualRectsIncludingNonCompositingDescendants(
+          layout_object_);
     }
   } else {
     // We need to make sure our decendants get a geometry update. In principle,
@@ -3154,7 +3150,7 @@
 
   if (diff.CompositingReasonsChanged()) {
     SetNeedsCompositingInputsUpdate();
-  } else {
+  } else if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
     // For querying stale GetCompositingState().
     DisableCompositingQueryAsserts disable;
 
@@ -3479,13 +3475,15 @@
   if (!stacking_context)
     return;
 
-  // This invalidation code intentionally refers to stale state.
-  DisableCompositingQueryAsserts disabler;
+  if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
+    // This invalidation code intentionally refers to stale state.
+    DisableCompositingQueryAsserts disabler;
 
-  // Changes of stacking may result in graphics layers changing size
-  // due to new contents painting into them.
-  if (auto* mapping = stacking_context->GetCompositedLayerMapping())
-    mapping->SetNeedsGraphicsLayerUpdate(kGraphicsLayerUpdateSubtree);
+    // Changes of stacking may result in graphics layers changing size
+    // due to new contents painting into them.
+    if (auto* mapping = stacking_context->GetCompositedLayerMapping())
+      mapping->SetNeedsGraphicsLayerUpdate(kGraphicsLayerUpdateSubtree);
+  }
 
   if (stacking_context->StackingNode())
     stacking_context->StackingNode()->DirtyZOrderLists();
diff --git a/third_party/blink/renderer/core/style/computed_style.cc b/third_party/blink/renderer/core/style/computed_style.cc
index cd061dd..ba5680b 100644
--- a/third_party/blink/renderer/core/style/computed_style.cc
+++ b/third_party/blink/renderer/core/style/computed_style.cc
@@ -2235,8 +2235,7 @@
   if (OutlineStyleIsAuto()) {
     return GraphicsContext::FocusRingOutsetExtent(
         OutlineOffset(), GetDefaultOffsetForFocusRing(),
-        std::ceil(GetOutlineStrokeWidthForFocusRing()),
-        LayoutTheme::GetTheme().IsFocusRingOutset());
+        std::ceil(GetOutlineStrokeWidthForFocusRing()));
   }
   return base::ClampAdd(OutlineWidth(), OutlineOffset()).Max(0);
 }
@@ -2249,9 +2248,6 @@
 #if defined(OS_MACOSX)
   return OutlineWidth();
 #else
-  if (LayoutTheme::GetTheme().IsFocusRingOutset()) {
-    return OutlineWidth();
-  }
   // Draw an outline with thickness in proportion to the zoom level, but never
   // so narrow that it becomes invisible.
   return std::max(EffectiveZoom(), 1.f);
diff --git a/third_party/blink/renderer/core/style/computed_style_test.cc b/third_party/blink/renderer/core/style/computed_style_test.cc
index f200764..9a426bb 100644
--- a/third_party/blink/renderer/core/style/computed_style_test.cc
+++ b/third_party/blink/renderer/core/style/computed_style_test.cc
@@ -88,12 +88,10 @@
     static uint16_t outline_width = 4;
     style->SetOutlineWidth(outline_width);
 
-    double expected_width =
-        LayoutTheme::GetTheme().IsFocusRingOutset() ? outline_width : 3.5;
+    double expected_width = 3.5;
     EXPECT_EQ(expected_width, style->GetOutlineStrokeWidthForFocusRing());
 
-    expected_width =
-        LayoutTheme::GetTheme().IsFocusRingOutset() ? outline_width : 1.0;
+    expected_width = 1.0;
     style->SetEffectiveZoom(0.5);
     EXPECT_EQ(expected_width, style->GetOutlineStrokeWidthForFocusRing());
 #endif
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index 52204d7..8673b9e1 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -1710,6 +1710,8 @@
       auto* element = DynamicTo<Element>(node);
       while (element && !element->GetLayoutObject()) {
         const ComputedStyle* style = element->EnsureComputedStyle();
+        if (!style)
+          continue;
         if (is_first_loop && style->Visibility() != EVisibility::kVisible)
           return true;
         // CSS Display:
@@ -1750,6 +1752,8 @@
     auto* element = DynamicTo<Element>(node);
     if (element && node->isConnected()) {
       const ComputedStyle* style = element->EnsureComputedStyle();
+      if (!style)
+        return false;
       return style->Display() == EDisplay::kNone ||
              style->Visibility() != EVisibility::kVisible;
     }
diff --git a/third_party/blink/renderer/modules/webaudio/audio_listener.cc b/third_party/blink/renderer/modules/webaudio/audio_listener.cc
index 8cb80d5..6a347e4 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_listener.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_listener.cc
@@ -150,15 +150,26 @@
 }
 
 bool AudioListener::HasSampleAccurateValues() const {
-  return positionX()->Handler().HasSampleAccurateValues() ||
-         positionY()->Handler().HasSampleAccurateValues() ||
-         positionZ()->Handler().HasSampleAccurateValues() ||
-         forwardX()->Handler().HasSampleAccurateValues() ||
-         forwardY()->Handler().HasSampleAccurateValues() ||
-         forwardZ()->Handler().HasSampleAccurateValues() ||
-         upX()->Handler().HasSampleAccurateValues() ||
-         upY()->Handler().HasSampleAccurateValues() ||
-         upZ()->Handler().HasSampleAccurateValues();
+  return positionX()->Handler().HasSampleAccurateValuesTimeline() ||
+         positionY()->Handler().HasSampleAccurateValuesTimeline() ||
+         positionZ()->Handler().HasSampleAccurateValuesTimeline() ||
+         forwardX()->Handler().HasSampleAccurateValuesTimeline() ||
+         forwardY()->Handler().HasSampleAccurateValuesTimeline() ||
+         forwardZ()->Handler().HasSampleAccurateValuesTimeline() ||
+         upX()->Handler().HasSampleAccurateValuesTimeline() ||
+         upY()->Handler().HasSampleAccurateValuesTimeline() ||
+         upZ()->Handler().HasSampleAccurateValuesTimeline();
+}
+
+bool AudioListener::IsAudioRate() const {
+  return positionX()->Handler().IsAudioRate() ||
+         positionY()->Handler().IsAudioRate() ||
+         positionZ()->Handler().IsAudioRate() ||
+         forwardX()->Handler().IsAudioRate() ||
+         forwardY()->Handler().IsAudioRate() ||
+         forwardZ()->Handler().IsAudioRate() ||
+         upX()->Handler().IsAudioRate() || upY()->Handler().IsAudioRate() ||
+         upZ()->Handler().IsAudioRate();
 }
 
 void AudioListener::UpdateValuesIfNeeded(uint32_t frames_to_process) {
diff --git a/third_party/blink/renderer/modules/webaudio/audio_listener.h b/third_party/blink/renderer/modules/webaudio/audio_listener.h
index 6be215b..135706c4 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_listener.h
+++ b/third_party/blink/renderer/modules/webaudio/audio_listener.h
@@ -69,6 +69,10 @@
   // True if any of AudioParams have automations.
   bool HasSampleAccurateValues() const;
 
+  // True if any of the AudioParams are set for a-rate automations
+  // (the default).
+  bool IsAudioRate() const;
+
   // Update the internal state of the listener, including updating the dirty
   // state of all PannerNodes if necessary.
   void UpdateState();
diff --git a/third_party/blink/renderer/modules/webaudio/audio_param.cc b/third_party/blink/renderer/modules/webaudio/audio_param.cc
index ecdcc71..68545cc7 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_param.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_param.cc
@@ -271,7 +271,11 @@
   if (NumberOfRenderingConnections() > 0) {
     DCHECK_LE(number_of_values, audio_utilities::kRenderQuantumFrames);
 
-    summing_bus_->SetChannelMemory(0, values, number_of_values);
+    // If we're not sample accurate, we only need one value, so make the summing
+    // bus have length 1.  When the connections are added in, only the first
+    // value will be added.  Which is exactly what we want.
+    summing_bus_->SetChannelMemory(0, values,
+                                   sample_accurate ? number_of_values : 1);
 
     for (unsigned i = 0; i < NumberOfRenderingConnections(); ++i) {
       AudioNodeOutput* output = RenderingOutput(i);
@@ -285,6 +289,14 @@
       summing_bus_->SumFrom(*connection_bus);
     }
 
+    // If we're not sample accurate, duplicate the first element of |values| to
+    // all of the elements.
+    if (!sample_accurate) {
+      for (unsigned k = 0; k < number_of_values; ++k) {
+        values[k] = values[0];
+      }
+    }
+
     // Clamp the values now to the nominal range
     float min_value = MinValue();
     float max_value = MaxValue();
diff --git a/third_party/blink/renderer/modules/webaudio/panner_node.cc b/third_party/blink/renderer/modules/webaudio/panner_node.cc
index e1e46d9..cacac039 100644
--- a/third_party/blink/renderer/modules/webaudio/panner_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/panner_node.cc
@@ -192,7 +192,8 @@
       Listener()->WaitForHRTFDatabaseLoaderThreadCompletion();
     }
 
-    if (HasSampleAccurateValues() || Listener()->HasSampleAccurateValues()) {
+    if ((HasSampleAccurateValues() || Listener()->HasSampleAccurateValues()) &&
+        (IsAudioRate() || Listener()->IsAudioRate())) {
       // It's tempting to skip sample-accurate processing if
       // isAzimuthElevationDirty() and isDistanceConeGain() both return false.
       // But in general we can't because something may scheduled to start in the
@@ -330,10 +331,9 @@
                            Listener()->HrtfDatabaseLoader());
   Listener()->AddPanner(*this);
 
-  // Set the cached values to the current values to start things off.  The
-  // panner is already marked as dirty, so this won't matter.
-  last_position_ = GetPosition();
-  last_orientation_ = Orientation();
+  // The panner is already marked as dirty, so |last_position_| and
+  // |last_orientation_| will bet updated on first use.  Don't need to
+  // set them here.
 
   AudioHandler::Initialize();
 }
@@ -710,12 +710,18 @@
 }
 
 bool PannerHandler::HasSampleAccurateValues() const {
-  return position_x_->HasSampleAccurateValues() ||
-         position_y_->HasSampleAccurateValues() ||
-         position_z_->HasSampleAccurateValues() ||
-         orientation_x_->HasSampleAccurateValues() ||
-         orientation_y_->HasSampleAccurateValues() ||
-         orientation_z_->HasSampleAccurateValues();
+  return position_x_->HasSampleAccurateValuesTimeline() ||
+         position_y_->HasSampleAccurateValuesTimeline() ||
+         position_z_->HasSampleAccurateValuesTimeline() ||
+         orientation_x_->HasSampleAccurateValuesTimeline() ||
+         orientation_y_->HasSampleAccurateValuesTimeline() ||
+         orientation_z_->HasSampleAccurateValuesTimeline();
+}
+
+bool PannerHandler::IsAudioRate() const {
+  return position_x_->IsAudioRate() || position_y_->IsAudioRate() ||
+         position_z_->IsAudioRate() || orientation_x_->IsAudioRate() ||
+         orientation_y_->IsAudioRate() || orientation_z_->IsAudioRate();
 }
 
 void PannerHandler::UpdateDirtyState() {
diff --git a/third_party/blink/renderer/modules/webaudio/panner_node.h b/third_party/blink/renderer/modules/webaudio/panner_node.h
index e74f9d0..4c405b2 100644
--- a/third_party/blink/renderer/modules/webaudio/panner_node.h
+++ b/third_party/blink/renderer/modules/webaudio/panner_node.h
@@ -176,18 +176,34 @@
   float cached_distance_cone_gain_;
 
   const FloatPoint3D GetPosition() const {
-    return FloatPoint3D(position_x_->Value(), position_y_->Value(),
-                        position_z_->Value());
+    auto x = position_x_->IsAudioRate() ? position_x_->FinalValue()
+                                        : position_x_->Value();
+    auto y = position_y_->IsAudioRate() ? position_y_->FinalValue()
+                                        : position_y_->Value();
+    auto z = position_z_->IsAudioRate() ? position_z_->FinalValue()
+                                        : position_z_->Value();
+
+    return FloatPoint3D(x, y, z);
   }
 
   const FloatPoint3D Orientation() const {
-    return FloatPoint3D(orientation_x_->Value(), orientation_y_->Value(),
-                        orientation_z_->Value());
+    auto x = orientation_x_->IsAudioRate() ? orientation_x_->FinalValue()
+                                           : orientation_x_->Value();
+    auto y = orientation_y_->IsAudioRate() ? orientation_y_->FinalValue()
+                                           : orientation_y_->Value();
+    auto z = orientation_z_->IsAudioRate() ? orientation_z_->FinalValue()
+                                           : orientation_z_->Value();
+
+    return FloatPoint3D(x, y, z);
   }
 
   // True if any of this panner's AudioParams have automations.
   bool HasSampleAccurateValues() const;
 
+  // True if any of the panner's AudioParams are set for a-rate
+  // automations (the default).
+  bool IsAudioRate() const;
+
   scoped_refptr<AudioParamHandler> position_x_;
   scoped_refptr<AudioParamHandler> position_y_;
   scoped_refptr<AudioParamHandler> position_z_;
diff --git a/third_party/blink/renderer/platform/graphics/compositing/layers_as_json.cc b/third_party/blink/renderer/platform/graphics/compositing/layers_as_json.cc
index 3502e78..b4b306f3 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/layers_as_json.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/layers_as_json.cc
@@ -52,7 +52,7 @@
   if (!layer->DrawsContent())
     json->SetBoolean("drawsContent", false);
 
-  if (!layer->double_sided())
+  if (layer->should_check_backface_visibility())
     json->SetString("backfaceVisibility", "hidden");
 
   if (Color(layer->background_color()).Alpha()) {
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
index 015e3f9..fc86897 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
@@ -1286,7 +1286,6 @@
     layer->SetClipTreeIndex(clip_id);
     layer->SetEffectTreeIndex(effect_id);
     bool backface_hidden = property_state.Transform().IsBackfaceHidden();
-    layer->SetDoubleSided(!backface_hidden);
     layer->SetShouldCheckBackfaceVisibility(backface_hidden);
     bool has_will_change_transform =
         property_state.Transform().RequiresCompositingForWillChangeTransform();
diff --git a/third_party/blink/renderer/platform/graphics/graphics_context.cc b/third_party/blink/renderer/platform/graphics/graphics_context.cc
index 8473a8d..8c471fe 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_context.cc
+++ b/third_party/blink/renderer/platform/graphics/graphics_context.cc
@@ -322,10 +322,7 @@
 
 namespace {
 
-int AdjustedFocusRingOffset(int offset,
-                            int default_offset,
-                            int width,
-                            bool is_outset) {
+int AdjustedFocusRingOffset(int offset, int default_offset, int width) {
   if (::features::IsFormControlsRefreshEnabled()) {
     // For FormControlsRefresh the focus ring has a default offset that
     // depends on the element type.
@@ -335,8 +332,6 @@
 #if defined(OS_MACOSX)
   return offset + 2;
 #else
-  if (is_outset)
-    return offset + width - (width + 1) / 2;
   return 0;
 #endif
 }
@@ -345,19 +340,17 @@
 
 int GraphicsContext::FocusRingOutsetExtent(int offset,
                                            int default_offset,
-                                           int width,
-                                           bool is_outset) {
+                                           int width) {
   // Unlike normal outlines (whole width is outside of the offset), focus
   // rings can be drawn with the center of the path aligned with the offset, so
   // only half of the width is outside of the offset.
   if (::features::IsFormControlsRefreshEnabled()) {
     // For FormControlsRefresh 2/3 of the width is outside of the offset.
-    return AdjustedFocusRingOffset(offset, default_offset, width, is_outset) +
+    return AdjustedFocusRingOffset(offset, default_offset, width) +
            std::ceil(width / 3.f) * 2;
   }
 
-  return AdjustedFocusRingOffset(offset, /*default_offset=*/0, width,
-                                 is_outset) +
+  return AdjustedFocusRingOffset(offset, /*default_offset=*/0, width) +
          (width + 1) / 2;
 }
 
@@ -398,8 +391,7 @@
                                             float width,
                                             int offset,
                                             float border_radius,
-                                            const Color& color,
-                                            bool is_outset) {
+                                            const Color& color) {
   unsigned rect_count = rects.size();
   if (!rect_count)
     return;
@@ -408,8 +400,8 @@
   if (!::features::IsFormControlsRefreshEnabled()) {
     // For FormControlsRefresh the offset is already adjusted by
     // GraphicsContext::DrawFocusRing.
-    offset = AdjustedFocusRingOffset(offset, /*default_offset=*/0,
-                                     std::ceil(width), is_outset);
+    offset =
+        AdjustedFocusRingOffset(offset, /*default_offset=*/0, std::ceil(width));
   }
   for (unsigned i = 0; i < rect_count; i++) {
     SkIRect r = rects[i];
@@ -432,38 +424,19 @@
   }
 }
 
-namespace {
-
-static const double kFocusRingLuminanceThreshold = 0.45;
-
-bool ShouldDrawInnerFocusRingForContrast(bool is_outset,
-                                         float width,
-                                         Color color) {
-  if (!is_outset || width < 3) {
-    return false;
-  }
-  double h = 0.0, s = 0.0, l = 0.0;
-  color.GetHSL(h, s, l);
-  return l < kFocusRingLuminanceThreshold;
-}
-
-}  // namespace
-
 void GraphicsContext::DrawFocusRing(const Vector<IntRect>& rects,
                                     float width,
                                     int offset,
                                     int default_offset,
                                     float border_radius,
                                     float min_border_width,
-                                    const Color& color,
-                                    bool is_outset) {
+                                    const Color& color) {
   if (::features::IsFormControlsRefreshEnabled()) {
     // The focus ring is made of two borders which have a 2:1 ratio.
     const float first_border_width = (width / 3) * 2;
     const float second_border_width = width - first_border_width;
 
-    offset = AdjustedFocusRingOffset(offset, default_offset, std::ceil(width),
-                                     is_outset);
+    offset = AdjustedFocusRingOffset(offset, default_offset, std::ceil(width));
     // How much space the focus ring would like to take from the actual border.
     const float inside_border_width = 1;
     if (min_border_width >= inside_border_width) {
@@ -473,27 +446,11 @@
     // artifacts.
     DrawFocusRingInternal(rects, first_border_width,
                           offset + std::ceil(second_border_width),
-                          border_radius, SK_ColorWHITE, is_outset);
+                          border_radius, SK_ColorWHITE);
     DrawFocusRingInternal(rects, first_border_width, offset, border_radius,
-                          color, is_outset);
+                          color);
   } else {
-    // If a focus ring is outset and the color is dark, it may be hard to see on
-    // dark backgrounds. In this case, we'll actually draw two focus rings, the
-    // outset focus ring with a white inner ring for contrast.
-    if (ShouldDrawInnerFocusRingForContrast(is_outset, width, color)) {
-      int contrast_offset = static_cast<int>(std::floor(width * 0.5));
-      // We create a 1px gap for the contrast ring. The contrast ring is drawn
-      // first, and we overdraw by a pixel to ensure no gaps or AA artifacts.
-      DrawFocusRingInternal(rects, contrast_offset, offset, border_radius,
-                            SK_ColorWHITE, is_outset);
-      DrawFocusRingInternal(rects, width - contrast_offset,
-                            offset + contrast_offset, border_radius, color,
-                            is_outset);
-
-    } else {
-      DrawFocusRingInternal(rects, width, offset, border_radius, color,
-                            is_outset);
-    }
+    DrawFocusRingInternal(rects, width, offset, border_radius, color);
   }
 }
 
diff --git a/third_party/blink/renderer/platform/graphics/graphics_context.h b/third_party/blink/renderer/platform/graphics/graphics_context.h
index 6e943b9..88784e6 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_context.h
+++ b/third_party/blink/renderer/platform/graphics/graphics_context.h
@@ -365,8 +365,7 @@
                      int default_offset,
                      float border_radius,
                      float min_border_width,
-                     const Color&,
-                     bool is_outset);
+                     const Color&);
   void DrawFocusRing(const Path&, float width, int offset, const Color&);
 
   enum Edge {
@@ -422,10 +421,7 @@
                                           FloatPoint& p2,
                                           float stroke_width);
 
-  static int FocusRingOutsetExtent(int offset,
-                                   int default_offset,
-                                   int width,
-                                   bool is_outset);
+  static int FocusRingOutsetExtent(int offset, int default_offset, int width);
 
   void SetInDrawingRecorder(bool);
   bool InDrawingRecorder() const { return in_drawing_recorder_; }
@@ -480,8 +476,7 @@
                              float width,
                              int offset,
                              float border_radius,
-                             const Color&,
-                             bool is_outset);
+                             const Color&);
 
   // SkCanvas wrappers.
   void ClipRRect(const SkRRect&,
diff --git a/third_party/blink/renderer/platform/graphics/graphics_layer.cc b/third_party/blink/renderer/platform/graphics/graphics_layer.cc
index 8cafc6b..22e4bd8f 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_layer.cc
+++ b/third_party/blink/renderer/platform/graphics/graphics_layer.cc
@@ -587,10 +587,6 @@
   mask_layer_ = mask_layer;
 }
 
-bool GraphicsLayer::BackfaceVisibility() const {
-  return CcLayer()->double_sided();
-}
-
 void GraphicsLayer::SetHitTestable(bool should_hit_test) {
   if (hit_testable_ == should_hit_test)
     return;
diff --git a/third_party/blink/renderer/platform/graphics/graphics_layer.h b/third_party/blink/renderer/platform/graphics/graphics_layer.h
index 14ed1bad1..710ce05 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_layer.h
+++ b/third_party/blink/renderer/platform/graphics/graphics_layer.h
@@ -153,8 +153,6 @@
   bool ContentsOpaque() const;
   void SetContentsOpaque(bool);
 
-  bool BackfaceVisibility() const;
-
   void SetHitTestable(bool);
   bool GetHitTestable() const { return hit_testable_; }
 
diff --git a/third_party/blink/renderer/platform/heap/concurrent_marking_test.cc b/third_party/blink/renderer/platform/heap/concurrent_marking_test.cc
index 8f02fc2..8491225 100644
--- a/third_party/blink/renderer/platform/heap/concurrent_marking_test.cc
+++ b/third_party/blink/renderer/platform/heap/concurrent_marking_test.cc
@@ -38,7 +38,7 @@
 
 template <typename C>
 void AddToCollection() {
-  constexpr int kIterations = 100;
+  constexpr int kIterations = 10;
   IncrementalMarkingTestDriver driver(ThreadState::Current());
   Persistent<CollectionWrapper<C>> persistent =
       MakeGarbageCollected<CollectionWrapper<C>>();
@@ -57,7 +57,7 @@
 
 template <typename C, typename GetLocation>
 void RemoveFromCollectionAtLocation(GetLocation location) {
-  constexpr int kIterations = 100;
+  constexpr int kIterations = 10;
   IncrementalMarkingTestDriver driver(ThreadState::Current());
   Persistent<CollectionWrapper<C>> persistent =
       MakeGarbageCollected<CollectionWrapper<C>>();
@@ -309,7 +309,7 @@
 // Additional test for vectors and deques
 template <typename V>
 void PopFromCollection() {
-  constexpr int kIterations = 100;
+  constexpr int kIterations = 10;
   IncrementalMarkingTestDriver driver(ThreadState::Current());
   Persistent<CollectionWrapper<V>> persistent =
       MakeGarbageCollected<CollectionWrapper<V>>();
@@ -379,7 +379,7 @@
 // HeapVector with inlined buffer
 
 template <typename T>
-class HeapInlinedVectorAdapter : public HeapVectorAdapter<T, 100> {};
+class HeapInlinedVectorAdapter : public HeapVectorAdapter<T, 10> {};
 
 TEST_F(ConcurrentMarkingTest, AddToInlinedVector) {
   AddToCollection<HeapInlinedVectorAdapter<Member<IntegerObject>>>();
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
index 9f7a84d5..5ee62b2f 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
@@ -55,12 +55,28 @@
   STATIC_ONLY(CrossThreadCopier);
 };
 
+template <>
+struct CrossThreadCopier<
+    std::vector<media::VideoEncodeAccelerator::Config::SpatialLayer>>
+    : public CrossThreadCopierPassThrough<
+          std::vector<media::VideoEncodeAccelerator::Config::SpatialLayer>> {
+  STATIC_ONLY(CrossThreadCopier);
+};
 }  // namespace WTF
 
 namespace blink {
 
 namespace {
 
+bool ConvertKbpsToBps(uint32_t bitrate_kbps, uint32_t* bitrate_bps) {
+  if (!base::IsValueInRangeForNumericType<uint32_t>(bitrate_kbps *
+                                                    UINT64_C(1000))) {
+    return false;
+  }
+  *bitrate_bps = bitrate_kbps * 1000;
+  return true;
+}
+
 webrtc::VideoEncoder::EncoderInfo CopyToWebrtcEncoderInfo(
     const media::VideoEncoderInfo& enc_info) {
   webrtc::VideoEncoder::EncoderInfo info;
@@ -93,6 +109,83 @@
   return info;
 }
 
+// Create VEA::Config::SpatialLayer from |codec_settings|. If some config of
+// |codec_settings| is not supported, returns false.
+bool CreateSpatialLayersConfig(
+    const webrtc::VideoCodec& codec_settings,
+    std::vector<media::VideoEncodeAccelerator::Config::SpatialLayer>*
+        spatial_layers) {
+  if (codec_settings.codecType == webrtc::kVideoCodecVP8 &&
+      codec_settings.mode == webrtc::VideoCodecMode::kScreensharing &&
+      codec_settings.VP8().numberOfTemporalLayers > 1) {
+    // This is a VP8 stream with screensharing using temporal layers for
+    // temporal scalability. Since this implementation does not yet implement
+    // temporal layers, fall back to software codec, if cfm and board is known
+    // to have a CPU that can handle it.
+    if (base::FeatureList::IsEnabled(features::kWebRtcScreenshareSwEncoding)) {
+      // TODO(sprang): Add support for temporal layers so we don't need
+      // fallback. See eg http://crbug.com/702017
+      DVLOG(1) << "Falling back to software encoder.";
+      return false;
+    }
+  }
+  if (codec_settings.codecType == webrtc::kVideoCodecVP9 &&
+      codec_settings.VP9().numberOfSpatialLayers > 1) {
+    DVLOG(1)
+        << "VP9 SVC not yet supported by HW codecs, falling back to sofware.";
+    return false;
+  }
+
+  // We fill SpatialLayer only in temporal layer or spatial layer encoding.
+  switch (codec_settings.codecType) {
+    case webrtc::kVideoCodecVP8:
+      if (codec_settings.VP8().numberOfTemporalLayers > 1) {
+        // Though there is no SVC in VP8 spec. We allocate 1 element in
+        // spatial_layers for temporal layer encoding.
+        spatial_layers->resize(1u);
+        auto& sl = (*spatial_layers)[0];
+        sl.width = codec_settings.width;
+        sl.height = codec_settings.height;
+        if (!ConvertKbpsToBps(codec_settings.startBitrate, &sl.bitrate_bps))
+          return false;
+        sl.framerate = codec_settings.maxFramerate;
+        sl.max_qp = base::saturated_cast<uint8_t>(codec_settings.qpMax);
+        sl.num_of_temporal_layers = base::saturated_cast<uint8_t>(
+            codec_settings.VP8().numberOfTemporalLayers);
+      }
+      break;
+    case webrtc::kVideoCodecVP9:
+      // Since one TL and one SL can be regarded as one simple stream,
+      // SpatialLayer is not filled.
+      if (codec_settings.VP9().numberOfTemporalLayers > 1 ||
+          codec_settings.VP9().numberOfSpatialLayers > 1) {
+        spatial_layers->resize(codec_settings.VP9().numberOfSpatialLayers);
+        for (size_t i = 0; i < spatial_layers->size(); ++i) {
+          const webrtc::SpatialLayer& rtc_sl = codec_settings.spatialLayers[i];
+          // We ignore non active spatial layer and don't proceed further. There
+          // must NOT be an active higher spatial layer than non active spatial
+          // layer.
+          if (!rtc_sl.active)
+            break;
+          auto& sl = (*spatial_layers)[i];
+          sl.width = base::checked_cast<int32_t>(rtc_sl.width);
+          sl.height = base::checked_cast<int32_t>(rtc_sl.height);
+          if (!ConvertKbpsToBps(rtc_sl.targetBitrate, &sl.bitrate_bps))
+            return false;
+          sl.bitrate_bps = rtc_sl.targetBitrate * 1000;
+          sl.framerate = base::saturated_cast<int32_t>(rtc_sl.maxFramerate);
+          sl.max_qp = base::saturated_cast<uint8_t>(rtc_sl.qpMax);
+          sl.num_of_temporal_layers =
+              base::saturated_cast<uint8_t>(rtc_sl.numberOfTemporalLayers);
+        }
+      }
+      break;
+    default:
+      break;
+  }
+  return true;
+}
+
 struct RTCTimestamps {
   RTCTimestamps(const base::TimeDelta& media_timestamp,
                 int32_t rtp_timestamp,
@@ -183,11 +276,14 @@
   // call.
   // RTCVideoEncoder expects to be able to call this function synchronously from
   // its own thread, hence the |async_waiter| and |async_retval| arguments.
-  void CreateAndInitializeVEA(const gfx::Size& input_visible_size,
-                              uint32_t bitrate,
-                              media::VideoCodecProfile profile,
-                              base::WaitableEvent* async_waiter,
-                              int32_t* async_retval);
+  void CreateAndInitializeVEA(
+      const gfx::Size& input_visible_size,
+      uint32_t bitrate,
+      media::VideoCodecProfile profile,
+      const std::vector<media::VideoEncodeAccelerator::Config::SpatialLayer>&
+          spatial_layers,
+      base::WaitableEvent* async_waiter,
+      int32_t* async_retval);
 
   webrtc::VideoEncoder::EncoderInfo GetEncoderInfo() const;
 
@@ -400,6 +496,8 @@
     const gfx::Size& input_visible_size,
     uint32_t bitrate,
     media::VideoCodecProfile profile,
+    const std::vector<media::VideoEncodeAccelerator::Config::SpatialLayer>&
+        spatial_layers,
     base::WaitableEvent* async_waiter,
     int32_t* async_retval) {
   DVLOG(3) << __func__;
@@ -462,7 +560,8 @@
       base::nullopt, base::nullopt, storage_type,
       video_content_type_ == webrtc::VideoContentType::SCREENSHARE
           ? media::VideoEncodeAccelerator::Config::ContentType::kDisplay
-          : media::VideoEncodeAccelerator::Config::ContentType::kCamera);
+          : media::VideoEncodeAccelerator::Config::ContentType::kCamera,
+      spatial_layers);
   if (!video_encoder_->Initialize(config, this)) {
     LogAndNotifyError(FROM_HERE, "Error initializing video_encoder",
                       media::VideoEncodeAccelerator::kInvalidArgumentError);
@@ -1012,7 +1111,8 @@
 }
 
 bool RTCVideoEncoder::Impl::IsBitrateTooHigh(uint32_t bitrate) {
-  if (base::IsValueInRangeForNumericType<uint32_t>(bitrate * UINT64_C(1000)))
+  uint32_t bitrate_bps = 0;
+  if (ConvertKbpsToBps(bitrate, &bitrate_bps))
     return false;
   LogAndNotifyError(FROM_HERE, "Overflow converting bitrate from kbps to bps",
                     media::VideoEncodeAccelerator::kInvalidArgumentError);
@@ -1135,33 +1235,17 @@
   if (impl_)
     Release();
 
-  if (codec_settings->codecType == webrtc::kVideoCodecVP8 &&
-      codec_settings->mode == webrtc::VideoCodecMode::kScreensharing &&
-      codec_settings->VP8().numberOfTemporalLayers > 1) {
-    // This is a VP8 stream with screensharing using temporal layers for
-    // temporal scalability. Since this implementation does not yet implement
-    // temporal layers, fall back to software codec, if cfm and board is known
-    // to have a CPU that can handle it.
-    if (base::FeatureList::IsEnabled(features::kWebRtcScreenshareSwEncoding)) {
-      // TODO(sprang): Add support for temporal layers so we don't need
-      // fallback. See eg http://crbug.com/702017
-      DVLOG(1) << "Falling back to software encoder.";
-      return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
-    }
-  }
-  if (codec_settings->codecType == webrtc::kVideoCodecVP9 &&
-      codec_settings->VP9().numberOfSpatialLayers > 1) {
-    DVLOG(1)
-        << "VP9 SVC not yet supported by HW codecs, falling back to sofware.";
-    return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
-  }
-
   impl_ =
       new Impl(gpu_factories_, ProfileToWebRtcVideoCodecType(profile_),
                (codec_settings->mode == webrtc::VideoCodecMode::kScreensharing)
                    ? webrtc::VideoContentType::SCREENSHARE
                    : webrtc::VideoContentType::UNSPECIFIED);
 
+  std::vector<media::VideoEncodeAccelerator::Config::SpatialLayer>
+      spatial_layers;
+  if (!CreateSpatialLayersConfig(*codec_settings, &spatial_layers))
+    return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
+
   // This wait is necessary because this task is completed in GPU process
   // asynchronously but WebRTC API is synchronous.
   base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow_wait;
@@ -1175,7 +1259,7 @@
           &RTCVideoEncoder::Impl::CreateAndInitializeVEA,
           scoped_refptr<Impl>(impl_),
           gfx::Size(codec_settings->width, codec_settings->height),
-          codec_settings->startBitrate, profile_,
+          codec_settings->startBitrate, profile_, spatial_layers,
           CrossThreadUnretained(&initialization_waiter),
           CrossThreadUnretained(&initialization_retval)));
 
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder_test.cc b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder_test.cc
index e6b01eda..4be9420 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder_test.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder_test.cc
@@ -122,6 +122,9 @@
       case webrtc::kVideoCodecH264:
         media_profile = media::H264PROFILE_BASELINE;
         break;
+      case webrtc::kVideoCodecVP9:
+        media_profile = media::VP9PROFILE_PROFILE0;
+        break;
       default:
         ADD_FAILURE() << "Unexpected codec type: " << codec_type;
         media_profile = media::VIDEO_CODEC_PROFILE_UNKNOWN;
@@ -231,6 +234,37 @@
                          Values(webrtc::kVideoCodecVP8,
                                 webrtc::kVideoCodecH264));
 
+TEST_F(RTCVideoEncoderTest, CreateAndInitSucceedsForTemporalLayer) {
+  const webrtc::VideoCodecType codec_type = webrtc::kVideoCodecVP9;
+  CreateEncoder(codec_type);
+  webrtc::VideoCodec codec{};
+  codec.codecType = codec_type;
+  codec.width = kInputFrameWidth;
+  codec.height = kInputFrameHeight;
+  codec.startBitrate = kStartBitrate;
+  codec.maxBitrate = codec.startBitrate * 2;
+  codec.minBitrate = codec.startBitrate / 2;
+  codec.maxFramerate = 24;
+  codec.active = true;
+  codec.qpMax = 30;
+  codec.numberOfSimulcastStreams = 1;
+  codec.mode = webrtc::VideoCodecMode::kRealtimeVideo;
+  webrtc::VideoCodecVP9& vp9 = *codec.VP9();
+  vp9.numberOfTemporalLayers = 3;
+  vp9.numberOfSpatialLayers = 1;
+  webrtc::SpatialLayer& sl = codec.spatialLayers[0];
+  sl.width = kInputFrameWidth;
+  sl.height = kInputFrameHeight;
+  sl.maxFramerate = 24;
+  sl.numberOfTemporalLayers = vp9.numberOfTemporalLayers;
+  sl.targetBitrate = kStartBitrate;
+  sl.maxBitrate = sl.targetBitrate;
+  sl.minBitrate = sl.targetBitrate;
+  sl.qpMax = 30;
+  sl.active = true;
+  EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, rtc_encoder_->InitEncode(&codec, 1, 12345));
+}
+
 // Checks that WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE is returned when there is
 // platform error.
 TEST_F(RTCVideoEncoderTest, SoftwareFallbackAfterError) {
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index ba5f4c63..7b2408e 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -509,6 +509,7 @@
     },
     {
       name: "CSSRevert",
+      status: "test",
       depends_on: ["CSSCascade"],
     },
     {
@@ -916,7 +917,7 @@
       // unit tests.
       name: "LayoutNG",
       // Keep this list in sync with the one in LayoutNGFlexBox below.
-      implied_by: ["LayoutNGBlockFragmentation", "LayoutNGFieldset", "LayoutNGFragmentItem", "LayoutNGLineCache", "EditingNG", "BidiCaretAffinity", "LayoutNGTable", "LayoutNGFragmentTraversal"],
+      implied_by: ["LayoutNGBlockFragmentation", "LayoutNGFieldset", "LayoutNGFragmentItem", "LayoutNGGrid", "LayoutNGLineCache", "EditingNG", "BidiCaretAffinity", "LayoutNGTable", "LayoutNGFragmentTraversal"],
       status: "stable",
     },
     {
@@ -927,7 +928,7 @@
     },
     {
       name: "LayoutNGFlexBox",
-      implied_by: ["LayoutNGBlockFragmentation", "LayoutNGFieldset", "LayoutNGFragmentItem", "LayoutNGLineCache", "EditingNG", "BidiCaretAffinity", "LayoutNGTable", "LayoutNGFragmentTraversal"],
+      implied_by: ["LayoutNGBlockFragmentation", "LayoutNGFieldset", "LayoutNGFragmentItem", "LayoutNGGrid", "LayoutNGLineCache", "EditingNG", "BidiCaretAffinity", "LayoutNGTable", "LayoutNGFragmentTraversal"],
       status: "experimental",
     },
     {
@@ -945,6 +946,9 @@
       name: "LayoutNGFragmentTraversal",
     },
     {
+      name: "LayoutNGGrid",
+    },
+    {
       name: "LayoutNGLineCache",
     },
     {
diff --git a/third_party/blink/web_tests/LeakExpectations b/third_party/blink/web_tests/LeakExpectations
index 86fe9c4d..ed29471 100644
--- a/third_party/blink/web_tests/LeakExpectations
+++ b/third_party/blink/web_tests/LeakExpectations
@@ -118,6 +118,9 @@
 # Sheriff 2019-12-30
 crbug.com/1038388 [ Linux ] http/tests/devtools/tracing/timeline-time/timeline-time.js [ Pass Failure ]
 
+# Sheriff 2020-04-03
+crbug.com/1067650 [ Linux ] virtual/threaded/http/tests/devtools/tracing/timeline-misc/timeline-load-event.js [ Pass Failure ]
+
 ###########################################################################
 # WARNING: Memory leaks must be fixed asap. Sheriff is expected to revert #
 # culprit CLs instead of suppressing the leaks. If you have any question, #
diff --git a/third_party/blink/web_tests/NeverFixTests b/third_party/blink/web_tests/NeverFixTests
index 75caad9..2b59006 100644
--- a/third_party/blink/web_tests/NeverFixTests
+++ b/third_party/blink/web_tests/NeverFixTests
@@ -312,8 +312,6 @@
 
 # WPT for CSS cascade
 crbug.com/763610 external/wpt/css/CSS2/cascade/* [ Skip ]
-# The revert keyword is not yet supported.
-external/wpt/css/css-cascade/revert-val-001.html [ Skip ]
 
 # These tests require a DRM system that is not available with content_shell.
 external/wpt/encrypted-media/drm-check-encryption-scheme.https.html [ Skip ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index a6932c6..d7eca9b1 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1841,10 +1841,6 @@
 crbug.com/791529 virtual/cascade/external/wpt/css/css-variables/variable-transitions-value-before-transition-property-variable.html [ Skip ]
 crbug.com/989123 virtual/cascade/external/wpt/css/css-typed-om/the-stylepropertymap/computed/get-auto-min-size.html [ Failure ]
 
-# This is for some reason WontFix'd in the main expectations (see NeverFixTests).
-# That seems wrong, as we should support 'revert' eventually.
-crbug.com/579788 virtual/cascade/external/wpt/css/css-cascade/revert-val-001.html [ Skip ]
-
 # Flaky tests:
 crbug.com/987224 virtual/cascade/external/wpt/css/css-typed-om/interfaces.html [ Pass Timeout ]
 crbug.com/987224 [ IOS ] virtual/cascade/external/wpt/css/css-paint-api/idlharness.html [ Pass Timeout ]
@@ -2745,7 +2741,6 @@
 #crbug.com/626703 [ Win ] external/wpt/css/css-flexbox/flex-minimum-width-flex-items-001.xht [ Failure ]
 #crbug.com/626703 [ Win ] external/wpt/css/css-flexbox/flex-minimum-width-flex-items-003.xht [ Failure ]
 #crbug.com/626703 [ Win ] external/wpt/css/css-flexbox/flexbox_flex-natural-mixed-basis-auto.html [ Failure ]
-#crbug.com/626703 external/wpt/css/css-cascade/revert-val-001.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-writing-modes/sizing-orthog-htb-in-vrl-013.xht [ Failure ]
 crbug.com/626703 external/wpt/css/css-writing-modes/sizing-orthog-vlr-in-htb-008.xht [ Failure ]
 crbug.com/626703 external/wpt/css/css-writing-modes/sizing-orthog-vlr-in-htb-020.xht [ Failure ]
diff --git a/third_party/blink/web_tests/css3/filters/backdrop-filter-rendering-no-background-expected.html b/third_party/blink/web_tests/css3/filters/backdrop-filter-rendering-no-background-expected.html
deleted file mode 100644
index d501199..0000000
--- a/third_party/blink/web_tests/css3/filters/backdrop-filter-rendering-no-background-expected.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<style> 
-
-div {
-  width: 100px;
-  height: 100px;
-  position :absolute;
-}
-
-.background_object {
-  left: 10px;
-  top: 10px;
-  background: orange;
-}
-
-.grayscale {
-  left: 60px;
-  top: 60px;
-  width: 50px;
-  height: 50px;
-  background: #ACACAC;
-}
-
-</style>
-</head>
-<body>
-  <div class="background_object"></div>
-  <div class="grayscale"></div>
-</body>
-</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/css3/filters/backdrop-filter-rendering-no-background.html b/third_party/blink/web_tests/css3/filters/backdrop-filter-rendering-no-background.html
deleted file mode 100644
index 8ec20083..0000000
--- a/third_party/blink/web_tests/css3/filters/backdrop-filter-rendering-no-background.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<style> 
-
-div {
-  width: 100px;
-  height: 100px;
-  position :absolute;
-}
-
-.background_object {
-  left: 10px;
-  top: 10px;
-  background: orange;
-}
-
-.grayscale_backdrop {
-  left: 60px;
-  top: 60px;
-  backdrop-filter: grayscale(100%);
-}
-
-</style>
-</head>
-<body>
-  <div class="background_object"></div>
-  <div class="grayscale_backdrop"></div>
-</body>
-</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/css3/filters/backdrop-filter-rendering.html b/third_party/blink/web_tests/css3/filters/backdrop-filter-rendering.html
deleted file mode 100644
index 38e343b0..0000000
--- a/third_party/blink/web_tests/css3/filters/backdrop-filter-rendering.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<style>
-
-div {
-  width: 100px;
-  height: 100px;
-  position :absolute;
-}
-
-.background_object {
-  left: 10px;
-  top: 10px;
-  background: orange;
-}
-
-.grayscale_backdrop {
-  left: 60px;
-  top: 60px;
-  background: #fff0;
-  backdrop-filter: grayscale(100%);
-}
-
-
-</style>
-</head>
-<body>
-  <div class="background_object"></div>
-  <div class="grayscale_backdrop"></div>
-</body>
-</html>
diff --git a/third_party/blink/web_tests/css3/filters/bug419429.html b/third_party/blink/web_tests/css3/filters/bug419429.html
deleted file mode 100644
index 35e3c752..0000000
--- a/third_party/blink/web_tests/css3/filters/bug419429.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<!DOCTYPE html>
-<marquee>
-  <li style="filter: url(#foo); color: white"></li>
-</marquee>
-<!-- Test passes by not crashing -->
-<p>PASS</p>
diff --git a/third_party/blink/web_tests/css3/flexbox/overflow-auto-intrinsic-size.html b/third_party/blink/web_tests/css3/flexbox/overflow-auto-intrinsic-size.html
new file mode 100644
index 0000000..a38ab2a5
--- /dev/null
+++ b/third_party/blink/web_tests/css3/flexbox/overflow-auto-intrinsic-size.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html>
+<title>CSS Flexbox: scrollbars on overflow auto accounted by intrinsic size.</title>
+<link rel="stylesheet" href="resources/flexbox.css">
+<link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#flex-direction-property">
+<link rel="help" href="https://www.w3.org/TR/CSS22/visufx.html#overflow">
+<link rel="issue" href="https://github.com/web-platform-tests/wpt/issues/22580#issue-591447665">
+<meta name="assert" content="How 'overflow: auto' elements contribute their scrollbar thickness to their intrinsic size is UA-specific behavior."/>
+<style>
+.flexbox {
+    border: 5px solid green;
+    position: relative;
+    width: 50px;
+}
+
+.inline-flexbox {
+    border: 5px solid green;
+    position: relative;
+    height: 50px;
+}
+
+.overflow {
+    border: 1px solid red;
+    overflow: auto;
+    min-width: 0;
+    min-height: 0;
+}
+
+.vertical {
+    writing-mode: vertical-rl;
+}
+</style>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="../../resources/check-layout-th.js"></script>
+<body onload="checkLayout('.flexbox, .inline-flexbox')">
+<div id=log></div>
+
+<div class="flexbox vertical to-be-checked" check-height check-accounts-scrollbar>
+  <div class="overflow"><div style="width: 100px; height: 20px"></div></div>
+</div>
+
+<div class="flexbox row-reverse vertical to-be-checked" check-height check-accounts-scrollbar>
+  <div class="overflow"><div style="width: 100px; height: 20px"></div></div>
+</div>
+
+<div class="inline-flexbox column to-be-checked" check-width check-accounts-scrollbar>
+  <div class="overflow"><div style="width: 20px; height: 100px"></div></div>
+</div>
+
+<div class="inline-flexbox column-reverse to-be-checked" check-width check-accounts-scrollbar>
+  <div class="overflow"><div style="width: 20px; height: 100px"></div></div>
+</div>
+
+<div class="inline-flexbox column to-be-checked" check-width check-accounts-scrollbar>
+  <div class="overflow align-self-baseline"><div style="width: 20px; height: 100px"></div></div>
+</div>
+
+<div class="inline-flexbox column-reverse to-be-checked" check-width check-accounts-scrollbar>
+  <div class="overflow align-self-baseline"><div style="width: 20px; height: 100px"></div></div>
+</div>
+
+<!-- This div is only for measuring scrollbar size -->
+<div id="measure" style="height: 100px; width: 100px; display: inline-block; overflow: auto;">
+  <div style="min-height: 300px;"></div>
+</div>
+
+<script>
+  var measure = document.getElementById('measure');
+  var scrollbarSize = measure.offsetWidth - measure.clientWidth;
+
+  var nodes = document.getElementsByClassName("to-be-checked");
+  for (var i = 0; i < nodes.length; i++) {
+    var node = nodes[i];
+
+    // Here, the things contributing height are:
+    //
+    // (a) each innermost div contributes an explicit height: 20px value.
+    // (b) the .overflow div contributes 2px of border (1px top + bottom),
+    //     plus the height of its scrollbar from overflow:auto.
+    // (c) the .flexbox div contributes 10px of border (5px top + bottom).
+    //
+    // So, the total height is 20px + 2px + 10px + scrollbarHeight,
+    // which simplifies to 32px + scrollbarHeight.
+    //
+    // Analogously, the same logic applies for nodes where width is tested.
+    var size =  32;
+    if (node.hasAttribute("check-height")) {
+      var height = node.hasAttribute("check-accounts-scrollbar") ? scrollbarSize : 0;
+      node.setAttribute("data-expected-height", size + height);
+    } else {
+      var width = node.hasAttribute("check-accounts-scrollbar") ? scrollbarSize : 0;
+      node.setAttribute("data-expected-width", size + width);
+    }
+  }
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/custom-elements/spec/create-element.html b/third_party/blink/web_tests/custom-elements/spec/create-element.html
index c1d78cb7..65b0e5e 100644
--- a/third_party/blink/web_tests/custom-elements/spec/create-element.html
+++ b/third_party/blink/web_tests/custom-elements/spec/create-element.html
@@ -37,7 +37,7 @@
 }
 
 test_with_window((w) => {
-  assert_throws_dom_exception(w, 'InvalidCharacterError', () => {
+  assert_throws_dom('InvalidCharacterError', w.DOMException, () => {
     w.document.createElement('.invalid.name.');
   });
 }, 'createElement   1. If localName does not match the Name production, then throw an InvalidCharacterError');
diff --git a/third_party/blink/web_tests/custom-elements/spec/custom-elements-registry/when_defined.html b/third_party/blink/web_tests/custom-elements/spec/custom-elements-registry/when_defined.html
index fa6a4ba..e674311 100644
--- a/third_party/blink/web_tests/custom-elements/spec/custom-elements-registry/when_defined.html
+++ b/third_party/blink/web_tests/custom-elements/spec/custom-elements-registry/when_defined.html
@@ -93,9 +93,9 @@
 
   function promise_rejects_with_dom_exception_syntax_error(global_context, test, promise, description) {
     return promise.then(test.unreached_func("Should have rejected: " + description)).catch(function(e) {
-        assert_throws_dom_exception(global_context, 'SYNTAX_ERR', function () { throw e; })         
+        assert_throws_dom('SYNTAX_ERR', global_context.DOMException, function () { throw e; })
       });
-    }  
+    }
 })();
 </script>
 </body>
diff --git a/third_party/blink/web_tests/custom-elements/spec/define-builtin-element.html b/third_party/blink/web_tests/custom-elements/spec/define-builtin-element.html
index fa2dc321..1d4f763 100644
--- a/third_party/blink/web_tests/custom-elements/spec/define-builtin-element.html
+++ b/third_party/blink/web_tests/custom-elements/spec/define-builtin-element.html
@@ -19,7 +19,7 @@
     'a.b-c'
   ];
   valid_custom_element_names.forEach((val) => {
-    assert_throws_dom_exception(w, 'NotSupportedError', () => {
+    assert_throws_dom('NotSupportedError', w.DOMException, () => {
       w.customElements.define('a-a', A, { extends: val });
     }, `having valid custon element name element interface (${val}) ` +
        'for extends should throw a NotSupportedError')
@@ -38,7 +38,7 @@
     42
   ]
   HTMLUnknownElement_names.forEach((val) => {
-    assert_throws_dom_exception(w, 'NotSupportedError', () => {
+    assert_throws_dom('NotSupportedError', w.DOMException, () => {
       w.customElements.define('a-a', A, { extends: val });
     }, `having element interface for extends (${val}) undefined in specs` +
        ' should throw a NotSupportedError');
diff --git a/third_party/blink/web_tests/custom-elements/spec/define-element.html b/third_party/blink/web_tests/custom-elements/spec/define-element.html
index 1475f06..f2f9969 100644
--- a/third_party/blink/web_tests/custom-elements/spec/define-element.html
+++ b/third_party/blink/web_tests/custom-elements/spec/define-element.html
@@ -47,7 +47,7 @@
   ];
   class X extends w.HTMLElement {}
   invalid_names.forEach((name) => {
-    assert_throws_dom_exception(w, 'SYNTAX_ERR', () => {
+    assert_throws_dom('SYNTAX_ERR', w.DOMException, () => {
       w.customElements.define(name, X);
     })
   });
@@ -57,7 +57,7 @@
   class X extends w.HTMLElement {}
   class Y extends w.HTMLElement {}
   w.customElements.define('a-a', X);
-  assert_throws_dom_exception(w, 'NotSupportedError', () => {
+  assert_throws_dom('NotSupportedError', w.DOMException, () => {
     w.customElements.define('a-a', Y);
   }, 'defining an element with a name that is already defined should throw ' +
      'a NotSupportedError');
@@ -68,7 +68,7 @@
   let X = (function () {}).bind({});
   Object.defineProperty(X, 'prototype', {
     get() {
-      assert_throws_dom_exception(w, 'NotSupportedError', () => {
+      assert_throws_dom('NotSupportedError', w.DOMException, () => {
         w.customElements.define('a-a', Y);
       }, 'defining an element with a name that is being defined should ' +
          'throw a NotSupportedError');
@@ -82,7 +82,7 @@
 test_with_window((w) => {
   class X extends w.HTMLElement {}
   w.customElements.define('a-a', X);
-  assert_throws_dom_exception(w, 'NotSupportedError', () => {
+  assert_throws_dom('NotSupportedError', w.DOMException, () => {
     w.customElements.define('a-b', X);
   }, 'defining an element with a constructor that is already in the ' +
      'registry should throw a NotSupportedError');
@@ -117,7 +117,7 @@
   let X = (function () {}).bind({});
   Object.defineProperty(X, 'prototype', {
     get() {
-      assert_throws_dom_exception(w, 'NotSupportedError', () => {
+      assert_throws_dom('NotSupportedError', w.DOMException, () => {
         w.customElements.define('second-name', X);
       }, 'defining an element with a constructor that is being defined ' +
          'should throw a NotSupportedError');
@@ -132,7 +132,7 @@
   let X = (function () {}).bind({});
   Object.defineProperty(X, 'prototype', {
     get() {
-      assert_throws_dom_exception(w, 'NotSupportedError', () => {
+      assert_throws_dom('NotSupportedError', w.DOMException, () => {
         w.customElements.define('second-name', class extends HTMLElement { });
       }, 'defining an element while element definition is running should ' +
          'throw a NotSupportedError');
@@ -189,7 +189,7 @@
 
   class C extends w.HTMLElement {}
   w.customElements.define('a-a', C);
-  assert_throws_dom_exception(w, 'SYNTAX_ERR', () => {
+  assert_throws_dom('SYNTAX_ERR', w.DOMException, () => {
     let invalid_name = 'annotation-xml';
     let reused_constructor = C;
     w.customElements.define(invalid_name, reused_constructor);
diff --git a/third_party/blink/web_tests/custom-elements/spec/resources/custom-elements-helpers.js b/third_party/blink/web_tests/custom-elements/spec/resources/custom-elements-helpers.js
index 500f3d0a..1f8a6025 100644
--- a/third_party/blink/web_tests/custom-elements/spec/resources/custom-elements-helpers.js
+++ b/third_party/blink/web_tests/custom-elements/spec/resources/custom-elements-helpers.js
@@ -21,22 +21,6 @@
   }, name);
 }
 
-// TODO(1066131): After https://github.com/web-platform-tests/wpt/pull/21876 is
-// rolled into Chromium, this function can be replaced with:
-//   assert_throws_dom(code, global_context.DOMException, func, description);
-function assert_throws_dom_exception(global_context, code, func, description) {
-  let exception;
-  assert_throws_dom(code, () => {
-    try {
-      func.call(this);
-    } catch(e) {
-      exception = e;
-      throw e;
-    }
-  }, description);
-  assert_true(exception instanceof global_context.DOMException, 'DOMException on the appropriate window');
-}
-
 function assert_array_equals_callback_invocations(actual, expected, description) {
   assert_equals(actual.length, expected.length);
   for (let len=actual.length, i=0; i<len; ++i) {
@@ -80,7 +64,7 @@
 // with the expected DOMException.
 function assert_reports_dom(w, expected_error, func, description) {
   const e = assert_reports_impl(w, func);
-  assert_throws_dom(expected_error, () => { throw e; }, description);
+  assert_throws_dom(expected_error, w.DOMException, () => { throw e; }, description);
 }
 
 // Asserts that func synchronously invokes the error event handler in w
diff --git a/third_party/blink/web_tests/custom-elements/v0-v1-interop.html b/third_party/blink/web_tests/custom-elements/v0-v1-interop.html
index ea32575..dcc2ff8 100644
--- a/third_party/blink/web_tests/custom-elements/v0-v1-interop.html
+++ b/third_party/blink/web_tests/custom-elements/v0-v1-interop.html
@@ -10,7 +10,7 @@
   class X extends w.HTMLElement {}
 
   w.customElements.define('new-old', X);
-  assert_throws_dom("NotSupportedError", () => {
+  assert_throws_dom("NotSupportedError", w.DOMException, () => {
     w.document.registerElement('new-old', {prototype: X.prototype});
   }, '"registering" (v0) a name already "defined" should throw');
 
@@ -18,7 +18,7 @@
     prototype: Object.create(w.HTMLElement.prototype)
   });
   class Y extends w.HTMLElement {}
-  assert_throws_dom("NotSupportedError", () => {
+  assert_throws_dom("NotSupportedError", w.DOMException, () => {
     w.customElements.define('old-new', Y);
   }, '"defining" (v1) a name already "registered" (v0) should throw');
 }, 'Overlapping old and new-style custom elements are not allowed');
diff --git a/third_party/blink/web_tests/editing/pasteboard/data-transfer-items.html b/third_party/blink/web_tests/editing/pasteboard/data-transfer-items.html
index 83a85e98..c55bda9 100644
--- a/third_party/blink/web_tests/editing/pasteboard/data-transfer-items.html
+++ b/third_party/blink/web_tests/editing/pasteboard/data-transfer-items.html
@@ -51,7 +51,7 @@
 
           // Check that an exception is properly raised when attempting to
           // add a duplicate string type.
-          assert_throws_dom('NotSupportedError',
+          assert_throws_dom('NotSupportedError', selection.window.DOMException,
               () => items.add('Moo', 'text/plain'),
               'Adding another text/plain data');
         });
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json
index 76204545..3b51492 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json
@@ -87659,6 +87659,18 @@
      {}
     ]
    ],
+   "css/css-transforms/preserve3d-overflow-percent.html": [
+    [
+     "css/css-transforms/preserve3d-overflow-percent.html",
+     [
+      [
+       "/common/blank.html",
+       "!="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-transforms/rotate/svg-rotate-3args-002.html": [
     [
      "css/css-transforms/rotate/svg-rotate-3args-002.html",
@@ -175103,6 +175115,18 @@
    "mathml/relations/css-styling/mathvariant-tailed-ref.html": [
     []
    ],
+   "mathml/relations/css-styling/padding-border-margin/border-002-expected.txt": [
+    []
+   ],
+   "mathml/relations/css-styling/padding-border-margin/margin-002-expected.txt": [
+    []
+   ],
+   "mathml/relations/css-styling/padding-border-margin/margin-003-expected.txt": [
+    []
+   ],
+   "mathml/relations/css-styling/padding-border-margin/padding-002-expected.txt": [
+    []
+   ],
    "mathml/relations/css-styling/padding-border-margin/padding-border-margin-001-ref.html": [
     []
    ],
@@ -240172,6 +240196,12 @@
      {}
     ]
    ],
+   "editing/other/move-inserted-node-from-DOMNodeInserted-during-exec-command-insertHTML.html": [
+    [
+     "editing/other/move-inserted-node-from-DOMNodeInserted-during-exec-command-insertHTML.html",
+     {}
+    ]
+   ],
    "editing/other/non-html-document.html": [
     [
      "editing/other/non-html-document.html",
@@ -275983,6 +276013,12 @@
      {}
     ]
    ],
+   "mathml/relations/css-styling/padding-border-margin/margin-003.html": [
+    [
+     "mathml/relations/css-styling/padding-border-margin/margin-003.html",
+     {}
+    ]
+   ],
    "mathml/relations/css-styling/padding-border-margin/padding-001.html": [
     [
      "mathml/relations/css-styling/padding-border-margin/padding-001.html",
@@ -290200,7 +290236,9 @@
    "origin-policy/ids/two-ids.https.html": [
     [
      "origin-policy/ids/two-ids.https.html",
-     {}
+     {
+      "timeout": "long"
+     }
     ]
    ],
    "page-visibility/idlharness.window.js": [
@@ -422131,6 +422169,10 @@
    "368784c74f51c774cf8ea1b6f55127e72fd5854e",
    "reftest"
   ],
+  "css/css-transforms/preserve3d-overflow-percent.html": [
+   "1c4962001e3f5f55c2a671f43abe217454cc2ae5",
+   "reftest"
+  ],
   "css/css-transforms/reference/backface-visibility-hidden-ref.html": [
    "6a3e305e5c0c7fca6c8d53574b7a09eb21afb6cc",
    "support"
@@ -452079,6 +452121,10 @@
    "2cd1232d00b8bdcf3a48ba01b3f2cfd05e27f094",
    "testharness"
   ],
+  "editing/other/move-inserted-node-from-DOMNodeInserted-during-exec-command-insertHTML.html": [
+   "41e012a62e9617f372f85d0a0cedefe8ad42fbd6",
+   "testharness"
+  ],
   "editing/other/non-html-document.html": [
    "ffd2e6f59464c56fac6694856978192ed03a199c",
    "testharness"
@@ -485967,6 +486013,10 @@
    "784491cf2e1bf60ea7c9edc951b9252d903d592f",
    "testharness"
   ],
+  "mathml/relations/css-styling/padding-border-margin/border-002-expected.txt": [
+   "fd33b8b3d2401a457424dca28d495c1ea5f71ecb",
+   "support"
+  ],
   "mathml/relations/css-styling/padding-border-margin/border-002.html": [
    "38d89aa186e1b78e4f51944c02b37574ef9ac651",
    "testharness"
@@ -485975,14 +486025,30 @@
    "e7e2584cc45215aabc608b006529d0799e7c7617",
    "testharness"
   ],
+  "mathml/relations/css-styling/padding-border-margin/margin-002-expected.txt": [
+   "12188ba6124b485fdeaa2d70ca69c2e8dd0b8275",
+   "support"
+  ],
   "mathml/relations/css-styling/padding-border-margin/margin-002.html": [
    "4853c8bee9c732187de150293785554c253f796c",
    "testharness"
   ],
+  "mathml/relations/css-styling/padding-border-margin/margin-003-expected.txt": [
+   "005624f4ec270ce62fee2d0db92d967b0684a14f",
+   "support"
+  ],
+  "mathml/relations/css-styling/padding-border-margin/margin-003.html": [
+   "5f0dbf4cc0edcdb406d53f74d55afa500865e392",
+   "testharness"
+  ],
   "mathml/relations/css-styling/padding-border-margin/padding-001.html": [
    "afcd930452655f2265cf5929c28200e0668a3cc4",
    "testharness"
   ],
+  "mathml/relations/css-styling/padding-border-margin/padding-002-expected.txt": [
+   "31be5578ca3ce7039b83731a70075487636fe85f",
+   "support"
+  ],
   "mathml/relations/css-styling/padding-border-margin/padding-002.html": [
    "e1425a0b1b92a26c38a8561fe742c3c5f69e4e6c",
    "testharness"
@@ -486240,7 +486306,7 @@
    "support"
   ],
   "mathml/support/mathml-fragments.js": [
-   "8376ccf375def30bec02e4226ea189140efcbe65",
+   "7e2113e95bf549155c9b30a6ca60a365fc384768",
    "support"
   ],
   "mathml/support/operator-dictionary.js": [
@@ -496484,7 +496550,7 @@
    "testharness"
   ],
   "origin-policy/ids/two-ids.https.html": [
-   "630f07280790e0631fe1c2e795194bd541ffee72",
+   "da9ab2e951171050bba0b91557657d1c953824b9",
    "testharness"
   ],
   "origin-policy/policies/README.md": [
@@ -512844,7 +512910,7 @@
    "testharness"
   ],
   "svg/animations/svglength-additive-by-8.html": [
-   "c61cb65ebd923f82d3c44ae00328e4712d214019",
+   "459fc7382a596292d913ab9643be6b32a32726e2",
    "testharness"
   ],
   "svg/animations/svglength-additive-from-by-1.html": [
@@ -513968,11 +514034,11 @@
    "testharness"
   ],
   "svg/painting/parsing/stroke-dasharray-invalid.svg": [
-   "0c356b6433955588c7b0ed32d9ff8c2eab4c7619",
+   "53a9640c8ffbcb2ec511ea808f02574e934e07b8",
    "testharness"
   ],
   "svg/painting/parsing/stroke-dasharray-valid.svg": [
-   "e47ebc62a1296f75c42af980b85073ebe135605b",
+   "9326118ceb886c64c936a0dbbf789b87f7302391",
    "testharness"
   ],
   "svg/painting/parsing/stroke-dashoffset-computed.svg": [
@@ -513980,15 +514046,15 @@
    "testharness"
   ],
   "svg/painting/parsing/stroke-dashoffset-invalid.svg": [
-   "64e2eec764a13bab403172a1568d085d745033d2",
+   "2040355e2282a59d95a5ace4e1114b0cd14c4741",
    "testharness"
   ],
   "svg/painting/parsing/stroke-dashoffset-valid-expected.txt": [
-   "a683e2f4a8a7d8d528dee4453b30899b59622dd5",
+   "2de3aab0e71b5a5ceee2b36d5846a583b8453b38",
    "support"
   ],
   "svg/painting/parsing/stroke-dashoffset-valid.svg": [
-   "f34774e68d7afa7a336ed7cbd30b44695451d74d",
+   "fe7ba12c8870a556b7fd66d74bb18bc5cbf84cf4",
    "testharness"
   ],
   "svg/painting/parsing/stroke-invalid.svg": [
@@ -514060,15 +514126,15 @@
    "testharness"
   ],
   "svg/painting/parsing/stroke-width-invalid.svg": [
-   "0d3f63d077f29a0a36f6443164dc7f24421a3f62",
+   "2111e376ac4000096cd13e939ecce1cb652e805f",
    "testharness"
   ],
   "svg/painting/parsing/stroke-width-valid-expected.txt": [
-   "9ec8028894ac20908757d53235c2a1438061fad2",
+   "e9b028d4eb427f694d430fdc9d27eb353c6a90f5",
    "support"
   ],
   "svg/painting/parsing/stroke-width-valid.svg": [
-   "f90781284dcf54b2e864e0607ae3e880a40531e1",
+   "1ee0449afae675b84f1118875ae21c3c7aee67cc",
    "testharness"
   ],
   "svg/painting/parsing/text-rendering-computed.svg": [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-cascade/revert-val-004.html b/third_party/blink/web_tests/external/wpt/css/css-cascade/revert-val-004.html
new file mode 100644
index 0000000..6a7046c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-cascade/revert-val-004.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<title>CSS Cascade: using 'revert' with the 'all' property</title>
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#default">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  div {
+    display: inline;
+  }
+  .revert {
+    all: revert;
+  }
+</style>
+<div id=div></div>
+<script>
+  test(function() {
+    let cs = getComputedStyle(div);
+    assert_equals(cs.display, 'inline');
+    div.className = 'revert';
+    assert_equals(cs.display, 'block');
+  }, 'The revert keyword works with the all property');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-color-adjust/parsing/color-scheme-computed-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-color-adjust/parsing/color-scheme-computed-expected.txt
deleted file mode 100644
index d6ed092..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-color-adjust/parsing/color-scheme-computed-expected.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-This is a testharness.js-based test.
-PASS Property color-scheme value 'normal'
-PASS Property color-scheme value 'light dark'
-PASS Property color-scheme value 'dark light'
-PASS Property color-scheme value 'light unknown'
-PASS Property color-scheme value 'only light'
-PASS Property color-scheme value 'light light'
-PASS Property color-scheme value 'light only'
-PASS Property color-scheme value 'none'
-PASS Property color-scheme value 'initial'
-PASS Property color-scheme value 'inherit'
-PASS Property color-scheme value 'unset'
-FAIL Property color-scheme value 'revert' assert_true: 'revert' is a supported value for color-scheme. expected true got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/css/css-color-adjust/parsing/color-scheme-valid-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-color-adjust/parsing/color-scheme-valid-expected.txt
deleted file mode 100644
index ec130a5..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-color-adjust/parsing/color-scheme-valid-expected.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-This is a testharness.js-based test.
-PASS e.style['color-scheme'] = "normal" should set the property value
-PASS e.style['color-scheme'] = "light" should set the property value
-PASS e.style['color-scheme'] = "dark" should set the property value
-PASS e.style['color-scheme'] = "light dark" should set the property value
-PASS e.style['color-scheme'] = "dark light" should set the property value
-PASS e.style['color-scheme'] = "only light" should set the property value
-PASS e.style['color-scheme'] = "light only" should set the property value
-PASS e.style['color-scheme'] = "light light" should set the property value
-PASS e.style['color-scheme'] = "dark dark" should set the property value
-PASS e.style['color-scheme'] = "light purple" should set the property value
-PASS e.style['color-scheme'] = "purple dark interesting" should set the property value
-PASS e.style['color-scheme'] = "none" should set the property value
-PASS e.style['color-scheme'] = "light none" should set the property value
-PASS e.style['color-scheme'] = "inherit" should set the property value
-PASS e.style['color-scheme'] = "initial" should set the property value
-PASS e.style['color-scheme'] = "unset" should set the property value
-FAIL e.style['color-scheme'] = "revert" should set the property value assert_not_equals: property should be set got disallowed value ""
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/overflow-auto-006.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/overflow-auto-006.html
index 4fda7eff..be0f8f1 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-flexbox/overflow-auto-006.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/overflow-auto-006.html
@@ -42,14 +42,6 @@
   <div class="overflow"><div style="width: 100px; height: 20px"></div></div>
 </div>
 
-<div class="flexbox vertical to-be-checked" check-height check-accounts-scrollbar>
-  <div class="overflow"><div style="width: 100px; height: 20px"></div></div>
-</div>
-
-<div class="flexbox row-reverse vertical to-be-checked" check-height check-accounts-scrollbar>
-  <div class="overflow"><div style="width: 100px; height: 20px"></div></div>
-</div>
-
 <div class="flexbox to-be-checked" check-height check-accounts-scrollbar>
   <div class="overflow align-self-baseline"><div style="width: 100px; height: 20px"></div></div>
 </div>
@@ -66,14 +58,6 @@
   <div class="overflow align-self-baseline"><div style="width: 100px; height: 20px"></div></div>
 </div>
 
-<div class="inline-flexbox column to-be-checked" check-width check-accounts-scrollbar>
-  <div class="overflow"><div style="width: 20px; height: 100px"></div></div>
-</div>
-
-<div class="inline-flexbox column-reverse to-be-checked" check-width check-accounts-scrollbar>
-  <div class="overflow"><div style="width: 20px; height: 100px"></div></div>
-</div>
-
 <div class="inline-flexbox column vertical to-be-checked" check-width check-accounts-scrollbar>
   <div class="overflow"><div style="width: 20px; height: 100px"></div></div>
 </div>
@@ -82,14 +66,6 @@
   <div class="overflow"><div style="width: 20px; height: 100px"></div></div>
 </div>
 
-<div class="inline-flexbox column to-be-checked" check-width check-accounts-scrollbar>
-  <div class="overflow align-self-baseline"><div style="width: 20px; height: 100px"></div></div>
-</div>
-
-<div class="inline-flexbox column-reverse to-be-checked" check-width check-accounts-scrollbar>
-  <div class="overflow align-self-baseline"><div style="width: 20px; height: 100px"></div></div>
-</div>
-
 <div class="inline-flexbox column vertical to-be-checked" check-width>
   <div class="overflow align-self-baseline"><div style="width: 20px; height: 100px"></div></div>
 </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-forced-color-adjust/parsing/forced-color-adjust-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-forced-color-adjust/parsing/forced-color-adjust-invalid.html
index 6329305a..464b072 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-forced-color-adjust/parsing/forced-color-adjust-invalid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-forced-color-adjust/parsing/forced-color-adjust-invalid.html
@@ -10,6 +10,5 @@
   test_invalid_value("forced-color-adjust", "none none");
   test_invalid_value("forced-color-adjust", "none auto");
   test_invalid_value("forced-color-adjust", "1");
-  test_invalid_value("forced-color-adjust", "revert");
   test_invalid_value("forced-color-adjust", "default");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/property-cascade.html b/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/property-cascade.html
index 5f0b7eb50..bb50213d 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/property-cascade.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/property-cascade.html
@@ -1,6 +1,6 @@
 <!DOCTYPE html>
 <link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/#the-registerproperty-function" />
-<meta name="assert" content="Verifies that registering a propety does not affect the cascade" />
+<meta name="assert" content="Verifies that registering a property does not affect the cascade" />
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/registered-property-revert.html b/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/registered-property-revert.html
new file mode 100644
index 0000000..3d0473ea
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/registered-property-revert.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1" />
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#default" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+    #parent {
+        --inherited: 10px;
+        --non-inherited: 10px;
+    }
+    #child {
+        --inherited: 20px;
+        --non-inherited: 20px;
+        --inherited: revert;
+        --non-inherited: revert;
+    }
+</style>
+<div id=parent>
+    <div id=child>
+    </div>
+</div>
+<script>
+
+CSS.registerProperty({
+    name: "--inherited",
+    syntax: "<length>",
+    initialValue: "0px",
+    inherits: true
+});
+
+CSS.registerProperty({
+    name: "--non-inherited",
+    syntax: "<length>",
+    initialValue: "0px",
+    inherits: false
+});
+
+test(function(){
+    let cs = getComputedStyle(child);
+    assert_equals(cs.getPropertyValue('--inherited'), '10px');
+}, 'Inherited registered custom property can be reverted');
+
+test(function(){
+    let cs = getComputedStyle(child);
+    assert_equals(cs.getPropertyValue('--non-inherited'), '0px');
+}, 'Non-inherited registered custom property can be reverted');
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/preserve3d-overflow-percent.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/preserve3d-overflow-percent.html
new file mode 100644
index 0000000..1c49620
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/preserve3d-overflow-percent.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Overflow with preserve-3d and percentage-transformed parent is computed using the right reference box</title>
+<link rel="author" title="Mozilla" href="https://mozilla.org">
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-transforms-2/#transform-style-property">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1626840">
+<link rel="mismatch" href="/common/blank.html">
+<style>
+:root {
+  overflow: hidden;
+}
+body {
+  margin: 0;
+  font-size: 28px;
+}
+#map {
+  width: 100%;
+  height: 100%;
+  backface-visibility: hidden;
+}
+.floor {
+  width: 100%;
+  height: 100vh;
+}
+.skew {
+	height: 20em;
+	width: 20em;
+	position: relative;
+	left: 50%;
+	top: calc(50% + 1.5em);
+	transform-origin: 0% 0%;
+	transform: rotateX(45deg) rotateZ(-45deg) translateX(-50%) translateY(-50%);
+	transform-style: preserve-3d;
+}
+.cylinder {
+	position: absolute;
+  background-color: green;
+  transform-style: preserve-3d;
+  width: 7em;
+  height: 7em;
+  transform: translateZ(1em);
+}
+</style>
+<div id="map">
+  <div class="floor">
+    <div class="skew" style="width: 13em; height: 47em; left: calc(50% + 0em); top: calc(50% + 0.5em);">
+      <div class="cylinder" style="left: calc(1.5em + 0em); top: calc(38em + 0em);"></div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/resources/testsuite.js b/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/resources/testsuite.js
index 27042c78..0ec2566 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/resources/testsuite.js
+++ b/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/resources/testsuite.js
@@ -33,6 +33,10 @@
     description: 'unset keyword',
     input: new CSSKeywordValue('initial')
   },
+  {
+    description: 'revert keyword',
+    input: new CSSKeywordValue('revert')
+  },
 ];
 
 const gVarReferenceExamples = [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-variables/variable-definition-keywords-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-variables/variable-definition-keywords-expected.txt
deleted file mode 100644
index a941a61..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-variables/variable-definition-keywords-expected.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-This is a testharness.js-based test.
-PASS computed style inherit
-PASS computed style initial
-PASS computed style unset
-FAIL computed style revert assert_equals: expected "revert" but got "20px"
-PASS specified style inherit
-PASS specified style initial
-PASS specified style unset
-FAIL specified style revert assert_equals: expected " revert" but got "revert"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/css/css-will-change/parsing/will-change-invalid-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-will-change/parsing/will-change-invalid-expected.txt
index 321eebf..c820d53 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-will-change/parsing/will-change-invalid-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/css/css-will-change/parsing/will-change-invalid-expected.txt
@@ -6,6 +6,7 @@
 PASS e.style['will-change'] = "transform, initial" should not set the property value
 PASS e.style['will-change'] = "transform, inherit" should not set the property value
 FAIL e.style['will-change'] = "transform, unset" should not set the property value assert_equals: expected "" but got "transform"
+PASS e.style['will-change'] = "transform, revert" should not set the property value
 PASS e.style['will-change'] = "transform, default" should not set the property value
 PASS e.style['will-change'] = "will-change" should not set the property value
 PASS e.style['will-change'] = "none" should not set the property value
diff --git a/third_party/blink/web_tests/external/wpt/css/css-will-change/parsing/will-change-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-will-change/parsing/will-change-invalid.html
index 83710ddbc..aa99676b 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-will-change/parsing/will-change-invalid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-will-change/parsing/will-change-invalid.html
@@ -22,6 +22,7 @@
 test_invalid_value("will-change", "transform, initial");
 test_invalid_value("will-change", "transform, inherit");
 test_invalid_value("will-change", "transform, unset");
+test_invalid_value("will-change", "transform, revert");
 test_invalid_value("will-change", "transform, default");
 
 // will-change additionally excludes the following from <custom-ident>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filters-grayscale-001.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filters-grayscale-001.html
new file mode 100644
index 0000000..75fe110b8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filters-grayscale-001.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<title>CSS Backdrop Filters: Grayscale(50%)</title>
+<link rel="author" title="Mason Freed" href="mailto:masonfreed@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects-2/#BackdropFilterProperty">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects-1/#supported-filter-functions">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=831485">
+<link rel="match" href="reference/backdrop-filters-grayscale-001-ref.html">
+<meta name="assert" content="Check that backdrop-filter works with grayscale(50%)."/>
+<style>
+.square {
+    position: absolute;
+    width: 100px;
+    height: 100px;
+    top: 100px;
+    left: 100px;
+    background: blue;
+}
+
+.filt {
+    backdrop-filter: grayscale(50%);
+    position: absolute;
+    width: 200px;
+    height: 200px;
+    top: 50px;
+    left: 50px;
+}
+
+.greenbox {
+    position: absolute;
+    width: 50px;
+    height: 50px;
+    top: 75px;
+    left: 75px;
+    background: green;
+}
+</style>
+<p>You should see a dark blue rectangle with a green box.</p>
+<div class="square"></div>
+<div class="filt">
+    <div class="greenbox"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filters-grayscale-002.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filters-grayscale-002.html
new file mode 100644
index 0000000..971bed8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filters-grayscale-002.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Backdrop Filters: Grayscale(100%) with transparent background color</title>
+<link rel="author" title="Hendrik Wagenaar" href="mailto:hendrikw@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects-2/#BackdropFilterProperty">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects-1/#supported-filter-functions">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=497522">
+<link rel="match" href="reference/backdrop-filters-grayscale-002-ref.html">
+<meta name="assert" content="Check that backdrop-filter works with grayscale(100%) and a transparent background color."/>
+
+<style>
+div {
+  width: 100px;
+  height: 100px;
+  position: absolute;
+}
+
+.background_object {
+  left: 10px;
+  top: 10px;
+  background: orange;
+}
+
+.grayscale_backdrop {
+  left: 60px;
+  top: 60px;
+  background: #fff0;
+  backdrop-filter: grayscale(100%);
+}
+</style>
+</head>
+
+<body>
+  <div class="background_object"></div>
+  <div class="grayscale_backdrop"></div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filters-grayscale-003.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filters-grayscale-003.html
new file mode 100644
index 0000000..816846d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filters-grayscale-003.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Backdrop Filters: Grayscale(100%) with no background</title>
+<link rel="author" title="Jay Dasika" href="mailto:jaydasika@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects-2/#BackdropFilterProperty">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects-1/#supported-filter-functions">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=527452">
+<link rel="match" href="reference/backdrop-filters-grayscale-003-ref.html">
+<meta name="assert" content="Check that backdrop-filter works with grayscale(100%) and no background."/>
+
+<style>
+div {
+  width: 100px;
+  height: 100px;
+  position: absolute;
+}
+
+.background_object {
+  left: 10px;
+  top: 10px;
+  background: orange;
+}
+
+.grayscale_backdrop {
+  left: 60px;
+  top: 60px;
+  backdrop-filter: grayscale(100%);
+}
+</style>
+</head>
+
+<body>
+  <div class="background_object"></div>
+  <div class="grayscale_backdrop"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filters-grayscale.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filters-grayscale.html
deleted file mode 100644
index 925c83cd..0000000
--- a/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filters-grayscale.html
+++ /dev/null
@@ -1,36 +0,0 @@
-<!DOCTYPE html>
-<title>CSS Backdrop Filters Animation: Grayscale</title>
-<link rel="author" title="Mason Freed" href="mailto:masonfreed@chromium.org">
-<link rel="help" href="https://drafts.fxtf.org/filter-effects-2/#BackdropFilterProperty">
-<link rel="match" href="backdrop-filters-grayscale-ref.html">
-<style>
-    .square {
-        position: absolute;
-        width: 100px;
-        height: 100px;
-        top: 100px;
-        left: 100px;
-        background: blue;
-    }
-    .filt {
-        backdrop-filter: grayscale(50%);
-        position: absolute;
-        width: 200px;
-        height: 200px;
-        top: 50px;
-        left: 50px;
-    }
-    .greenbox {
-        position: absolute;
-        width: 50px;
-        height: 50px;
-        top: 75px;
-        left: 75px;
-        background: green;
-    }
-</style>
-<p>You should see a dark blue rectangle with a green box.</p>
-<div class="square"></div>
-<div class="filt">
-    <div class="greenbox"></div>
-</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/filter-url-to-non-existent-filter-001.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/filter-url-to-non-existent-filter-001.html
new file mode 100644
index 0000000..6351c90
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/filter-url-to-non-existent-filter-001.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>CSS Filters: url() with non-existent filter reference</title>
+<link rel="author" title="Justin Novosad" href="mailto:junov@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects-1/#FilterProperty">
+<link rel="help" href="https://drafts.csswg.org/css-values-4/#local-urls">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=419429">
+<link rel="match" href="reference/filter-url-to-non-existent-filter-001-ref.html">
+<meta name="assert" content="Tests that a non-existent filter reference is gracefully ignored."/>
+
+<marquee>
+  <li style="filter: url(#foo); color: white"></li>
+</marquee>
+<!-- Test passes by not crashing -->
+<p>PASS</p>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filters-grayscale-ref.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/backdrop-filters-grayscale-001-ref.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filters-grayscale-ref.html
rename to third_party/blink/web_tests/external/wpt/css/filter-effects/reference/backdrop-filters-grayscale-001-ref.html
diff --git a/third_party/blink/web_tests/css3/filters/backdrop-filter-rendering-expected.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/backdrop-filters-grayscale-002-ref.html
similarity index 92%
copy from third_party/blink/web_tests/css3/filters/backdrop-filter-rendering-expected.html
copy to third_party/blink/web_tests/external/wpt/css/filter-effects/reference/backdrop-filters-grayscale-002-ref.html
index 0ea8694..b241017 100644
--- a/third_party/blink/web_tests/css3/filters/backdrop-filter-rendering-expected.html
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/backdrop-filters-grayscale-002-ref.html
@@ -2,11 +2,10 @@
 <html>
 <head>
 <style>
-
 div {
   width: 100px;
   height: 100px;
-  position :absolute;
+  position: absolute;
 }
 
 .background_object {
@@ -22,7 +21,6 @@
   height: 50px;
   background: #ACACAC;
 }
-
 </style>
 </head>
 <body>
diff --git a/third_party/blink/web_tests/css3/filters/backdrop-filter-rendering-expected.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/backdrop-filters-grayscale-003-ref.html
similarity index 92%
rename from third_party/blink/web_tests/css3/filters/backdrop-filter-rendering-expected.html
rename to third_party/blink/web_tests/external/wpt/css/filter-effects/reference/backdrop-filters-grayscale-003-ref.html
index 0ea8694..9ce975c 100644
--- a/third_party/blink/web_tests/css3/filters/backdrop-filter-rendering-expected.html
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/backdrop-filters-grayscale-003-ref.html
@@ -2,11 +2,10 @@
 <html>
 <head>
 <style>
-
 div {
   width: 100px;
   height: 100px;
-  position :absolute;
+  position: absolute;
 }
 
 .background_object {
@@ -22,11 +21,10 @@
   height: 50px;
   background: #ACACAC;
 }
-
 </style>
 </head>
 <body>
   <div class="background_object"></div>
   <div class="grayscale"></div>
 </body>
-</html>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/css3/filters/bug419429-expected.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/reference/filter-url-to-non-existent-filter-001-ref.html
similarity index 100%
rename from third_party/blink/web_tests/css3/filters/bug419429-expected.html
rename to third_party/blink/web_tests/external/wpt/css/filter-effects/reference/filter-url-to-non-existent-filter-001-ref.html
diff --git a/third_party/blink/web_tests/external/wpt/editing/other/move-inserted-node-from-DOMNodeInserted-during-exec-command-insertHTML.html b/third_party/blink/web_tests/external/wpt/editing/other/move-inserted-node-from-DOMNodeInserted-during-exec-command-insertHTML.html
new file mode 100644
index 0000000..41e012a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/editing/other/move-inserted-node-from-DOMNodeInserted-during-exec-command-insertHTML.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div contenteditable>
+<p id="p1"><br></p>
+<p id="p2"></p>
+</div>
+<script>
+"use strict";
+let editor = document.querySelector("[contenteditable]");
+let p1 = document.getElementById("p1");
+let p2 = document.getElementById("p2");
+p1.addEventListener("DOMNodeInserted", event => {
+  if (event.target.localName === "i") {
+    p2.appendChild(event.target);
+  }
+});
+document.getSelection().collapse(p1, 0);
+document.execCommand("insertHTML", false,
+                     "<b>bold1</b><i>italic1</i><b>bold2</b><i>italic2</i>");
+test(function () {
+  assert_in_array(p1.innerHTML, ["<b>bold1</b><b>bold2</b><br>", "<b>bold1</b><b>bold2</b>"]);
+}, "First <p> element should have only <b> elements");
+test(function () {
+  assert_equals(p2.innerHTML, "<i>italic1</i><i>italic2</i>");
+}, "Second <p> element should have only <i> elements");
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/image-parse-url-base.html b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/image-parse-url-base.html
new file mode 100644
index 0000000..f8201c49
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/image-parse-url-base.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Image load parses URL after microtask runs</title>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<img id="img">
+<script>
+const t = async_test("An image request's parsed URL should be affected by a " +
+                     "dynamically-inserted <base>, if it was inserted before " +
+                     "the image request microtask runs");
+
+t.step(() => {
+  const elm = document.getElementById('img');
+  elm.src = 'resources/image.png';
+  elm.onload = t.unreached_func("The image should have failed to load, as " +
+                                "the request URL should be affected by the " +
+                                "<base> element");
+  elm.onerror = t.step_func_done();
+
+  const base = document.createElement("base");
+  base.setAttribute("href", "bogus/");
+  document.head.appendChild(base);
+});
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/relevant-mutations-expected.txt b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/relevant-mutations-expected.txt
index 4c673bc0..e1d49827 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/relevant-mutations-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/relevant-mutations-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 81 tests; 74 PASS, 7 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 83 tests; 76 PASS, 7 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS src set
 PASS src changed
 PASS src removed
@@ -20,6 +20,7 @@
 PASS crossorigin use-credentials to absent, src absent
 PASS crossorigin use-credentials to empty, src absent
 PASS crossorigin use-credentials to anonymous, src absent
+PASS crossorigin use-credentials to invalid, src absent
 PASS crossorigin absent to empty, src already set
 PASS crossorigin absent to anonymous, src already set
 PASS crossorigin absent to use-credentials, src already set
@@ -30,6 +31,7 @@
 PASS crossorigin use-credentials to absent, src already set
 PASS crossorigin use-credentials to empty, src already set
 PASS crossorigin use-credentials to anonymous, src already set
+PASS crossorigin use-credentials to invalid, src already set
 FAIL inserted into picture assert_unreached: update the image data didn't run Reached unreachable code
 FAIL removed from picture assert_unreached: update the image data didn't run Reached unreachable code
 PASS parent is picture, previous source inserted
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/relevant-mutations.html b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/relevant-mutations.html
index 8b8ce7d..db9a7ee 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/relevant-mutations.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/relevant-mutations.html
@@ -31,6 +31,7 @@
 <img crossorigin=use-credentials data-desc="crossorigin use-credentials to absent, src absent">
 <img crossorigin=use-credentials data-desc="crossorigin use-credentials to empty, src absent">
 <img crossorigin=use-credentials data-desc="crossorigin use-credentials to anonymous, src absent">
+<img crossorigin=use-credentials data-desc="crossorigin use-credentials to invalid, src absent">
 
 <img src="/images/green-2x2.png" data-desc="crossorigin absent to empty, src already set">
 <img src="/images/green-2x2.png" data-desc="crossorigin absent to anonymous, src already set">
@@ -42,6 +43,7 @@
 <img src="/images/green-2x2.png" crossorigin=use-credentials data-desc="crossorigin use-credentials to absent, src already set">
 <img src="/images/green-2x2.png" crossorigin=use-credentials data-desc="crossorigin use-credentials to empty, src already set">
 <img src="/images/green-2x2.png" crossorigin=use-credentials data-desc="crossorigin use-credentials to anonymous, src already set">
+<img src="/images/green-2x2.png" crossorigin=use-credentials data-desc="crossorigin use-credentials to invalid, src already set">
 
 <img src="/images/green-2x2.png" data-desc="inserted into picture"><picture></picture>
 
@@ -220,6 +222,10 @@
     img.crossOrigin = 'anonymous';
   }, 'timeout');
 
+  t('crossorigin use-credentials to invalid, src absent', function(img) {
+    img.crossOrigin = 'foobar';
+  }, 'timeout');
+
   // When src is set, changing the crossorigin attribute state MUST generate
   // events.
 
@@ -263,6 +269,10 @@
     img.crossOrigin = 'anonymous';
   }, 'load');
 
+  t('crossorigin use-credentials to invalid, src already set', function(img) {
+    img.crossOrigin = 'foobar';
+  }, 'load');
+
   t('inserted into picture', function(img) {
     img.nextSibling.appendChild(img);
   }, 'load');
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.center-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.center-expected.txt
deleted file mode 100644
index ba4acff1..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.center-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL textAlign center is the center of the em squares (not the bounding box) assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.center.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.center.html
index 4a39529..3dfd54b 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.center.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.center.html
@@ -20,9 +20,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.center.worker-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.center.worker-expected.txt
deleted file mode 100644
index ba4acff1..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.center.worker-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL textAlign center is the center of the em squares (not the bounding box) assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.center.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.center.worker.js
index e252bd4..41df11c 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.center.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.center.worker.js
@@ -16,9 +16,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.end.ltr-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.end.ltr-expected.txt
deleted file mode 100644
index 0400d43..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.end.ltr-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL textAlign end with ltr is the right edge assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.end.ltr.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.end.ltr.html
index 0db1858..9048f3c 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.end.ltr.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.end.ltr.html
@@ -20,9 +20,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.end.ltr.worker-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.end.ltr.worker-expected.txt
deleted file mode 100644
index 0400d43..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.end.ltr.worker-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL textAlign end with ltr is the right edge assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.end.ltr.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.end.ltr.worker.js
index c059df5..8566b5b9 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.end.ltr.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.end.ltr.worker.js
@@ -16,9 +16,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.end.rtl.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.end.rtl.html
index 9f8f8c4..db7586c 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.end.rtl.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.end.rtl.html
@@ -20,9 +20,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.end.rtl.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.end.rtl.worker.js
index 89011c3..be84626 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.end.rtl.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.end.rtl.worker.js
@@ -16,9 +16,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.left-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.left-expected.txt
deleted file mode 100644
index b05d263e..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.left-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL textAlign left is the left of the first em square (not the bounding box) assert_approx_equals: Red channel of the pixel at (95, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.left.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.left.html
index a57aa950..ce394fb 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.left.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.left.html
@@ -20,9 +20,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.left.worker-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.left.worker-expected.txt
deleted file mode 100644
index b05d263e..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.left.worker-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL textAlign left is the left of the first em square (not the bounding box) assert_approx_equals: Red channel of the pixel at (95, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.left.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.left.worker.js
index 90964286..f224b95 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.left.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.left.worker.js
@@ -16,9 +16,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.right-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.right-expected.txt
deleted file mode 100644
index f8b5d31..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.right-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL textAlign right is the right of the last em square (not the bounding box) assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.right.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.right.html
index 087b379..aeb26b5 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.right.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.right.html
@@ -20,9 +20,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.right.worker-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.right.worker-expected.txt
deleted file mode 100644
index f8b5d31..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.right.worker-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL textAlign right is the right of the last em square (not the bounding box) assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.right.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.right.worker.js
index 1af5da3..80cf656 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.right.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.right.worker.js
@@ -16,9 +16,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.start.ltr-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.start.ltr-expected.txt
deleted file mode 100644
index cb82dc1c..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.start.ltr-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL textAlign start with ltr is the left edge assert_approx_equals: Red channel of the pixel at (95, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.start.ltr.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.start.ltr.html
index 72af4d3..ee4f1517 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.start.ltr.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.start.ltr.html
@@ -20,9 +20,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.start.ltr.worker-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.start.ltr.worker-expected.txt
deleted file mode 100644
index cb82dc1c..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.start.ltr.worker-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL textAlign start with ltr is the left edge assert_approx_equals: Red channel of the pixel at (95, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.start.ltr.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.start.ltr.worker.js
index eb98a05..c1ac7de 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.start.ltr.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.start.ltr.worker.js
@@ -16,9 +16,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.start.rtl.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.start.rtl.html
index ab4179a..33408f3 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.start.rtl.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.start.rtl.html
@@ -20,9 +20,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.start.rtl.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.start.rtl.worker.js
index 585ad73..c01e3c4 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.start.rtl.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.align.start.rtl.worker.js
@@ -16,9 +16,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.alphabetic-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.alphabetic-expected.txt
deleted file mode 100644
index ef60f611..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.alphabetic-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL OffscreenCanvas test: 2d.text.draw.baseline.alphabetic assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.alphabetic.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.alphabetic.html
index c64bf9d..fa93666c 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.alphabetic.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.alphabetic.html
@@ -20,9 +20,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.alphabetic.worker-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.alphabetic.worker-expected.txt
deleted file mode 100644
index dcc1552..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.alphabetic.worker-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL 2d assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.alphabetic.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.alphabetic.worker.js
index 3112e11..d234d3e 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.alphabetic.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.alphabetic.worker.js
@@ -16,9 +16,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.bottom-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.bottom-expected.txt
deleted file mode 100644
index 4524520..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.bottom-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL textBaseline bottom is the bottom of the em square (not the bounding box) assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.bottom.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.bottom.html
index f5381e3a..ffdc84d5 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.bottom.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.bottom.html
@@ -20,9 +20,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.bottom.worker-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.bottom.worker-expected.txt
deleted file mode 100644
index 4524520..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.bottom.worker-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL textBaseline bottom is the bottom of the em square (not the bounding box) assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.bottom.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.bottom.worker.js
index e8e75fa..0dc46e7 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.bottom.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.bottom.worker.js
@@ -16,9 +16,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.hanging.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.hanging.html
index 3dc426b..38d44158 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.hanging.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.hanging.html
@@ -20,9 +20,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.hanging.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.hanging.worker.js
index de79c40..c1030c8a 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.hanging.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.hanging.worker.js
@@ -16,9 +16,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.ideographic-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.ideographic-expected.txt
index de081074..88ea146f 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.ideographic-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.ideographic-expected.txt
@@ -1,4 +1,4 @@
 This is a testharness.js-based test.
-FAIL OffscreenCanvas test: 2d.text.draw.baseline.ideographic assert_approx_equals: Red channel of the pixel at (95, 5) expected 0 +/- 2 but got 255
+FAIL OffscreenCanvas test: 2d.text.draw.baseline.ideographic assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.ideographic.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.ideographic.html
index df3f17d..fb1aad2b 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.ideographic.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.ideographic.html
@@ -20,9 +20,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.ideographic.worker-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.ideographic.worker-expected.txt
index b41ede0b..dcc1552 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.ideographic.worker-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.ideographic.worker-expected.txt
@@ -1,4 +1,4 @@
 This is a testharness.js-based test.
-FAIL 2d assert_approx_equals: Red channel of the pixel at (95, 5) expected 0 +/- 2 but got 255
+FAIL 2d assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.ideographic.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.ideographic.worker.js
index b4f2770..f3ca4a1d 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.ideographic.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.ideographic.worker.js
@@ -16,9 +16,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.middle-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.middle-expected.txt
deleted file mode 100644
index 0d46566..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.middle-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL textBaseline middle is the middle of the em square (not the bounding box) assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.middle.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.middle.html
index cf1a28d9..0a7f356 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.middle.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.middle.html
@@ -20,9 +20,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.middle.worker-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.middle.worker-expected.txt
deleted file mode 100644
index 0d46566..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.middle.worker-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL textBaseline middle is the middle of the em square (not the bounding box) assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.middle.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.middle.worker.js
index 9b644c8..800d602 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.middle.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.middle.worker.js
@@ -16,9 +16,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.top-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.top-expected.txt
deleted file mode 100644
index 543ddae..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.top-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL textBaseline top is the top of the em square (not the bounding box) assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.top.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.top.html
index 19ebb86..b9c6d1e 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.top.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.top.html
@@ -20,9 +20,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.top.worker-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.top.worker-expected.txt
deleted file mode 100644
index 543ddae..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.top.worker-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL textBaseline top is the top of the em square (not the bounding box) assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.top.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.top.worker.js
index 5e88657..1079f14 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.top.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.baseline.top.worker.js
@@ -16,9 +16,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fill.maxWidth.bound-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fill.maxWidth.bound-expected.txt
deleted file mode 100644
index 8232873..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fill.maxWidth.bound-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL fillText handles maxWidth based on line size, not bounding box size assert_approx_equals: Red channel of the pixel at (95, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fill.maxWidth.bound.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fill.maxWidth.bound.html
index 0dfb426..ef942c01 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fill.maxWidth.bound.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fill.maxWidth.bound.html
@@ -20,9 +20,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fill.maxWidth.bound.worker-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fill.maxWidth.bound.worker-expected.txt
deleted file mode 100644
index 8232873..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fill.maxWidth.bound.worker-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL fillText handles maxWidth based on line size, not bounding box size assert_approx_equals: Red channel of the pixel at (95, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fill.maxWidth.bound.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fill.maxWidth.bound.worker.js
index 0b1fe85..8300b22 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fill.maxWidth.bound.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fill.maxWidth.bound.worker.js
@@ -16,9 +16,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fill.maxWidth.fontface.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fill.maxWidth.fontface.html
index f14ca8c..8a8d68f 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fill.maxWidth.fontface.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fill.maxWidth.fontface.html
@@ -20,9 +20,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#0f0';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#f00';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fill.maxWidth.fontface.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fill.maxWidth.fontface.worker.js
index 96a3979f..dfcacff 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fill.maxWidth.fontface.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fill.maxWidth.fontface.worker.js
@@ -16,9 +16,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#0f0';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#f00';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface-expected.txt
deleted file mode 100644
index 87dcfc1be..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL OffscreenCanvas test: 2d.text.draw.fontface assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.html
index 285b6e3..f0273f66 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.html
@@ -20,9 +20,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '67px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '67px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.notinpage-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.notinpage-expected.txt
deleted file mode 100644
index 995821c..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.notinpage-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL @font-face fonts should work even if they are not used in the page assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.notinpage.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.notinpage.html
index d9d9708d..55b1e71a 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.notinpage.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.notinpage.html
@@ -20,9 +20,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '67px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '67px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.notinpage.worker-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.notinpage.worker-expected.txt
deleted file mode 100644
index 995821c..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.notinpage.worker-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL @font-face fonts should work even if they are not used in the page assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.notinpage.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.notinpage.worker.js
index 33b99294..d7132bb 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.notinpage.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.notinpage.worker.js
@@ -16,9 +16,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '67px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '67px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.repeat-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.repeat-expected.txt
deleted file mode 100644
index 4da5200..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.repeat-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL Draw with the font immediately, then wait a bit until and draw again. (This crashes some version of WebKit.) assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.repeat.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.repeat.html
index ee204a0..3448c241 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.repeat.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.repeat.html
@@ -20,13 +20,16 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.fillStyle = '#f00';
-ctx.fillRect(0, 0, 100, 50);
-ctx.font = '67px CanvasTest';
-ctx.fillStyle = '#0f0';
-ctx.fillText('AA', 0, 50);
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.fillStyle = '#f00';
+    ctx.fillRect(0, 0, 100, 50);
+    ctx.font = '67px CanvasTest';
+    ctx.fillStyle = '#0f0';
+    ctx.fillText('AA', 0, 50);
     ctx.fillText('AA', 0, 50);
     _assertPixelApprox(offscreenCanvas, 5,5, 0,255,0,255, "5,5", "0,255,0,255", 2);
     _assertPixelApprox(offscreenCanvas, 95,5, 0,255,0,255, "95,5", "0,255,0,255", 2);
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.repeat.worker-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.repeat.worker-expected.txt
deleted file mode 100644
index 4da5200..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.repeat.worker-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL Draw with the font immediately, then wait a bit until and draw again. (This crashes some version of WebKit.) assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.repeat.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.repeat.worker.js
index f278469..d577465 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.repeat.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.repeat.worker.js
@@ -16,13 +16,16 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.fillStyle = '#f00';
-ctx.fillRect(0, 0, 100, 50);
-ctx.font = '67px CanvasTest';
-ctx.fillStyle = '#0f0';
-ctx.fillText('AA', 0, 50);
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.fillStyle = '#f00';
+    ctx.fillRect(0, 0, 100, 50);
+    ctx.font = '67px CanvasTest';
+    ctx.fillStyle = '#0f0';
+    ctx.fillText('AA', 0, 50);
     ctx.fillText('AA', 0, 50);
     _assertPixelApprox(offscreenCanvas, 5,5, 0,255,0,255, "5,5", "0,255,0,255", 2);
     _assertPixelApprox(offscreenCanvas, 95,5, 0,255,0,255, "95,5", "0,255,0,255", 2);
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.worker-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.worker-expected.txt
deleted file mode 100644
index dcc1552..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.worker-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL 2d assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.worker.js
index eeadc01..2bef126 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.fontface.worker.js
@@ -16,9 +16,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '67px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '67px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.basic-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.basic-expected.txt
deleted file mode 100644
index 5471fb1f..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.basic-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL U+0020 is rendered the correct size (1em wide) assert_approx_equals: Red channel of the pixel at (25, 25) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.basic.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.basic.html
index a9daf1d..108e7a6 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.basic.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.basic.html
@@ -20,9 +20,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.basic.worker-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.basic.worker-expected.txt
deleted file mode 100644
index 5471fb1f..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.basic.worker-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL U+0020 is rendered the correct size (1em wide) assert_approx_equals: Red channel of the pixel at (25, 25) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.basic.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.basic.worker.js
index b14c246e..18d01b8 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.basic.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.basic.worker.js
@@ -16,9 +16,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.end-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.end-expected.txt
index 74e8af4c..2e4a7d0 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.end-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.end-expected.txt
@@ -1,4 +1,4 @@
 This is a testharness.js-based test.
-FAIL Space characters at the end of a line are collapsed (per CSS) assert_approx_equals: Red channel of the pixel at (25, 25) expected 0 +/- 2 but got 255
+FAIL Space characters at the end of a line are collapsed (per CSS) assert_approx_equals: Red channel of the pixel at (75, 25) expected 0 +/- 2 but got 255
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.end.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.end.html
index cd11c29..7489fe6 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.end.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.end.html
@@ -20,9 +20,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.end.worker-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.end.worker-expected.txt
index 74e8af4c..2e4a7d0 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.end.worker-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.end.worker-expected.txt
@@ -1,4 +1,4 @@
 This is a testharness.js-based test.
-FAIL Space characters at the end of a line are collapsed (per CSS) assert_approx_equals: Red channel of the pixel at (25, 25) expected 0 +/- 2 but got 255
+FAIL Space characters at the end of a line are collapsed (per CSS) assert_approx_equals: Red channel of the pixel at (75, 25) expected 0 +/- 2 but got 255
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.end.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.end.worker.js
index 7b40c640..10670f05 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.end.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.end.worker.js
@@ -16,9 +16,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.nonspace-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.nonspace-expected.txt
deleted file mode 100644
index c71d94f..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.nonspace-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL Non-space characters are not converted to U+0020 and collapsed assert_approx_equals: Red channel of the pixel at (25, 25) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.nonspace.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.nonspace.html
index 1924626..3241ac7 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.nonspace.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.nonspace.html
@@ -20,9 +20,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.nonspace.worker-expected.txt b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.nonspace.worker-expected.txt
deleted file mode 100644
index c71d94f..0000000
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.nonspace.worker-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL Non-space characters are not converted to U+0020 and collapsed assert_approx_equals: Red channel of the pixel at (25, 25) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.nonspace.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.nonspace.worker.js
index f13bcc93..04627cd 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.nonspace.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.nonspace.worker.js
@@ -16,9 +16,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.other.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.other.html
index 586ae37..1afde6d 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.other.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.other.html
@@ -20,9 +20,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.other.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.other.worker.js
index 0f88a44..375b1842 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.other.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.other.worker.js
@@ -16,9 +16,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.space.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.space.html
index ac0d0b4..621d052 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.space.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.space.html
@@ -20,9 +20,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.space.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.space.worker.js
index 2ff1ba5..0e367af 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.space.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.space.worker.js
@@ -16,9 +16,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.start.html b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.start.html
index a9e1f5a..3b86a0a1 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.start.html
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.start.html
@@ -20,9 +20,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.start.worker.js b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.start.worker.js
index a9a8cc2..81c6a571 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.start.worker.js
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/text/2d.text.draw.space.collapse.start.worker.js
@@ -16,9 +16,12 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.font = '50px CanvasTest';
-new Promise(function(resolve) { step_timeout(resolve, 500); })
-  .then(function() {
+var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+let fonts = (self.fonts ? self.fonts : document.fonts);
+f.load();
+fonts.add(f);
+fonts.ready.then(function() {
+    ctx.font = '50px CanvasTest';
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/offscreen-canvas/tools/tests2d.yaml b/third_party/blink/web_tests/external/wpt/offscreen-canvas/tools/tests2d.yaml
index 846dc8b..4ff765b 100644
--- a/third_party/blink/web_tests/external/wpt/offscreen-canvas/tools/tests2d.yaml
+++ b/third_party/blink/web_tests/external/wpt/offscreen-canvas/tools/tests2d.yaml
@@ -10273,9 +10273,12 @@
   fonts:
     - CanvasTest
   code: |
-    ctx.font = '50px CanvasTest';
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.font = '50px CanvasTest';
         ctx.fillStyle = '#0f0';
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#f00';
@@ -10294,9 +10297,12 @@
   fonts:
     - CanvasTest
   code: |
-    ctx.font = '50px CanvasTest';
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.font = '50px CanvasTest';
         ctx.fillStyle = '#f00';
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
@@ -10314,9 +10320,12 @@
   fonts:
     - CanvasTest
   code: |
-    ctx.font = '67px CanvasTest';
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.font = '67px CanvasTest';
         ctx.fillStyle = '#f00';
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
@@ -10336,13 +10345,16 @@
     - CanvasTest
   fonthack: 0
   code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    ctx.font = '67px CanvasTest';
-    ctx.fillStyle = '#0f0';
-    ctx.fillText('AA', 0, 50);
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.fillStyle = '#f00';
+        ctx.fillRect(0, 0, 100, 50);
+        ctx.font = '67px CanvasTest';
+        ctx.fillStyle = '#0f0';
+        ctx.fillText('AA', 0, 50);
         ctx.fillText('AA', 0, 50);
         @assert pixel 5,5 ==~ 0,255,0,255;
         @assert pixel 95,5 ==~ 0,255,0,255;
@@ -10359,9 +10371,12 @@
     - CanvasTest
   fonthack: 0
   code: |
-    ctx.font = '67px CanvasTest';
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.font = '67px CanvasTest';
         ctx.fillStyle = '#f00';
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
@@ -10380,9 +10395,12 @@
   fonts:
     - CanvasTest
   code: |
-    ctx.font = '50px CanvasTest';
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.font = '50px CanvasTest';
         ctx.fillStyle = '#f00';
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
@@ -10404,9 +10422,12 @@
   fonts:
     - CanvasTest
   code: |
-    ctx.font = '50px CanvasTest';
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.font = '50px CanvasTest';
         ctx.fillStyle = '#f00';
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
@@ -10428,9 +10449,12 @@
   fonts:
     - CanvasTest
   code: |
-    ctx.font = '50px CanvasTest';
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.font = '50px CanvasTest';
         ctx.fillStyle = '#f00';
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
@@ -10451,9 +10475,12 @@
   fonts:
     - CanvasTest
   code: |
-    ctx.font = '50px CanvasTest';
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.font = '50px CanvasTest';
         ctx.fillStyle = '#f00';
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
@@ -10474,9 +10501,12 @@
   fonts:
     - CanvasTest
   code: |
-    ctx.font = '50px CanvasTest';
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.font = '50px CanvasTest';
         ctx.fillStyle = '#f00';
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
@@ -10497,9 +10527,12 @@
   fonts:
     - CanvasTest
   code: |
-    ctx.font = '50px CanvasTest';
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.font = '50px CanvasTest';
         ctx.fillStyle = '#f00';
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
@@ -10521,9 +10554,12 @@
   fonts:
     - CanvasTest
   code: |
-    ctx.font = '50px CanvasTest';
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.font = '50px CanvasTest';
         ctx.fillStyle = '#f00';
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
@@ -10545,9 +10581,12 @@
   fonts:
     - CanvasTest
   code: |
-    ctx.font = '50px CanvasTest';
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.font = '50px CanvasTest';
         ctx.fillStyle = '#f00';
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
@@ -10570,9 +10609,12 @@
     - CanvasTest
   canvas: width="100" height="50" dir="ltr"
   code: |
-    ctx.font = '50px CanvasTest';
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.font = '50px CanvasTest';
         ctx.fillStyle = '#f00';
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
@@ -10596,9 +10638,12 @@
     - CanvasTest
   canvas: width="100" height="50" dir="rtl"
   code: |
-    ctx.font = '50px CanvasTest';
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.font = '50px CanvasTest';
         ctx.fillStyle = '#f00';
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
@@ -10621,9 +10666,12 @@
     - CanvasTest
   canvas: width="100" height="50" dir="ltr"
   code: |
-    ctx.font = '50px CanvasTest';
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.font = '50px CanvasTest';
         ctx.fillStyle = '#f00';
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
@@ -10647,9 +10695,12 @@
     - CanvasTest
   canvas: width="100" height="50" dir="rtl"
   code: |
-    ctx.font = '50px CanvasTest';
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.font = '50px CanvasTest';
         ctx.fillStyle = '#f00';
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
@@ -10671,9 +10722,12 @@
   fonts:
     - CanvasTest
   code: |
-    ctx.font = '50px CanvasTest';
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.font = '50px CanvasTest';
         ctx.fillStyle = '#f00';
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
@@ -10696,9 +10750,12 @@
   fonts:
     - CanvasTest
   code: |
-    ctx.font = '50px CanvasTest';
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.font = '50px CanvasTest';
         ctx.fillStyle = '#f00';
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
@@ -10715,9 +10772,12 @@
   fonts:
     - CanvasTest
   code: |
-    ctx.font = '50px CanvasTest';
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.font = '50px CanvasTest';
         ctx.fillStyle = '#f00';
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
@@ -10734,9 +10794,12 @@
   fonts:
     - CanvasTest
   code: |
-    ctx.font = '50px CanvasTest';
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.font = '50px CanvasTest';
         ctx.fillStyle = '#f00';
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
@@ -10753,9 +10816,12 @@
   fonts:
     - CanvasTest
   code: |
-    ctx.font = '50px CanvasTest';
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.font = '50px CanvasTest';
         ctx.fillStyle = '#f00';
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
@@ -10772,9 +10838,12 @@
   fonts:
     - CanvasTest
   code: |
-    ctx.font = '50px CanvasTest';
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.font = '50px CanvasTest';
         ctx.fillStyle = '#f00';
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
@@ -10791,9 +10860,12 @@
   fonts:
     - CanvasTest
   code: |
-    ctx.font = '50px CanvasTest';
-    new Promise(function(resolve) { step_timeout(resolve, 500); })
-      .then(function() {
+    var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+    let fonts = (self.fonts ? self.fonts : document.fonts);
+    f.load();
+    fonts.add(f);
+    fonts.ready.then(function() {
+        ctx.font = '50px CanvasTest';
         ctx.fillStyle = '#f00';
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
diff --git a/third_party/blink/web_tests/external/wpt/origin-policy/ids/two-ids.https.html b/third_party/blink/web_tests/external/wpt/origin-policy/ids/two-ids.https.html
index 630f072..da9ab2e9 100644
--- a/third_party/blink/web_tests/external/wpt/origin-policy/ids/two-ids.https.html
+++ b/third_party/blink/web_tests/external/wpt/origin-policy/ids/two-ids.https.html
@@ -1,6 +1,7 @@
 <!DOCTYPE HTML>
 <meta charset="utf-8">
 <title>Origin policy second "ids" member must take precedence</title>
+<meta name=timeout content=long>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../resources/origin-policy-test-runner.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioparam-interface/k-rate-panner-connections.html b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioparam-interface/k-rate-panner-connections.html
new file mode 100644
index 0000000..77b32c2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioparam-interface/k-rate-panner-connections.html
@@ -0,0 +1,236 @@
+<!doctype html>
+<html>
+  <head>
+    <title>
+      k-rate AudioParams with inputs for PannerNode
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    </title>
+  </head>
+
+  <body>
+    <script>
+      let audit = Audit.createTaskRunner();
+
+      audit.define(
+          {label: 'Panner x', description: 'k-rate input'},
+          async (task, should) => {
+            await testPannerParams(should, {param: 'positionX'});
+            task.done();
+          });
+
+      audit.define(
+          {label: 'Panner y', description: 'k-rate input'},
+          async (task, should) => {
+            await testPannerParams(should, {param: 'positionY'});
+            task.done();
+          });
+
+      audit.define(
+          {label: 'Panner z', description: 'k-rate input'},
+          async (task, should) => {
+            await testPannerParams(should, {param: 'positionZ'});
+            task.done();
+          });
+
+      audit.define(
+          {label: 'Listener x', description: 'k-rate input'},
+          async (task, should) => {
+            await testListenerParams(should, {param: 'positionX'});
+            task.done();
+          });
+
+      audit.define(
+          {label: 'Listener y', description: 'k-rate input'},
+          async (task, should) => {
+            await testListenerParams(should, {param: 'positionY'});
+            task.done();
+          });
+
+      audit.define(
+          {label: 'Listener z', description: 'k-rate input'},
+          async (task, should) => {
+            await testListenerParams(should, {param: 'positionZ'});
+            task.done();
+          });
+
+      audit.run();
+
+      async function testPannerParams(should, options) {
+        // Arbitrary sample rate and duration.
+        const sampleRate = 8000;
+        const testFrames = 5 * RENDER_QUANTUM_FRAMES;
+        let testDuration = testFrames / sampleRate;
+        // Four channels needed because the first two are for the output of
+        // the reference panner, and the next two are for the test panner.
+        let context = new OfflineAudioContext({
+          numberOfChannels: 4,
+          sampleRate: sampleRate,
+          length: testDuration * sampleRate
+        });
+
+        let merger = new ChannelMergerNode(
+            context, {numberOfInputs: context.destination.channelCount});
+        merger.connect(context.destination);
+
+        // Create a stereo source out of two mono sources
+        let src0 = new ConstantSourceNode(context, {offset: 1});
+        let src1 = new ConstantSourceNode(context, {offset: 2});
+        let src = new ChannelMergerNode(context, {numberOfInputs: 2});
+        src0.connect(src, 0, 0);
+        src1.connect(src, 0, 1);
+
+        let finalPosition = 100;
+
+        // Reference panner node with k-rate AudioParam automations.  The
+        // output of this panner is the reference output.
+        let refNode = new PannerNode(context);
+        // Initialize the panner location to somewhat arbitrary values.
+        refNode.positionX.value = 1;
+        refNode.positionY.value = 50;
+        refNode.positionZ.value = -25;
+
+        // Set the AudioParam under test with the appropriate automations.
+        refNode[options.param].automationRate = 'k-rate';
+        refNode[options.param].setValueAtTime(1, 0);
+        refNode[options.param].linearRampToValueAtTime(
+            finalPosition, testDuration);
+        let refSplit = new ChannelSplitterNode(context, {numberOfOutputs: 2});
+
+        // Test panner node with k-rate AudioParam with inputs.
+        let tstNode = new PannerNode(context);
+        tstNode.positionX.value = 1;
+        tstNode.positionY.value = 50;
+        tstNode.positionZ.value = -25;
+        tstNode[options.param].value = 0;
+        tstNode[options.param].automationRate = 'k-rate';
+        let tstSplit = new ChannelSplitterNode(context, {numberOfOutputs: 2});
+
+        // The input to the AudioParam.  It must have the same automation
+        // sequence as used by refNode.  And must be a-rate to demonstrate
+        // the k-rate effect of the AudioParam.
+        let mod = new ConstantSourceNode(context, {offset: 0});
+        mod.offset.setValueAtTime(1, 0);
+        mod.offset.linearRampToValueAtTime(finalPosition, testDuration);
+
+        mod.connect(tstNode[options.param]);
+
+        src.connect(refNode).connect(refSplit);
+        src.connect(tstNode).connect(tstSplit);
+
+        refSplit.connect(merger, 0, 0);
+        refSplit.connect(merger, 1, 1);
+        tstSplit.connect(merger, 0, 2);
+        tstSplit.connect(merger, 1, 3);
+
+        mod.start();
+        src0.start();
+        src1.start();
+
+        const buffer = await context.startRendering();
+        let expected0 = buffer.getChannelData(0);
+        let expected1 = buffer.getChannelData(1);
+        let actual0 = buffer.getChannelData(2);
+        let actual1 = buffer.getChannelData(3);
+
+        should(expected0, 'Expected output channel 0')
+            .notBeConstantValueOf(expected0[0]);
+        should(expected1, 'Expected output channel 1')
+            .notBeConstantValueOf(expected1[0]);
+
+        // Verify output is a stair step because positionX is k-rate,
+        // and no other AudioParam is changing.
+
+        for (let k = 0; k < testFrames; k += RENDER_QUANTUM_FRAMES) {
+          should(
+              actual0.slice(k, k + RENDER_QUANTUM_FRAMES),
+              `Channel 0 output[${k}, ${k + RENDER_QUANTUM_FRAMES - 1}]`)
+              .beConstantValueOf(actual0[k]);
+        }
+
+        for (let k = 0; k < testFrames; k += RENDER_QUANTUM_FRAMES) {
+          should(
+              actual1.slice(k, k + RENDER_QUANTUM_FRAMES),
+              `Channel 1 output[${k}, ${k + RENDER_QUANTUM_FRAMES - 1}]`)
+              .beConstantValueOf(actual1[k]);
+        }
+
+        should(actual0, 'Actual output channel 0').beCloseToArray(expected0, {
+          absoluteThreshold: 0
+        });
+        should(actual1, 'Actual output channel 1').beCloseToArray(expected1, {
+          absoluteThreshold: 0
+        });
+      }
+
+      async function testListenerParams(should, options) {
+        // Arbitrary sample rate and duration.
+        const sampleRate = 8000;
+        const testFrames = 5 * RENDER_QUANTUM_FRAMES;
+        let testDuration = testFrames / sampleRate;
+        // Four channels needed because the first two are for the output of
+        // the reference panner, and the next two are for the test panner.
+        let context = new OfflineAudioContext({
+          numberOfChannels: 2,
+          sampleRate: sampleRate,
+          length: testDuration * sampleRate
+        });
+
+        // Create a stereo source out of two mono sources
+        let src0 = new ConstantSourceNode(context, {offset: 1});
+        let src1 = new ConstantSourceNode(context, {offset: 2});
+        let src = new ChannelMergerNode(context, {numberOfInputs: 2});
+        src0.connect(src, 0, 0);
+        src1.connect(src, 0, 1);
+
+        let finalPosition = 100;
+
+        // Reference panner node with k-rate AudioParam automations.  The
+        // output of this panner is the reference output.
+        let panner = new PannerNode(context);
+        panner.positionX.value = 10;
+        panner.positionY.value = 50;
+        panner.positionZ.value = -25;
+
+        src.connect(panner);
+
+        let mod = new ConstantSourceNode(context, {offset: 0});
+        mod.offset.setValueAtTime(1, 0);
+        mod.offset.linearRampToValueAtTime(finalPosition, testDuration);
+
+        context.listener[options.param].automationRate = 'k-rate';
+        mod.connect(context.listener[options.param]);
+
+        panner.connect(context.destination);
+
+        src0.start();
+        src1.start();
+        mod.start();
+
+        const buffer = await context.startRendering();
+        let c0 = buffer.getChannelData(0);
+        let c1 = buffer.getChannelData(1);
+
+        // Verify output is a stair step because positionX is k-rate,
+        // and no other AudioParam is changing.
+
+        for (let k = 0; k < testFrames; k += RENDER_QUANTUM_FRAMES) {
+          should(
+              c0.slice(k, k + RENDER_QUANTUM_FRAMES),
+              `Channel 0 output[${k}, ${k + RENDER_QUANTUM_FRAMES - 1}]`)
+              .beConstantValueOf(c0[k]);
+        }
+
+        for (let k = 0; k < testFrames; k += RENDER_QUANTUM_FRAMES) {
+          should(
+              c1.slice(k, k + RENDER_QUANTUM_FRAMES),
+              `Channel 1 output[${k}, ${k + RENDER_QUANTUM_FRAMES - 1}]`)
+              .beConstantValueOf(c1[k]);
+        }
+      }
+    </script>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/fast/dom/custom/registration-context-isolation.html b/third_party/blink/web_tests/fast/dom/custom/registration-context-isolation.html
index 0fd4b6b..caefa0eb3 100644
--- a/third_party/blink/web_tests/fast/dom/custom/registration-context-isolation.html
+++ b/third_party/blink/web_tests/fast/dom/custom/registration-context-isolation.html
@@ -163,6 +163,7 @@
     var documentB = documentA.implementation.createDocument(null, '');
     assert_throws_dom(
         'NOT_SUPPORTED_ERR',
+        frame.contentWindow.DOMException,
         function() { documentB.registerElement('x-a'); });
 
     // This document will not process custom elements because there is
@@ -170,6 +171,7 @@
     var documentC = documentB.implementation.createHTMLDocument();
     assert_throws_dom(
         'NOT_SUPPORTED_ERR',
+        frame.contentWindow.DOMException,
         function() { documentC.registerElement('x-b'); });
 
     // Nor this one.
@@ -177,6 +179,7 @@
         'http://www.w3.org/1999/xhtml', 'html');
     assert_throws_dom(
         'NOT_SUPPORTED_ERR',
+        frame.contentWindow.DOMException,
         function() { documentD.registerElement('x-c'); });
 
     frame.remove();
diff --git a/third_party/blink/web_tests/fast/mediastream/MediaStreamTrack-applyConstraints-expected.txt b/third_party/blink/web_tests/fast/mediastream/MediaStreamTrack-applyConstraints-expected.txt
index d280be81..85637a6 100644
--- a/third_party/blink/web_tests/fast/mediastream/MediaStreamTrack-applyConstraints-expected.txt
+++ b/third_party/blink/web_tests/fast/mediastream/MediaStreamTrack-applyConstraints-expected.txt
@@ -1,6 +1,6 @@
 This is a testharness.js-based test.
 PASS applyConstraints() sets the value of a constraint set by getUserMedia()
-FAIL Attempting to change the deviceId with applyConstraints() fails promise_reject_js: function "function OverconstrainedError() { [native code] }" is not an Error subtype
+FAIL Attempting to change the deviceId with applyConstraints() fails promise_rejects_js: function "function OverconstrainedError() { [native code] }" is not an Error subtype
 PASS An unsupported constraint is ignored by applyConstraints()
 PASS All valid keys are returned for complex constraints
 PASS Simple integer
diff --git a/third_party/blink/web_tests/http/tests/media/captureStream-on-detached-video.html b/third_party/blink/web_tests/http/tests/media/captureStream-on-detached-video.html
index 726e80c..e4afa51a 100644
--- a/third_party/blink/web_tests/http/tests/media/captureStream-on-detached-video.html
+++ b/third_party/blink/web_tests/http/tests/media/captureStream-on-detached-video.html
@@ -9,8 +9,9 @@
 
     iframe.onload = t.step_func_done(function() {
         var video = iframe.contentDocument.getElementById("v");
+        var iframeDOMException = iframe.contentWindow.DOMException;
         iframe.remove();
-        assert_throws_dom("NotSupportedError",
+        assert_throws_dom("NotSupportedError", iframeDOMException,
             function() { video.captureStream(1.0); },
             "Failed to execute 'captureStream' on 'HTMLMediaElement': The context has been destroyed.");
     });
diff --git a/third_party/blink/web_tests/loader/image-loader-base.html b/third_party/blink/web_tests/loader/image-loader-base.html
deleted file mode 100644
index 3d64b13..0000000
--- a/third_party/blink/web_tests/loader/image-loader-base.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<title>base element test </title>
-<script src="../resources/testharness.js"></script>
-<script src="../resources/testharnessreport.js"></script>
-</head>
-<body>
-Test for bugs.chromium.org #569760: <br/>
-<img id="img1" ><br/>
-<script>
-var test = async_test('image loading should be loaded successfully regardless of base element');
-
-test.step(function () {
-    var elm = document.getElementById('img1');
-    elm.src = 'resources/image1.png';
-    elm.onerror = test.step_func(function () {
-        assert_unreached("onerror() of image.");
-        test.done(); 
-    });
-    elm.onload = test.step_func(function () {
-        assert_true(true, "Loaded successfully.");
-        test.done();
-    });
-
-    var base = document.createElement("base");
-    base.setAttribute("href", "resources/");
-    document.head.appendChild(base);
-});
-</script>
-</body>
-</html>
diff --git a/third_party/blink/web_tests/media/picture-in-picture/detached-iframe.html b/third_party/blink/web_tests/media/picture-in-picture/detached-iframe.html
index e25d05a..8372744 100644
--- a/third_party/blink/web_tests/media/picture-in-picture/detached-iframe.html
+++ b/third_party/blink/web_tests/media/picture-in-picture/detached-iframe.html
@@ -16,8 +16,9 @@
   await frameLoadedPromise;
 
   const video = await loadVideo(frame.contentDocument, '../content/test.ogv');
+  const frameDOMException = frame.contentWindow.DOMException;
   document.body.removeChild(frame);
-  return promise_rejects_dom(t, 'InvalidStateError',
+  return promise_rejects_dom(t, 'InvalidStateError', frameDOMException,
       requestPictureInPictureWithTrustedClick(video));
 }, 'request Picture-in-Picture rejects when frame is detached');
 </script>
diff --git a/third_party/blink/web_tests/media/picture-in-picture/v2/detached-iframe.html b/third_party/blink/web_tests/media/picture-in-picture/v2/detached-iframe.html
index fe99b71d6..e361ec8 100644
--- a/third_party/blink/web_tests/media/picture-in-picture/v2/detached-iframe.html
+++ b/third_party/blink/web_tests/media/picture-in-picture/v2/detached-iframe.html
@@ -18,8 +18,9 @@
   await frameLoadedPromise;
 
   const element = frame.contentDocument.createElement('div');
+  const frameDOMException = frame.contentWindow.DOMException;
   document.body.removeChild(frame);
-  return promise_rejects_dom(t, 'InvalidStateError',
+  return promise_rejects_dom(t, 'InvalidStateError', frameDOMException,
     requestPictureInPictureWithTrustedClick(element, { height: 1, width: 1 }));
 }, 'request Picture-in-Picture rejects when frame is detached');
 </script>
diff --git a/third_party/blink/web_tests/platform/mac/editing/selection/continuations-with-move-caret-to-boundary-expected.txt b/third_party/blink/web_tests/platform/mac/editing/selection/continuations-with-move-caret-to-boundary-expected.txt
index 2ca0d7c..a5dfe0c66 100644
--- a/third_party/blink/web_tests/platform/mac/editing/selection/continuations-with-move-caret-to-boundary-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/editing/selection/continuations-with-move-caret-to-boundary-expected.txt
@@ -1,6 +1,6 @@
 This is a testharness.js-based test.
 PASS Continuations across a block -20
-FAIL Continuations across a block -15 resources/testharness.js:1867:25)
+FAIL Continuations across a block -15 resources/testharness.js:1958:25)
 	 expected <style>* { font: 20px Ahem; }</style><p>^AAAAA</p><p>|BBBBB</p>,
 	 but got  <style>* { font: 20px Ahem; }</style><p>^AAAAA</p><p>BB|BBB</p>,
 	 sameupto <style>* { font: 20px Ahem; }</style><p>^AAAAA</p><p>
diff --git a/third_party/blink/web_tests/resources/idlharness.js b/third_party/blink/web_tests/resources/idlharness.js
index 18b11ec..d01da49 100644
--- a/third_party/blink/web_tests/resources/idlharness.js
+++ b/third_party/blink/web_tests/resources/idlharness.js
@@ -2406,10 +2406,7 @@
     if (!shouldRunSubTest(this.name)) {
         return;
     }
-    var a_test = subsetTestByKey(this.name, async_test, this.name + " interface: operation " + member.name +
-                            "(" + member.arguments.map(
-                                function(m) {return m.idlType.idlType; } ).join(", ")
-                            +")");
+    var a_test = subsetTestByKey(this.name, async_test, this.name + " interface: operation " + member);
     a_test.step(function()
     {
         // This function tests WebIDL as of 2015-12-29.
@@ -2749,10 +2746,19 @@
         exception = e;
     }
 
-    var expected_typeof =
-        this.members.some(function(member) { return member.legacycaller; })
-        ? "function"
-        : "object";
+    var expected_typeof;
+    if (this.name == "HTMLAllCollection")
+    {
+        // Result of [[IsHTMLDDA]] slot
+        expected_typeof = "undefined";
+    } else if (this.members.some(function(member) { return member.legacycaller; }))
+    {
+        expected_typeof = "function";
+    }
+    else
+    {
+        expected_typeof = "object";
+    }
 
     this.test_primary_interface_of(desc, obj, exception, expected_typeof);
 
@@ -2883,11 +2889,6 @@
         || member.type == "operation")
         && member.name)
         {
-            var described_name = member.name;
-            if (member.type == "operation")
-            {
-                described_name += "(" + member.arguments.map(arg => arg.idlType.idlType).join(", ") + ")";
-            }
             subsetTestByKey(this.name, test, function()
             {
                 assert_equals(exception, null, "Unexpected exception when evaluating object");
@@ -2919,7 +2920,15 @@
                         }
                         if (!thrown)
                         {
-                            this.array.assert_type_is(property, member.idlType);
+                            if (this.name == "Document" && member.name == "all")
+                            {
+                                // Result of [[IsHTMLDDA]] slot
+                                assert_equals(typeof property, "undefined");
+                            }
+                            else
+                            {
+                                this.array.assert_type_is(property, member.idlType);
+                            }
                         }
                     }
                     if (member.type == "operation")
@@ -2927,16 +2936,17 @@
                         assert_equals(typeof obj[member.name], "function");
                     }
                 }
-            }.bind(this), this.name + " interface: " + desc + ' must inherit property "' + described_name + '" with the proper type');
+            }.bind(this), this.name + " interface: " + desc + ' must inherit property "' + member + '" with the proper type');
         }
         // TODO: This is wrong if there are multiple operations with the same
         // identifier.
         // TODO: Test passing arguments of the wrong type.
         if (member.type == "operation" && member.name && member.arguments.length)
         {
-            var a_test = subsetTestByKey(this.name, async_test, this.name + " interface: calling " + member.name +
-            "(" + member.arguments.map(function(m) { return m.idlType.idlType; }).join(", ") +
-            ") on " + desc + " with too few arguments must throw TypeError");
+            var description =
+                this.name + " interface: calling " + member + " on " + desc +
+                " with too few arguments must throw TypeError";
+            var a_test = subsetTestByKey(this.name, async_test, description);
             a_test.step(function()
             {
                 assert_equals(exception, null, "Unexpected exception when evaluating object");
@@ -3150,6 +3160,36 @@
     return this.type == "operation" && this.special !== "static" && this.name == "toJSON";
 };
 
+IdlInterfaceMember.prototype.toString = function() {
+    function formatType(type) {
+        var result;
+        if (type.generic) {
+            result = type.generic + "<" + type.idlType.map(formatType).join(", ") + ">";
+        } else if (type.union) {
+            result = "(" + type.subtype.map(formatType).join(" or ") + ")";
+        } else {
+            result = type.idlType;
+        }
+        if (type.nullable) {
+            result += "?"
+        }
+        return result;
+    }
+
+    if (this.type === "operation") {
+        var args = this.arguments.map(function(m) {
+            return [
+                m.optional ? "optional " : "",
+                formatType(m.idlType),
+                m.variadic ? "..." : "",
+            ].join("");
+        }).join(", ");
+        return this.name + "(" + args + ")";
+    }
+
+    return this.name;
+}
+
 /// Internal helper functions ///
 function create_suitable_object(type)
 {
@@ -3294,17 +3334,10 @@
     if (!shouldRunSubTest(this.name)) {
         return;
     }
-    var args = member.arguments.map(function(a) {
-        var s = a.idlType.idlType;
-        if (a.variadic) {
-            s += '...';
-        }
-        return s;
-    }).join(", ");
     var a_test = subsetTestByKey(
         this.name,
         async_test,
-        this.name + ' namespace: operation ' + member.name + '(' + args + ')');
+        this.name + ' namespace: operation ' + member);
     a_test.step(function() {
         assert_own_property(
             self[this.name],
diff --git a/third_party/blink/web_tests/resources/testharness.js b/third_party/blink/web_tests/resources/testharness.js
index 9d31d0b6..63883e1 100644
--- a/third_party/blink/web_tests/resources/testharness.js
+++ b/third_party/blink/web_tests/resources/testharness.js
@@ -638,25 +638,87 @@
         });
     }
 
-    function promise_rejects_js(test, constructor, promise, description) {
-        return promise.then(test.unreached_func("Should have rejected: " + description)).catch(function(e) {
-            assert_throws_js_impl(constructor, function() { throw e },
-                                  description, "promise_reject_js");
-        });
+    /**
+     * Make a copy of a Promise in the current realm.
+     *
+     * @param {Promise} promise the given promise that may be from a different
+     *                          realm
+     * @returns {Promise}
+     *
+     * An arbitrary promise provided by the caller may have originated in
+     * another frame that have since navigated away, rendering the frame's
+     * document inactive. Such a promise cannot be used with `await` or
+     * Promise.resolve(), as microtasks associated with it may be prevented
+     * from being run. See https://github.com/whatwg/html/issues/5319 for a
+     * particular case.
+     *
+     * In functions we define here, there is an expectation from the caller
+     * that the promise is from the current realm, that can always be used with
+     * `await`, etc. We therefore create a new promise in this realm that
+     * inherit the value and status from the given promise.
+     */
+
+    function bring_promise_to_current_realm(promise) {
+        return new Promise(promise.then.bind(promise));
     }
 
-    function promise_rejects_dom(test, type, promise, description) {
-        return promise.then(test.unreached_func("Should have rejected: " + description)).catch(function(e) {
-            assert_throws_dom_impl(type, function() { throw e },
-                                   description, "promise_rejects_dom");
-        });
+    function promise_rejects_js(test, constructor, promise, description) {
+        return bring_promise_to_current_realm(promise)
+            .then(test.unreached_func("Should have rejected: " + description))
+            .catch(function(e) {
+                assert_throws_js_impl(constructor, function() { throw e },
+                                      description, "promise_rejects_js");
+            });
+    }
+
+    /**
+     * Assert that a Promise is rejected with the right DOMException.
+     *
+     * @param test the test argument passed to promise_test
+     * @param {number|string} type.  See documentation for assert_throws_dom.
+     *
+     * For the remaining arguments, there are two ways of calling
+     * promise_rejects_dom:
+     *
+     * 1) If the DOMException is expected to come from the current global, the
+     * third argument should be the promise expected to reject, and a fourth,
+     * optional, argument is the assertion description.
+     *
+     * 2) If the DOMException is expected to come from some other global, the
+     * third argument should be the DOMException constructor from that global,
+     * the fourth argument the promise expected to reject, and the fifth,
+     * optional, argument the assertion description.
+     */
+
+    function promise_rejects_dom(test, type, promiseOrConstructor, descriptionOrPromise, maybeDescription) {
+        let constructor, promise, description;
+        if (typeof promiseOrConstructor === "function" &&
+            promiseOrConstructor.name === "DOMException") {
+            constructor = promiseOrConstructor;
+            promise = descriptionOrPromise;
+            description = maybeDescription;
+        } else {
+            constructor = self.DOMException;
+            promise = promiseOrConstructor;
+            description = descriptionOrPromise;
+            assert(maybeDescription === undefined,
+                   "Too many args pased to no-constructor version of promise_rejects_dom");
+        }
+        return bring_promise_to_current_realm(promise)
+            .then(test.unreached_func("Should have rejected: " + description))
+            .catch(function(e) {
+                assert_throws_dom_impl(type, function() { throw e }, description,
+                                       "promise_rejects_dom", constructor);
+            });
     }
 
     function promise_rejects_exactly(test, exception, promise, description) {
-        return promise.then(test.unreached_func("Should have rejected: " + description)).catch(function(e) {
-            assert_throws_exactly_impl(exception, function() { throw e },
-                                       description, "promise_rejects_exactly");
-        });
+        return bring_promise_to_current_realm(promise)
+            .then(test.unreached_func("Should have rejected: " + description))
+            .catch(function(e) {
+                assert_throws_exactly_impl(exception, function() { throw e },
+                                           description, "promise_rejects_exactly");
+            });
     }
 
     /**
@@ -1421,7 +1483,9 @@
     function _assert_inherits(name) {
         return function (object, property_name, description)
         {
-            assert(typeof object === "object" || typeof object === "function",
+            assert(typeof object === "object" || typeof object === "function" ||
+                   // Or has [[IsHTMLDDA]] slot
+                   String(object) === "[object HTMLAllCollection]",
                    name, description,
                    "provided value is not an object");
 
@@ -1541,20 +1605,44 @@
      *        either be an exception name (e.g. "HierarchyRequestError",
      *        "WrongDocumentError") or the name of the corresponding error code
      *        (e.g. "HIERARCHY_REQUEST_ERR", "WRONG_DOCUMENT_ERR").
-     * @param {Function} func Function which should throw.
-     * @param {string} description Error description for the case that the error is not thrown.
+     *
+     * For the remaining arguments, there are two ways of calling
+     * promise_rejects_dom:
+     *
+     * 1) If the DOMException is expected to come from the current global, the
+     * second argument should be the function expected to throw and a third,
+     * optional, argument is the assertion description.
+     *
+     * 2) If the DOMException is expected to come from some other global, the
+     * second argument should be the DOMException constructor from that global,
+     * the third argument the function expected to throw, and the fourth, optional,
+     * argument the assertion description.
      */
-    function assert_throws_dom(type, func, description)
+    function assert_throws_dom(type, funcOrConstructor, descriptionOrFunc, maybeDescription)
     {
-        assert_throws_dom_impl(type, func, description, "assert_throws_dom")
+        let constructor, func, description;
+        if (funcOrConstructor.name === "DOMException") {
+            constructor = funcOrConstructor;
+            func = descriptionOrFunc;
+            description = maybeDescription;
+        } else {
+            constructor = self.DOMException;
+            func = funcOrConstructor;
+            description = descriptionOrFunc;
+            assert(maybeDescription === undefined,
+                   "Too many args pased to no-constructor version of assert_throws_dom");
+        }
+        assert_throws_dom_impl(type, func, description, "assert_throws_dom", constructor)
     }
     expose(assert_throws_dom, "assert_throws_dom");
 
     /**
-     * Like assert_throws_dom but allows specifying the assertion type
-     * (assert_throws_dom or promise_rejects_dom, in practice).
+     * Similar to assert_throws_dom but allows specifying the assertion type
+     * (assert_throws_dom or promise_rejects_dom, in practice).  The
+     * "constructor" argument must be the DOMException constructor from the
+     * global we expect the exception to come from.
      */
-    function assert_throws_dom_impl(type, func, description, assertion_type)
+    function assert_throws_dom_impl(type, func, description, assertion_type, constructor)
     {
         try {
             func.call(this);
@@ -1565,6 +1653,7 @@
                 throw e;
             }
 
+            // Basic sanity-checks on the thrown exception.
             assert(typeof e === "object",
                    assertion_type, description,
                    "${func} threw ${e} with type ${type}, not an object",
@@ -1678,19 +1767,21 @@
                 required_props.name = name;
             }
 
-            //We'd like to test that e instanceof the appropriate interface,
-            //but we can't, because we don't know what window it was created
-            //in.  It might be an instanceof the appropriate interface on some
-            //unknown other window.  TODO: Work around this somehow?  Maybe have
-            //the first arg just be a DOMException with the right name instead
-            //of the string-or-code thing we have now?
-
             for (var prop in required_props) {
                 assert(prop in e && e[prop] == required_props[prop],
                        assertion_type, description,
                        "${func} threw ${e} that is not a DOMException " + type + ": property ${prop} is equal to ${actual}, expected ${expected}",
                        {func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]});
             }
+
+            // Check that the exception is from the right global.  This check is last
+            // so more specific, and more informative, checks on the properties can
+            // happen in case a totally incorrect exception is thrown.
+            assert(e.constructor === constructor,
+                   assertion_type, description,
+                   "${func} threw an exception from the wrong global",
+                   {func});
+
         }
     }
 
@@ -2749,24 +2840,8 @@
         var message_port;
 
         if (is_service_worker(worker)) {
-            if (window.MessageChannel) {
-                // The ServiceWorker's implicit MessagePort is currently not
-                // reliably accessible from the ServiceWorkerGlobalScope due to
-                // Blink setting MessageEvent.source to null for messages sent
-                // via ServiceWorker.postMessage(). Until that's resolved,
-                // create an explicit MessageChannel and pass one end to the
-                // worker.
-                var message_channel = new MessageChannel();
-                message_port = message_channel.port1;
-                message_port.start();
-                worker.postMessage({type: "connect"}, [message_channel.port2]);
-            } else {
-                // If MessageChannel is not available, then try the
-                // ServiceWorker.postMessage() approach using MessageEvent.source
-                // on the other end.
-                message_port = navigator.serviceWorker;
-                worker.postMessage({type: "connect"});
-            }
+            message_port = navigator.serviceWorker;
+            worker.postMessage({type: "connect"});
         } else if (is_shared_worker(worker)) {
             message_port = worker.port;
             message_port.start();
diff --git a/third_party/blink/web_tests/resources/webidl2.js b/third_party/blink/web_tests/resources/webidl2.js
index ea30d6e..2a174c9 100644
--- a/third_party/blink/web_tests/resources/webidl2.js
+++ b/third_party/blink/web_tests/resources/webidl2.js
@@ -1,97 +1,97 @@
 (function webpackUniversalModuleDefinition(root, factory) {
-    if(typeof exports === 'object' && typeof module === 'object')
-        module.exports = factory();
-    else if(typeof define === 'function' && define.amd)
-        define([], factory);
-    else if(typeof exports === 'object')
-        exports["WebIDL2"] = factory();
-    else
-        root["WebIDL2"] = factory();
+	if(typeof exports === 'object' && typeof module === 'object')
+		module.exports = factory();
+	else if(typeof define === 'function' && define.amd)
+		define([], factory);
+	else if(typeof exports === 'object')
+		exports["WebIDL2"] = factory();
+	else
+		root["WebIDL2"] = factory();
 })(this, function() {
 return /******/ (function(modules) { // webpackBootstrap
-/******/     // The module cache
-/******/     var installedModules = {};
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
 /******/
-/******/     // The require function
-/******/     function __webpack_require__(moduleId) {
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
 /******/
-/******/         // Check if module is in cache
-/******/         if(installedModules[moduleId]) {
-/******/             return installedModules[moduleId].exports;
-/******/         }
-/******/         // Create a new module (and put it into the cache)
-/******/         var module = installedModules[moduleId] = {
-/******/             i: moduleId,
-/******/             l: false,
-/******/             exports: {}
-/******/         };
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId]) {
+/******/ 			return installedModules[moduleId].exports;
+/******/ 		}
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			i: moduleId,
+/******/ 			l: false,
+/******/ 			exports: {}
+/******/ 		};
 /******/
-/******/         // Execute the module function
-/******/         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
 /******/
-/******/         // Flag the module as loaded
-/******/         module.l = true;
+/******/ 		// Flag the module as loaded
+/******/ 		module.l = true;
 /******/
-/******/         // Return the exports of the module
-/******/         return module.exports;
-/******/     }
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
 /******/
 /******/
-/******/     // expose the modules object (__webpack_modules__)
-/******/     __webpack_require__.m = modules;
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
 /******/
-/******/     // expose the module cache
-/******/     __webpack_require__.c = installedModules;
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
 /******/
-/******/     // define getter function for harmony exports
-/******/     __webpack_require__.d = function(exports, name, getter) {
-/******/         if(!__webpack_require__.o(exports, name)) {
-/******/             Object.defineProperty(exports, name, { enumerable: true, get: getter });
-/******/         }
-/******/     };
+/******/ 	// define getter function for harmony exports
+/******/ 	__webpack_require__.d = function(exports, name, getter) {
+/******/ 		if(!__webpack_require__.o(exports, name)) {
+/******/ 			Object.defineProperty(exports, name, { enumerable: true, get: getter });
+/******/ 		}
+/******/ 	};
 /******/
-/******/     // define __esModule on exports
-/******/     __webpack_require__.r = function(exports) {
-/******/         if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
-/******/             Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
-/******/         }
-/******/         Object.defineProperty(exports, '__esModule', { value: true });
-/******/     };
+/******/ 	// define __esModule on exports
+/******/ 	__webpack_require__.r = function(exports) {
+/******/ 		if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
+/******/ 			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
+/******/ 		}
+/******/ 		Object.defineProperty(exports, '__esModule', { value: true });
+/******/ 	};
 /******/
-/******/     // create a fake namespace object
-/******/     // mode & 1: value is a module id, require it
-/******/     // mode & 2: merge all properties of value into the ns
-/******/     // mode & 4: return value when already ns object
-/******/     // mode & 8|1: behave like require
-/******/     __webpack_require__.t = function(value, mode) {
-/******/         if(mode & 1) value = __webpack_require__(value);
-/******/         if(mode & 8) return value;
-/******/         if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
-/******/         var ns = Object.create(null);
-/******/         __webpack_require__.r(ns);
-/******/         Object.defineProperty(ns, 'default', { enumerable: true, value: value });
-/******/         if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
-/******/         return ns;
-/******/     };
+/******/ 	// create a fake namespace object
+/******/ 	// mode & 1: value is a module id, require it
+/******/ 	// mode & 2: merge all properties of value into the ns
+/******/ 	// mode & 4: return value when already ns object
+/******/ 	// mode & 8|1: behave like require
+/******/ 	__webpack_require__.t = function(value, mode) {
+/******/ 		if(mode & 1) value = __webpack_require__(value);
+/******/ 		if(mode & 8) return value;
+/******/ 		if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
+/******/ 		var ns = Object.create(null);
+/******/ 		__webpack_require__.r(ns);
+/******/ 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
+/******/ 		if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
+/******/ 		return ns;
+/******/ 	};
 /******/
-/******/     // getDefaultExport function for compatibility with non-harmony modules
-/******/     __webpack_require__.n = function(module) {
-/******/         var getter = module && module.__esModule ?
-/******/             function getDefault() { return module['default']; } :
-/******/             function getModuleExports() { return module; };
-/******/         __webpack_require__.d(getter, 'a', getter);
-/******/         return getter;
-/******/     };
+/******/ 	// getDefaultExport function for compatibility with non-harmony modules
+/******/ 	__webpack_require__.n = function(module) {
+/******/ 		var getter = module && module.__esModule ?
+/******/ 			function getDefault() { return module['default']; } :
+/******/ 			function getModuleExports() { return module; };
+/******/ 		__webpack_require__.d(getter, 'a', getter);
+/******/ 		return getter;
+/******/ 	};
 /******/
-/******/     // Object.prototype.hasOwnProperty.call
-/******/     __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/ 	// Object.prototype.hasOwnProperty.call
+/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
 /******/
-/******/     // __webpack_public_path__
-/******/     __webpack_require__.p = "";
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
 /******/
 /******/
-/******/     // Load entry module and return exports
-/******/     return __webpack_require__(__webpack_require__.s = 0);
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(__webpack_require__.s = 0);
 /******/ })
 /************************************************************************/
 /******/ ([
@@ -2870,4 +2870,4 @@
 /***/ })
 /******/ ]);
 });
-//# sourceMappingURL=webidl2.js.map
+//# sourceMappingURL=webidl2.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/svg/filters/feTurbulence-negative-basefreq.html b/third_party/blink/web_tests/svg/filters/feTurbulence-negative-basefreq.html
index 4e67527..594f7db 100644
--- a/third_party/blink/web_tests/svg/filters/feTurbulence-negative-basefreq.html
+++ b/third_party/blink/web_tests/svg/filters/feTurbulence-negative-basefreq.html
@@ -15,13 +15,13 @@
 
     <!-- type=turbulence -->
     <filter id="tb1" x="0" y="0" width="1" height="1">
-        <feTurbulence type="fractalNoise" baseFrequency="-1 1"/>
+        <feTurbulence type="turbulence" baseFrequency="-1 1"/>
         <feColorMatrix values="1 0 0 0 0, 0 1 0 0 0.5, 0 0 1 0 0, 0 0 0 1 1"/>
     </filter>
     <rect width="100" height="100" filter="url(#tb1)" x="200"/>
 
     <filter id="tb2" x="0" y="0" width="1" height="1">
-        <feTurbulence type="fractalNoise" baseFrequency="1 -1"/>
+        <feTurbulence type="turbulence" baseFrequency="1 -1"/>
         <feColorMatrix values="1 0 0 0 0, 0 1 0 0 0.5, 0 0 1 0 0, 0 0 0 1 1"/>
     </filter>
     <rect width="100" height="100" filter="url(#tb2)" x="300"/>
diff --git a/third_party/blink/web_tests/virtual/force-defer-script/defer-script/async-script-expected.txt b/third_party/blink/web_tests/virtual/force-defer-script/defer-script/async-script-expected.txt
index 9160632..f10a2e52 100644
--- a/third_party/blink/web_tests/virtual/force-defer-script/defer-script/async-script-expected.txt
+++ b/third_party/blink/web_tests/virtual/force-defer-script/defer-script/async-script-expected.txt
@@ -1,4 +1,4 @@
-CONSOLE ERROR: line 3346: Uncaught Error: assert_equals: Normal script execution order comparison expected "Inline1;Sync1;EndOfBody;DOMContentLoaded;Async1;" but got "EndOfBody;Inline1;Sync1;DOMContentLoaded;Async1;"
+CONSOLE ERROR: line 3421: Uncaught Error: assert_equals: Normal script execution order comparison expected "Inline1;Sync1;EndOfBody;DOMContentLoaded;Async1;" but got "EndOfBody;Inline1;Sync1;DOMContentLoaded;Async1;"
 This is a testharness.js-based test.
 FAIL Async Script Execution Order (wrt possibly deferred Synchronous Script) Uncaught Error: assert_equals: Normal script execution order comparison expected "Inline1;Sync1;EndOfBody;DOMContentLoaded;Async1;" but got "EndOfBody;Inline1;Sync1;DOMContentLoaded;Async1;"
 Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/virtual/force-defer-script/defer-script/defer-script-expected.txt b/third_party/blink/web_tests/virtual/force-defer-script/defer-script/defer-script-expected.txt
index 2f9f63a..f88155f 100644
--- a/third_party/blink/web_tests/virtual/force-defer-script/defer-script/defer-script-expected.txt
+++ b/third_party/blink/web_tests/virtual/force-defer-script/defer-script/defer-script-expected.txt
@@ -1,4 +1,4 @@
-CONSOLE ERROR: line 3346: Uncaught Error: assert_equals: Normal defer script execution order comparison expected "Inline1;Sync1;Inline2;Sync2;EndOfBody;Defer1;Defer2;DOMContentLoaded;" but got "EndOfBody;Inline1;Sync1;Inline2;Sync2;Defer1;Defer2;DOMContentLoaded;"
+CONSOLE ERROR: line 3421: Uncaught Error: assert_equals: Normal defer script execution order comparison expected "Inline1;Sync1;Inline2;Sync2;EndOfBody;Defer1;Defer2;DOMContentLoaded;" but got "EndOfBody;Inline1;Sync1;Inline2;Sync2;Defer1;Defer2;DOMContentLoaded;"
 This is a testharness.js-based test.
 FAIL Defer Script Execution Order Uncaught Error: assert_equals: Normal defer script execution order comparison expected "Inline1;Sync1;Inline2;Sync2;EndOfBody;Defer1;Defer2;DOMContentLoaded;" but got "EndOfBody;Inline1;Sync1;Inline2;Sync2;Defer1;Defer2;DOMContentLoaded;"
 Harness: the test ran to completion.
diff --git a/tools/clang/scripts/build.py b/tools/clang/scripts/build.py
index 293558ade..7bd999b9 100755
--- a/tools/clang/scripts/build.py
+++ b/tools/clang/scripts/build.py
@@ -22,6 +22,14 @@
 import subprocess
 import sys
 
+
+try:
+  import urllib2 as urllib
+except ImportError:  # For Py3 compatibility
+  import urllib.request as urllib
+  import urllib.error as urllib
+
+
 from update import (CDS_URL, CHROMIUM_DIR, CLANG_REVISION, LLVM_BUILD_DIR,
                     FORCE_HEAD_REVISION_FILE, PACKAGE_VERSION, RELEASE_VERSION,
                     STAMP_FILE, CopyFile, CopyDiaDllTo, DownloadUrl,
@@ -128,19 +136,12 @@
   sys.exit(1)
 
 
-def UrlOpen(url):
-  # Normally we'd use urllib, but on our bots it can't connect to the GitHub API
-  # due to using too old TLS (see crbug.com/897796#c56). As a horrible
-  # workaround, shell out to curl instead. It seems curl is recent enough on all
-  # our machines that it can connect. On Windows it's in our gnuwin package.
-  # TODO(crbug.com/965937): Use urllib once our Python is recent enough.
-  return subprocess.check_output(['curl', '--silent', url])
-
 
 def GetLatestLLVMCommit():
   """Get the latest commit hash in the LLVM monorepo."""
-  ref = json.loads(UrlOpen(('https://api.github.com/repos/'
-                            'llvm/llvm-project/git/refs/heads/master')))
+  ref = json.loads(
+      urllib.urlopen(('https://api.github.com/repos/'
+                      'llvm/llvm-project/git/refs/heads/master')).read())
   assert ref['object']['type'] == 'commit'
   return ref['object']['sha']
 
@@ -394,12 +395,6 @@
   # Don't buffer stdout, so that print statements are immediately flushed.
   sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
 
-  # The gnuwin package also includes curl, which is needed to interact with the
-  # github API below.
-  # TODO(crbug.com/965937): Use urllib once our Python is recent enough, and
-  # move this down to where we fetch other build tools.
-  AddGnuWinToPath()
-
   # TODO(crbug.com/929645): Remove once we build on host systems with a modern
   # enough GCC to build Clang.
   MaybeDownloadHostGcc(args)
@@ -420,6 +415,7 @@
   WriteStampFile('', FORCE_HEAD_REVISION_FILE)
 
   AddCMakeToPath(args)
+  AddGnuWinToPath()
   DeleteChromeToolsShim()
 
 
diff --git a/tools/cygprofile/compare_orderfiles.py b/tools/cygprofile/compare_orderfiles.py
index 72bb65f..6e48a80 100755
--- a/tools/cygprofile/compare_orderfiles.py
+++ b/tools/cygprofile/compare_orderfiles.py
@@ -121,12 +121,13 @@
     commit_hash: (str) Git hash of the orderfile roll commit.
     clank_path: (str) Path to the clank repository.
   """
-  output = subprocess.check_output(
-      ['git', 'show', r'--format=%an %s', commit_hash], cwd=clank_path)
+  output = subprocess.check_output(['git', 'show', r'--format=%s', commit_hash],
+                                   cwd=clank_path)
   first_line = output.split('\n')[0]
-  # Capitalization changed at some point.
-  assert first_line.upper() == 'clank-autoroller Update Orderfile.'.upper(), (
-      'Not an orderfile commit')
+  # Capitalization changed at some point. Not checking the bot name because it
+  # changed too.
+  assert first_line.upper().endswith(
+      'Update Orderfile.'.upper()), ('Not an orderfile commit')
 
 
 def GetBeforeAfterOrderfileHashes(commit_hash, clank_path):
diff --git a/tools/metrics/BUILD.gn b/tools/metrics/BUILD.gn
index a97538c..2d277a1 100644
--- a/tools/metrics/BUILD.gn
+++ b/tools/metrics/BUILD.gn
@@ -104,6 +104,7 @@
 
     "//tools/metrics/ukm/ukm.xml",
     "//tools/metrics/ukm/codegen.py",
+    "//tools/metrics/ukm/codegen_test.py",
     "//tools/metrics/ukm/builders_template.py",
     "//tools/metrics/ukm/decode_template.py",
     "//tools/metrics/ukm/gen_builders.py",
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index aaecf6f0..2a3a492 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -39161,6 +39161,7 @@
   <int value="-164539906"
       label="OmniboxPreserveDefaultMatchAgainstAsyncUpdate:disabled"/>
   <int value="-161782023" label="AndroidMessagesProdEndpoint:enabled"/>
+  <int value="-160571071" label="InterestFeedV2:enabled"/>
   <int value="-159877930" label="MaterialDesignUserManager:disabled"/>
   <int value="-158549277" label="enable-embeddedsearch-api"/>
   <int value="-152677714" label="AsmJsToWebAssembly:enabled"/>
@@ -40502,6 +40503,7 @@
   <int value="1431934725" label="OmniboxAutocompleteTitles:disabled"/>
   <int value="1434515920" label="ReaderModeInCCT:enabled"/>
   <int value="1435251818" label="AutofillNoLocalSaveOnUploadSuccess:enabled"/>
+  <int value="1436454450" label="InterestFeedV2:disabled"/>
   <int value="1437413720" label="CooperativeScheduling:disabled"/>
   <int value="1441897340" label="AndroidSpellCheckerNonLowEnd:enabled"/>
   <int value="1442129147"
@@ -61792,6 +61794,13 @@
   <int value="17" label="TokenService::ExtractCredentials"/>
 </enum>
 
+<enum name="SourceIdConsistency">
+  <int value="0" label="Consistent"/>
+  <int value="1" label="ContainsInvalid"/>
+  <int value="2" label="NonUnique"/>
+  <int value="3" label="ContainsInvalidAndNonUnique"/>
+</enum>
+
 <enum name="SpareProcessMaybeTakeAction">
   <int value="0" label="NoSparePresent"/>
   <int value="1" label="MismatchedBrowserContext"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index b70dd3f..9122862 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -49226,6 +49226,19 @@
   <summary>Time to request a timestamp from the X server.</summary>
 </histogram>
 
+<histogram name="Event.LatencyInfo.Debug.SourceIdConsistency"
+    enum="SourceIdConsistency" expires_after="2020-09-01">
+  <owner>tommckee@chromium.org</owner>
+  <owner>speed-metrics-dev@chromium.org</owner>
+  <summary>
+    When tracking input event latency, we expect to associate some number of
+    latency records (instances of ui::LatencyInfo) with a CompositorFrame. Each
+    record is associated to a particular navigation through its ukm::SourceId
+    property. This histogram tracks how often the records of an individual frame
+    have varying or invalid source ids. See crbug.com/1062764.
+  </summary>
+</histogram>
+
 <histogram name="Event.MainThreadEventQueue.CoalescedCount" units="events"
     expires_after="2018-03-07">
   <obsolete>
@@ -73798,6 +73811,15 @@
   </summary>
 </histogram>
 
+<histogram name="Media.History.DatabaseSize" units="KB"
+    expires_after="2020-12-31">
+  <owner>beccahughes@chromium.org</owner>
+  <owner>media-dev@chromium.org</owner>
+  <summary>
+    The size of the Media History database. Recorded once on startup.
+  </summary>
+</histogram>
+
 <histogram name="Media.History.Init.Result" enum="MediaHistoryInitResult"
     expires_after="2020-12-31">
   <owner>beccahughes@chromium.org</owner>
@@ -190851,6 +190873,7 @@
     </obsolete>
   </suffix>
   <suffix name="DownloadDB" label="Databases for in-progress download."/>
+  <suffix name="DownloadService" label="Databases for download service."/>
   <suffix name="FeatureEngagementTrackerAvailabilityStore"
       label="Database for FeatureEngagementTracker feature availability."/>
   <suffix name="FeatureEngagementTrackerEventStore"
@@ -190888,6 +190911,9 @@
   <suffix name="PreviewsHintCacheStore" label="Databases for Previews Hints"/>
   <suffix name="PrintJobDatabase" label="Database for print job metadata."/>
   <suffix name="SharedDb" label="Shared database"/>
+  <suffix name="StrikeService" label="Database for strike service."/>
+  <suffix name="TabStateDatabase"
+      label="Database for NonCriticalPersistedTabData"/>
   <suffix name="UsageReportsBufferBackend"
       label="The result of the first attempt to open the usage reports buffer
              backend database."/>
@@ -190897,6 +190923,7 @@
       label="UsageStats database for TokenMappings."/>
   <suffix name="UsageStatsWebsiteEvent"
       label="UsageStats database for WebsiteEvents."/>
+  <suffix name="VideoDecodeStatsDB" label="Database for video decode stats"/>
   <affected-histogram name="LevelDB.ApproximateMemoryUse"/>
   <affected-histogram name="LevelDB.ApproximateMemTableMemoryUse"/>
   <affected-histogram name="LevelDB.Open"/>
diff --git a/tools/metrics/metrics_python_tests.py b/tools/metrics/metrics_python_tests.py
index d9e3572..38fd9ab 100755
--- a/tools/metrics/metrics_python_tests.py
+++ b/tools/metrics/metrics_python_tests.py
@@ -24,6 +24,7 @@
    'histograms/generate_expired_histograms_array_unittest.py',
    'histograms/pretty_print_test.py',
    'rappor/rappor_model_test.py',
+   'ukm/codegen_test.py',
    'ukm/gen_builders_test.py',
    'ukm/ukm_model_test.py',
    'ukm/xml_validations_test.py',
diff --git a/tools/metrics/ukm/codegen.py b/tools/metrics/ukm/codegen.py
index f4a0227..126f704d2 100644
--- a/tools/metrics/ukm/codegen.py
+++ b/tools/metrics/ukm/codegen.py
@@ -18,9 +18,9 @@
 
 
 def HashName(name):
-  # This must match the hash function in base/metrics/metric_hashes.cc
+  # This must match the hash function in //base/metrics/metrics_hashes.cc.
   # >Q: 8 bytes, big endian.
-  return struct.unpack('>Q', hashlib.md5(name).digest()[:8])[0]
+  return struct.unpack('>Q', hashlib.md5(name.encode()).digest()[:8])[0]
 
 
 class FileInfo(object):
diff --git a/tools/metrics/ukm/codegen_test.py b/tools/metrics/ukm/codegen_test.py
new file mode 100755
index 0000000..97748de9
--- /dev/null
+++ b/tools/metrics/ukm/codegen_test.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import unittest
+
+import codegen
+
+
+class CodegenTest(unittest.TestCase):
+  def testHash(self):
+    # Must match those in //base/metrics/metrics_hashes_unittest.cc.
+    self.assertEqual(codegen.HashName('Back'), 0x0557fa923dcee4d0)
+    self.assertEqual(codegen.HashName('Forward'), 0x67d2f6740a8eaebf)
+    self.assertEqual(codegen.HashName('NewTab'), 0x290eb683f96572f1)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/tools/perf/core/perf_data_generator.py b/tools/perf/core/perf_data_generator.py
index 057e9858..0fe1d7f 100755
--- a/tools/perf/core/perf_data_generator.py
+++ b/tools/perf/core/perf_data_generator.py
@@ -90,6 +90,10 @@
 
   ALL = (GENERIC, GTEST, TELEMETRY)
 
+# This is an opt-in list for tester which will skip the perf data handling.
+# The perf data will be handled on a separated 'processor' VM.
+# This list will be removed or replace by an opt-out list.
+LIGHTWEIGHT_TESTERS = ['linux-perf-fyi']
 
 FYI_BUILDERS = {
     'android-nexus5x-perf-fyi': {
@@ -1041,6 +1045,8 @@
   result['merge'] = {
       'script': '//tools/perf/process_perf_results.py',
   }
+  if builder_name in LIGHTWEIGHT_TESTERS:
+    result['merge']['args'] = ['--skip-perf']
 
   result['swarming'] = {
       # Always say this is true regardless of whether the tester
diff --git a/ui/accessibility/ax_node.cc b/ui/accessibility/ax_node.cc
index b2012c7..0db263e3 100644
--- a/ui/accessibility/ax_node.cc
+++ b/ui/accessibility/ax_node.cc
@@ -819,6 +819,7 @@
              item_role == ax::mojom::Role::kListItem ||
              item_role == ax::mojom::Role::kMenuItem ||
              item_role == ax::mojom::Role::kMenuItemRadio ||
+             item_role == ax::mojom::Role::kListBoxOption ||
              item_role == ax::mojom::Role::kTreeItem;
     case ax::mojom::Role::kMenu:
       return item_role == ax::mojom::Role::kMenuItem ||
diff --git a/ui/accessibility/ax_tree_unittest.cc b/ui/accessibility/ax_tree_unittest.cc
index c48318b..3cf7532 100644
--- a/ui/accessibility/ax_tree_unittest.cc
+++ b/ui/accessibility/ax_tree_unittest.cc
@@ -3647,6 +3647,43 @@
   EXPECT_OPTIONAL_EQ(2, item2->GetSetSize());
 }
 
+TEST(AXTreeTest, TestSetSizePostInSetListBoxOptionWithGroup) {
+  AXTreeUpdate initial_state;
+  initial_state.root_id = 1;
+  initial_state.nodes.resize(7);
+  initial_state.nodes[0].id = 1;
+  initial_state.nodes[0].child_ids = {2, 3};
+  initial_state.nodes[0].role = ax::mojom::Role::kListBox;
+  initial_state.nodes[1].id = 2;
+  initial_state.nodes[1].child_ids = {4, 5};
+  initial_state.nodes[1].role = ax::mojom::Role::kGroup;
+  initial_state.nodes[2].id = 3;
+  initial_state.nodes[2].child_ids = {6, 7};
+  initial_state.nodes[2].role = ax::mojom::Role::kGroup;
+  initial_state.nodes[3].id = 4;
+  initial_state.nodes[3].role = ax::mojom::Role::kListBoxOption;
+  initial_state.nodes[4].id = 5;
+  initial_state.nodes[4].role = ax::mojom::Role::kListBoxOption;
+  initial_state.nodes[5].id = 6;
+  initial_state.nodes[5].role = ax::mojom::Role::kListBoxOption;
+  initial_state.nodes[6].id = 7;
+  initial_state.nodes[6].role = ax::mojom::Role::kListBoxOption;
+  AXTree tree(initial_state);
+
+  AXNode* listbox_option1 = tree.GetFromId(4);
+  EXPECT_OPTIONAL_EQ(1, listbox_option1->GetPosInSet());
+  EXPECT_OPTIONAL_EQ(2, listbox_option1->GetSetSize());
+  AXNode* listbox_option2 = tree.GetFromId(5);
+  EXPECT_OPTIONAL_EQ(2, listbox_option2->GetPosInSet());
+  EXPECT_OPTIONAL_EQ(2, listbox_option2->GetSetSize());
+  AXNode* listbox_option3 = tree.GetFromId(6);
+  EXPECT_OPTIONAL_EQ(1, listbox_option3->GetPosInSet());
+  EXPECT_OPTIONAL_EQ(2, listbox_option3->GetSetSize());
+  AXNode* listbox_option4 = tree.GetFromId(7);
+  EXPECT_OPTIONAL_EQ(2, listbox_option4->GetPosInSet());
+  EXPECT_OPTIONAL_EQ(2, listbox_option4->GetSetSize());
+}
+
 TEST(AXTreeTest, OnNodeWillBeDeletedHasValidUnignoredParent) {
   AXTreeUpdate initial_state;
   initial_state.root_id = 1;
diff --git a/ui/display/mac/screen_mac.mm b/ui/display/mac/screen_mac.mm
index de59943..0d646134 100644
--- a/ui/display/mac/screen_mac.mm
+++ b/ui/display/mac/screen_mac.mm
@@ -148,6 +148,51 @@
   return BuildDisplayForScreen([[NSScreen screens] firstObject]);
 }
 
+std::vector<Display> BuildDisplaysFromQuartz() {
+  // Don't just return all online displays.  This would include displays
+  // that mirror other displays, which are not desired in this list.  It's
+  // tempting to use the count returned by CGGetActiveDisplayList, but active
+  // displays exclude sleeping displays, and those are desired.
+
+  // It would be ridiculous to have this many displays connected, but
+  // CGDirectDisplayID is just an integer, so supporting up to this many
+  // doesn't hurt.
+  CGDirectDisplayID online_displays[1024];
+  CGDisplayCount online_display_count = 0;
+  if (CGGetOnlineDisplayList(base::size(online_displays), online_displays,
+                             &online_display_count) != kCGErrorSuccess) {
+    return std::vector<Display>(1, BuildPrimaryDisplay());
+  }
+
+  typedef std::map<int64_t, NSScreen*> ScreenIdsToScreensMap;
+  ScreenIdsToScreensMap screen_ids_to_screens;
+  for (NSScreen* screen in [NSScreen screens]) {
+    NSDictionary* screen_device_description = [screen deviceDescription];
+    int64_t screen_id = [[screen_device_description
+        objectForKey:@"NSScreenNumber"] unsignedIntValue];
+    screen_ids_to_screens[screen_id] = screen;
+  }
+
+  std::vector<Display> displays;
+  for (CGDisplayCount online_display_index = 0;
+       online_display_index < online_display_count; ++online_display_index) {
+    CGDirectDisplayID online_display = online_displays[online_display_index];
+    if (CGDisplayMirrorsDisplay(online_display) == kCGNullDirectDisplay) {
+      // If this display doesn't mirror any other, include it in the list.
+      // The primary display in a mirrored set will be counted, but those that
+      // mirror it will not be.
+      ScreenIdsToScreensMap::iterator foundScreen =
+          screen_ids_to_screens.find(online_display);
+      if (foundScreen != screen_ids_to_screens.end()) {
+        displays.push_back(BuildDisplayForScreen(foundScreen->second));
+      }
+    }
+  }
+
+  return displays.empty() ? std::vector<Display>(1, BuildPrimaryDisplay())
+                          : displays;
+}
+
 // Returns the minimum Manhattan distance from |point| to corners of |screen|
 // frame.
 CGFloat GetMinimumDistanceToCorner(const NSPoint& point, NSScreen* screen) {
@@ -321,7 +366,9 @@
     // on Catalina, it has been observed that -[NSScreen screens] changes before
     // any notifications are received.
     // https://crbug.com/1021340.
-    OnNSScreensMayHaveChanged();
+    // When this happens, do not update |displays_|, because we may be iterating
+    // it higher up in the stack.
+    // https://crbug.com/1033866
     DLOG(ERROR) << "Value of -[NSScreen screens] changed before notification.";
     return BuildDisplayForScreen(screen);
   }
@@ -339,51 +386,6 @@
     old_displays_ = displays_;
   }
 
-  std::vector<Display> BuildDisplaysFromQuartz() const {
-    // Don't just return all online displays.  This would include displays
-    // that mirror other displays, which are not desired in this list.  It's
-    // tempting to use the count returned by CGGetActiveDisplayList, but active
-    // displays exclude sleeping displays, and those are desired.
-
-    // It would be ridiculous to have this many displays connected, but
-    // CGDirectDisplayID is just an integer, so supporting up to this many
-    // doesn't hurt.
-    CGDirectDisplayID online_displays[1024];
-    CGDisplayCount online_display_count = 0;
-    if (CGGetOnlineDisplayList(base::size(online_displays), online_displays,
-                               &online_display_count) != kCGErrorSuccess) {
-      return std::vector<Display>(1, BuildPrimaryDisplay());
-    }
-
-    typedef std::map<int64_t, NSScreen*> ScreenIdsToScreensMap;
-    ScreenIdsToScreensMap screen_ids_to_screens;
-    for (NSScreen* screen in [NSScreen screens]) {
-      NSDictionary* screen_device_description = [screen deviceDescription];
-      int64_t screen_id = [[screen_device_description
-          objectForKey:@"NSScreenNumber"] unsignedIntValue];
-      screen_ids_to_screens[screen_id] = screen;
-    }
-
-    std::vector<Display> displays;
-    for (CGDisplayCount online_display_index = 0;
-         online_display_index < online_display_count; ++online_display_index) {
-      CGDirectDisplayID online_display = online_displays[online_display_index];
-      if (CGDisplayMirrorsDisplay(online_display) == kCGNullDirectDisplay) {
-        // If this display doesn't mirror any other, include it in the list.
-        // The primary display in a mirrored set will be counted, but those that
-        // mirror it will not be.
-        ScreenIdsToScreensMap::iterator foundScreen =
-            screen_ids_to_screens.find(online_display);
-        if (foundScreen != screen_ids_to_screens.end()) {
-          displays.push_back(BuildDisplayForScreen(foundScreen->second));
-        }
-      }
-    }
-
-    return displays.empty() ? std::vector<Display>(1, BuildPrimaryDisplay())
-                            : displays;
-  }
-
   void OnNSScreensMayHaveChanged() const {
     // Timer::Reset() ensures at least another interval passes before the
     // associated task runs, effectively coalescing these events.
diff --git a/weblayer/BUILD.gn b/weblayer/BUILD.gn
index 48a8f32..c78673d 100644
--- a/weblayer/BUILD.gn
+++ b/weblayer/BUILD.gn
@@ -262,6 +262,7 @@
     "//components/variations/service",
     "//components/version_info",
     "//components/web_cache/browser",
+    "//components/webrtc",
     "//content:content_resources",
     "//content:dev_ui_content_resources",
     "//content/app/resources",
diff --git a/weblayer/browser/DEPS b/weblayer/browser/DEPS
index ef4fd49..38e7d8c 100644
--- a/weblayer/browser/DEPS
+++ b/weblayer/browser/DEPS
@@ -38,6 +38,7 @@
   "+components/variations",
   "+components/version_info",
   "+components/web_cache/browser",
+  "+components/webrtc",
   "+content/public",
   "+mojo/public",
   "+net",
diff --git a/weblayer/browser/android/permission_request_utils.cc b/weblayer/browser/android/permission_request_utils.cc
index ea1bd584..8ae2da02 100644
--- a/weblayer/browser/android/permission_request_utils.cc
+++ b/weblayer/browser/android/permission_request_utils.cc
@@ -4,16 +4,20 @@
 
 #include "weblayer/browser/android/permission_request_utils.h"
 
+#include <algorithm>
+
 #include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
 #include "content/public/browser/web_contents.h"
 #include "ui/android/window_android.h"
 #include "weblayer/browser/java/jni/PermissionRequestUtils_jni.h"
 
 namespace weblayer {
 
-void RequestAndroidPermission(content::WebContents* web_contents,
-                              ContentSettingsType content_settings_type,
-                              PermissionUpdatedCallback callback) {
+void RequestAndroidPermissions(
+    content::WebContents* web_contents,
+    const std::vector<ContentSettingsType>& content_settings_types,
+    PermissionsUpdatedCallback callback) {
   if (!web_contents) {
     std::move(callback).Run(false);
     return;
@@ -24,19 +28,27 @@
     std::move(callback).Run(false);
     return;
   }
+
+  std::vector<int> content_settings_ints;
+  std::transform(content_settings_types.begin(), content_settings_types.end(),
+                 content_settings_ints.begin(), [](ContentSettingsType type) {
+                   return static_cast<int>(type);
+                 });
+
   // The callback allocated here will be deleted in the call to OnResult, which
   // is guaranteed to be called.
   Java_PermissionRequestUtils_requestPermission(
       base::android::AttachCurrentThread(), window->GetJavaObject(),
       reinterpret_cast<jlong>(
-          new PermissionUpdatedCallback(std::move(callback))),
-      static_cast<int>(content_settings_type));
+          new PermissionsUpdatedCallback(std::move(callback))),
+      base::android::ToJavaIntArray(base::android::AttachCurrentThread(),
+                                    content_settings_ints));
 }
 
 void JNI_PermissionRequestUtils_OnResult(JNIEnv* env,
                                          jlong callback_ptr,
                                          jboolean result) {
-  auto* callback = reinterpret_cast<PermissionUpdatedCallback*>(callback_ptr);
+  auto* callback = reinterpret_cast<PermissionsUpdatedCallback*>(callback_ptr);
   std::move(*callback).Run(result);
   delete callback;
 }
diff --git a/weblayer/browser/android/permission_request_utils.h b/weblayer/browser/android/permission_request_utils.h
index d541fa6..5b9dc5d 100644
--- a/weblayer/browser/android/permission_request_utils.h
+++ b/weblayer/browser/android/permission_request_utils.h
@@ -5,6 +5,8 @@
 #ifndef WEBLAYER_BROWSER_ANDROID_PERMISSION_REQUEST_UTILS_H_
 #define WEBLAYER_BROWSER_ANDROID_PERMISSION_REQUEST_UTILS_H_
 
+#include <vector>
+
 #include "base/callback_forward.h"
 #include "components/content_settings/core/common/content_settings_types.h"
 
@@ -14,14 +16,15 @@
 
 namespace weblayer {
 
-using PermissionUpdatedCallback = base::OnceCallback<void(bool)>;
+using PermissionsUpdatedCallback = base::OnceCallback<void(bool)>;
 
 // Requests all necessary Android permissions related to
-// |content_settings_type|, and calls |callback|. |callback| will be called with
-// true if all permissions were successfully granted, and false otherwise.
-void RequestAndroidPermission(content::WebContents* web_contents,
-                              ContentSettingsType content_settings_type,
-                              PermissionUpdatedCallback callback);
+// |content_settings_types|, and calls |callback|. |callback| will be called
+// with true if all permissions were successfully granted, and false otherwise.
+void RequestAndroidPermissions(
+    content::WebContents* web_contents,
+    const std::vector<ContentSettingsType>& content_settings_type,
+    PermissionsUpdatedCallback callback);
 
 }  // namespace weblayer
 
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/UrlBarControllerImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/UrlBarControllerImpl.java
index 4cbe929d..a103f21 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/UrlBarControllerImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/UrlBarControllerImpl.java
@@ -19,6 +19,7 @@
 import org.chromium.base.LifetimeAssert;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.annotations.NativeMethods;
+import org.chromium.components.omnibox.SecurityButtonAnimationDelegate;
 import org.chromium.components.omnibox.SecurityStatusIcon;
 import org.chromium.weblayer_private.interfaces.IObjectWrapper;
 import org.chromium.weblayer_private.interfaces.IUrlBarController;
@@ -77,6 +78,7 @@
         private float mTextSize;
         private TextView mUrlTextView;
         private ImageButton mSecurityButton;
+        private final SecurityButtonAnimationDelegate mSecurityButtonAnimationDelegate;
 
         public UrlBarView(@NonNull Context context, Bundle options) {
             super(context);
@@ -86,6 +88,8 @@
             setBackgroundColor(Color.TRANSPARENT);
             mUrlTextView = findViewById(R.id.url_text);
             mSecurityButton = (ImageButton) findViewById(R.id.security_button);
+            mSecurityButtonAnimationDelegate = new SecurityButtonAnimationDelegate(
+                    mSecurityButton, mUrlTextView, R.dimen.security_status_icon_size);
 
             updateView();
         }
@@ -120,7 +124,7 @@
             mUrlTextView.setTextSize(
                     TypedValue.COMPLEX_UNIT_SP, Math.max(MINIMUM_TEXT_SIZE, mTextSize));
 
-            mSecurityButton.setImageResource(getSecurityIcon());
+            mSecurityButtonAnimationDelegate.updateSecurityButton(getSecurityIcon());
             mSecurityButton.setContentDescription(getContext().getResources().getString(
                     SecurityStatusIcon.getSecurityIconContentDescriptionResourceId(
                             UrlBarControllerImplJni.get().getConnectionSecurityLevel(
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/WebLayerFactoryImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/WebLayerFactoryImpl.java
index 4289b9d0..77cef846 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/WebLayerFactoryImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/WebLayerFactoryImpl.java
@@ -48,7 +48,7 @@
     @Override
     public boolean isClientSupported() {
         StrictModeWorkaround.apply();
-        return Math.abs(sClientMajorVersion - getImplementationMajorVersion()) <= 3;
+        return Math.abs(sClientMajorVersion - getImplementationMajorVersion()) <= 4;
     }
 
     /**
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/permissions/PermissionRequestUtils.java b/weblayer/browser/java/org/chromium/weblayer_private/permissions/PermissionRequestUtils.java
index f5f71cf..5d6157b5 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/permissions/PermissionRequestUtils.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/permissions/PermissionRequestUtils.java
@@ -16,10 +16,9 @@
 public final class PermissionRequestUtils {
     @CalledByNative
     private static void requestPermission(
-            WindowAndroid windowAndroid, long nativeCallback, int contentSettingsType) {
+            WindowAndroid windowAndroid, long nativeCallback, int[] contentSettingsTypes) {
         if (!AndroidPermissionRequester.requestAndroidPermissions(windowAndroid,
-                    new int[] {contentSettingsType},
-                    new AndroidPermissionRequester.RequestDelegate() {
+                    contentSettingsTypes, new AndroidPermissionRequester.RequestDelegate() {
                         @Override
                         public void onAndroidPermissionAccepted() {
                             PermissionRequestUtilsJni.get().onResult(nativeCallback, true);
@@ -29,8 +28,7 @@
                         public void onAndroidPermissionCanceled() {
                             PermissionRequestUtilsJni.get().onResult(nativeCallback, false);
                         }
-                    },
-                    new PermissionsClient())) {
+                    }, new PermissionsClient())) {
             PermissionRequestUtilsJni.get().onResult(nativeCallback, false);
         }
     }
diff --git a/weblayer/browser/java/res/layout/weblayer_url_bar.xml b/weblayer/browser/java/res/layout/weblayer_url_bar.xml
index dc83dfd9..f5a647e7 100644
--- a/weblayer/browser/java/res/layout/weblayer_url_bar.xml
+++ b/weblayer/browser/java/res/layout/weblayer_url_bar.xml
@@ -17,6 +17,7 @@
         android:layout_height="@dimen/security_status_icon_size"
         android:layout_width="@dimen/security_status_icon_size"
         android:scaleType="center"
+        android:visibility="gone"
         app:tint="@color/default_icon_color_tint_list"/>
 
     <TextView
diff --git a/weblayer/browser/permissions/geolocation_permission_context_delegate.cc b/weblayer/browser/permissions/geolocation_permission_context_delegate.cc
index 04a6fc4..5c543585 100644
--- a/weblayer/browser/permissions/geolocation_permission_context_delegate.cc
+++ b/weblayer/browser/permissions/geolocation_permission_context_delegate.cc
@@ -7,10 +7,6 @@
 #include "build/build_config.h"
 
 #if defined(OS_ANDROID)
-#include "components/content_settings/core/common/content_settings.h"
-#include "components/permissions/permission_util.h"
-#include "content/public/browser/web_contents.h"
-#include "ui/android/window_android.h"
 #include "weblayer/browser/android/permission_request_utils.h"
 #include "weblayer/browser/browser_context_impl.h"
 #include "weblayer/browser/tab_impl.h"
@@ -34,33 +30,6 @@
     bool allowed) {}
 
 #if defined(OS_ANDROID)
-bool GeolocationPermissionContextDelegate::
-    ShouldRequestAndroidLocationPermission(content::WebContents* web_contents) {
-  if (!web_contents)
-    return false;
-
-  auto* window_android = web_contents->GetTopLevelNativeWindow();
-  if (!window_android)
-    return false;
-
-  std::vector<std::string> android_permissions;
-  permissions::PermissionUtil::GetAndroidPermissionsForContentSetting(
-      ContentSettingsType::GEOLOCATION, &android_permissions);
-
-  for (const auto& android_permission : android_permissions) {
-    if (!window_android->HasPermission(android_permission))
-      return true;
-  }
-  return false;
-}
-
-void GeolocationPermissionContextDelegate::RequestAndroidPermission(
-    content::WebContents* web_contents,
-    PermissionUpdatedCallback callback) {
-  weblayer::RequestAndroidPermission(
-      web_contents, ContentSettingsType::GEOLOCATION, std::move(callback));
-}
-
 bool GeolocationPermissionContextDelegate::IsInteractable(
     content::WebContents* web_contents) {
   auto* tab = TabImpl::FromWebContents(web_contents);
diff --git a/weblayer/browser/permissions/geolocation_permission_context_delegate.h b/weblayer/browser/permissions/geolocation_permission_context_delegate.h
index 14422a9..f9c2756a 100644
--- a/weblayer/browser/permissions/geolocation_permission_context_delegate.h
+++ b/weblayer/browser/permissions/geolocation_permission_context_delegate.h
@@ -32,10 +32,6 @@
                         const GURL& requesting_frame,
                         bool allowed) override;
 #if defined(OS_ANDROID)
-  bool ShouldRequestAndroidLocationPermission(
-      content::WebContents* web_contents) override;
-  void RequestAndroidPermission(content::WebContents* web_contents,
-                                PermissionUpdatedCallback callback) override;
   bool IsInteractable(content::WebContents* web_contents) override;
   PrefService* GetPrefs(content::BrowserContext* browser_context) override;
   bool IsRequestingOriginDSE(content::BrowserContext* browser_context,
diff --git a/weblayer/browser/permissions/permission_manager_factory.cc b/weblayer/browser/permissions/permission_manager_factory.cc
index f1873fcb..0360af8 100644
--- a/weblayer/browser/permissions/permission_manager_factory.cc
+++ b/weblayer/browser/permissions/permission_manager_factory.cc
@@ -73,6 +73,15 @@
           blink::mojom::FeaturePolicyFeature::kEncryptedMedia);
 #endif
 
+  permission_contexts[ContentSettingsType::MEDIASTREAM_MIC] =
+      std::make_unique<SafePermissionContext>(
+          browser_context, ContentSettingsType::MEDIASTREAM_MIC,
+          blink::mojom::FeaturePolicyFeature::kMicrophone);
+  permission_contexts[ContentSettingsType::MEDIASTREAM_CAMERA] =
+      std::make_unique<SafePermissionContext>(
+          browser_context, ContentSettingsType::MEDIASTREAM_CAMERA,
+          blink::mojom::FeaturePolicyFeature::kCamera);
+
   // For now, all requests are denied. As features are added, their permission
   // contexts can be added here instead of DeniedPermissionContext.
   for (content::PermissionType type : content::GetAllPermissionTypes()) {
diff --git a/weblayer/browser/permissions/weblayer_permissions_client.cc b/weblayer/browser/permissions/weblayer_permissions_client.cc
index 447a3a4c..9b27ab1 100644
--- a/weblayer/browser/permissions/weblayer_permissions_client.cc
+++ b/weblayer/browser/permissions/weblayer_permissions_client.cc
@@ -9,6 +9,7 @@
 #include "weblayer/browser/permissions/permission_manager_factory.h"
 
 #if defined(OS_ANDROID)
+#include "weblayer/browser/android/permission_request_utils.h"
 #include "weblayer/browser/android/resource_mapper.h"
 #endif
 
@@ -44,6 +45,14 @@
 }
 
 #if defined(OS_ANDROID)
+void WebLayerPermissionsClient::RepromptForAndroidPermissions(
+    content::WebContents* web_contents,
+    const std::vector<ContentSettingsType>& content_settings_types,
+    PermissionsUpdatedCallback callback) {
+  RequestAndroidPermissions(web_contents, content_settings_types,
+                            std::move(callback));
+}
+
 int WebLayerPermissionsClient::MapToJavaDrawableId(int resource_id) {
   return weblayer::MapToJavaDrawableId(resource_id);
 }
diff --git a/weblayer/browser/permissions/weblayer_permissions_client.h b/weblayer/browser/permissions/weblayer_permissions_client.h
index 0998e34..1d7f899a 100644
--- a/weblayer/browser/permissions/weblayer_permissions_client.h
+++ b/weblayer/browser/permissions/weblayer_permissions_client.h
@@ -30,6 +30,10 @@
       content::BrowserContext* browser_context,
       ContentSettingsType type) override;
 #if defined(OS_ANDROID)
+  void RepromptForAndroidPermissions(
+      content::WebContents* web_contents,
+      const std::vector<ContentSettingsType>& content_settings_types,
+      PermissionsUpdatedCallback callback) override;
   int MapToJavaDrawableId(int resource_id) override;
 #endif
 
diff --git a/weblayer/browser/tab_impl.cc b/weblayer/browser/tab_impl.cc
index 53ef2b56..277e1b8 100644
--- a/weblayer/browser/tab_impl.cc
+++ b/weblayer/browser/tab_impl.cc
@@ -14,8 +14,11 @@
 #include "components/captive_portal/core/buildflags.h"
 #include "components/find_in_page/find_tab_helper.h"
 #include "components/find_in_page/find_types.h"
+#include "components/permissions/permission_manager.h"
 #include "components/permissions/permission_request_manager.h"
+#include "components/permissions/permission_result.h"
 #include "components/sessions/content/session_tab_helper.h"
+#include "components/webrtc/media_stream_devices_controller.h"
 #include "content/public/browser/file_select_listener.h"
 #include "content/public/browser/interstitial_page.h"
 #include "content/public/browser/navigation_controller.h"
@@ -32,6 +35,7 @@
 #include "weblayer/browser/i18n_util.h"
 #include "weblayer/browser/isolated_world_ids.h"
 #include "weblayer/browser/navigation_controller_impl.h"
+#include "weblayer/browser/permissions/permission_manager_factory.h"
 #include "weblayer/browser/persistence/browser_persister.h"
 #include "weblayer/browser/profile_impl.h"
 #include "weblayer/public/fullscreen_delegate.h"
@@ -545,6 +549,41 @@
   return true;
 }
 
+void TabImpl::RequestMediaAccessPermission(
+    content::WebContents* web_contents,
+    const content::MediaStreamRequest& request,
+    content::MediaResponseCallback callback) {
+  webrtc::MediaStreamDevicesController::RequestPermissions(
+      request, nullptr,
+      base::BindOnce(
+          [](content::MediaResponseCallback callback,
+             const blink::MediaStreamDevices& devices,
+             blink::mojom::MediaStreamRequestResult result,
+             bool blocked_by_feature_policy, ContentSetting audio_setting,
+             ContentSetting video_setting) {
+            std::move(callback).Run(devices, result, {});
+          },
+          base::Passed(std::move(callback))));
+}
+
+bool TabImpl::CheckMediaAccessPermission(
+    content::RenderFrameHost* render_frame_host,
+    const GURL& security_origin,
+    blink::mojom::MediaStreamType type) {
+  DCHECK(type == blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE ||
+         type == blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE);
+  ContentSettingsType content_settings_type =
+      type == blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE
+          ? ContentSettingsType::MEDIASTREAM_MIC
+          : ContentSettingsType::MEDIASTREAM_CAMERA;
+  return PermissionManagerFactory::GetForBrowserContext(
+             content::WebContents::FromRenderFrameHost(render_frame_host)
+                 ->GetBrowserContext())
+             ->GetPermissionStatusForFrame(content_settings_type,
+                                           render_frame_host, security_origin)
+             .content_setting == CONTENT_SETTING_ALLOW;
+}
+
 void TabImpl::EnterFullscreenModeForTab(
     content::WebContents* web_contents,
     const GURL& origin,
diff --git a/weblayer/browser/tab_impl.h b/weblayer/browser/tab_impl.h
index a1bc367..a76e9cc 100644
--- a/weblayer/browser/tab_impl.h
+++ b/weblayer/browser/tab_impl.h
@@ -172,6 +172,13 @@
   bool DoBrowserControlsShrinkRendererSize(
       const content::WebContents* web_contents) override;
   bool EmbedsFullscreenWidget() override;
+  void RequestMediaAccessPermission(
+      content::WebContents* web_contents,
+      const content::MediaStreamRequest& request,
+      content::MediaResponseCallback callback) override;
+  bool CheckMediaAccessPermission(content::RenderFrameHost* render_frame_host,
+                                  const GURL& security_origin,
+                                  blink::mojom::MediaStreamType type) override;
   void EnterFullscreenModeForTab(
       content::WebContents* web_contents,
       const GURL& origin,
diff --git a/weblayer/public/java/org/chromium/weblayer/WebLayer.java b/weblayer/public/java/org/chromium/weblayer/WebLayer.java
index 3a317fe..9d8ed3d 100644
--- a/weblayer/public/java/org/chromium/weblayer/WebLayer.java
+++ b/weblayer/public/java/org/chromium/weblayer/WebLayer.java
@@ -279,11 +279,14 @@
                 }
                 Class factoryClass = remoteClassLoader.loadClass(
                         "org.chromium.weblayer_private.WebLayerFactoryImpl");
+                // NOTE: the 20 comes from the previous scheme of incrementing versioning. It must
+                // remain at 20 for Chrome version 79.
+                // TODO(https://crbug.com/1031830): change 20 to -1 when tip of tree is at 83.
                 mFactory = IWebLayerFactory.Stub.asInterface(
                         (IBinder) factoryClass
                                 .getMethod("create", String.class, int.class, int.class)
                                 .invoke(null, WebLayerClientVersionConstants.PRODUCT_VERSION,
-                                        WebLayerClientVersionConstants.PRODUCT_MAJOR_VERSION, -1));
+                                        WebLayerClientVersionConstants.PRODUCT_MAJOR_VERSION, 20));
                 available = mFactory.isClientSupported();
                 majorVersion = mFactory.getImplementationMajorVersion();
                 version = mFactory.getImplementationVersion();