diff --git a/DEPS b/DEPS
index 9b88f31f..3627356 100644
--- a/DEPS
+++ b/DEPS
@@ -209,7 +209,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': 'ffeef16664ea6788023526de0705fa451881c407',
+  'skia_revision': 'b2a2e7c9e3fe50a3a77a068ae6dd29661f4dcab4',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -221,11 +221,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': '7444466cf7d22ce1c174931159839d422c76b917',
+  'angle_revision': '3b2ef1cdbecc0f83cd6191549dd4369bdc9b9d8e',
   # 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': '112faf441539267feaf9f0d84ce5181489f23b34',
+  'swiftshader_revision': '484a3e15893c769304509bf1607a42f205a7002d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -256,7 +256,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling NaCl
   # and whatever else without interference from each other.
-  'nacl_revision': '5dec1e72264c1ff96cd8f42e86e5ce30364c4c0c',
+  'nacl_revision': 'b99d3e6cccb5004a5e400f1531cfdac033014d39',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
@@ -288,7 +288,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': '11eafd8825130a221102f537c53cb1ce6f44b3e5',
+  'devtools_frontend_revision': 'eacc13c35821daac752d59480018e6573b13a5eb',
   # 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.
@@ -372,7 +372,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'libcxxabi_revision':    'f6a8e55d6e7719b835f3bdbe082245b8dd86cc4d',
+  'libcxxabi_revision':    '5f51521620a9229a8e7956a5a302031d3fc28b5f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -1338,7 +1338,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'ee1e7359d1d243b3f4059a659c2bb0684c2b3fd0',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'ea617741a1bcc0951495c6f8fcee219e1fc563d0',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1416,7 +1416,7 @@
       'packages': [
           {
               'package': 'fuchsia/third_party/aemu/linux-amd64',
-              'version': 'KmvDYKZZ-xQz7ZzzSKBh3HrWPpQv89vfFY9mBp6RDq4C'
+              'version': 'DwlFUQdUj3INh9MLxAcJZ3wbIZfyV56T7mDDgiUWq3oC'
           },
       ],
       'condition': 'host_os == "linux" and checkout_fuchsia',
@@ -1477,7 +1477,7 @@
     Var('chromium_git') + '/external/github.com/google/snappy.git' + '@' + 'ea368c2f07de5f31146a10214f27d15091b09771',
 
   'src/third_party/sqlite/src':
-    Var('chromium_git') + '/chromium/deps/sqlite.git' + '@' + '60efbb3584af2ba05e1935b3e07d3a861841db7c',
+    Var('chromium_git') + '/chromium/deps/sqlite.git' + '@' + '144e06fad93722336e6b066071b941b1763f6f18',
 
   'src/third_party/sqlite4java': {
       'packages': [
@@ -1552,7 +1552,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '267e0b71649664a27d79f23773f0bde9e0e1164f',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '2a605b13339db11a2e9052c35c41f899a53b098a',
+    Var('webrtc_git') + '/src.git' + '@' + 'b2f8c1675d74b860c63f97a38e63f3578505391d',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1613,7 +1613,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@c6bebbe6eaada749593f277d91df7281de7fe04a',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@efad8beeee3b28d639831c927fd97328cb571a4a',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/ash/content/scanning/resources/scanning_app.js b/ash/content/scanning/resources/scanning_app.js
index 78fa45c..0241fc54 100644
--- a/ash/content/scanning/resources/scanning_app.js
+++ b/ash/content/scanning/resources/scanning_app.js
@@ -29,11 +29,12 @@
 import {CrContainerShadowBehavior} from 'chrome://resources/cr_elements/cr_container_shadow_behavior.m.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {afterNextRender, html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getScanService} from './mojo_interface_provider.js';
-import {AppState, ScannerArr} from './scanning_app_types.js';
-import {colorModeFromString, fileTypeFromString, pageSizeFromString, tokenToString} from './scanning_app_util.js';
+import {AppState, ScannerArr, ScannerCapabilitiesResponse, ScannerInfo, ScannerSetting, ScanSettings} from './scanning_app_types.js';
+import {colorModeFromString, fileTypeFromString, getScannerDisplayName, pageSizeFromString, tokenToString} from './scanning_app_util.js';
 import {ScanningBrowserProxy, ScanningBrowserProxyImpl} from './scanning_browser_proxy.js';
 
 /**
@@ -62,8 +63,8 @@
   /** @private {?ash.scanning.mojom.ScanServiceInterface} */
   scanService_: null,
 
-  /** @private {!Map<string, !mojoBase.mojom.UnguessableToken>} */
-  scannerIds_: new Map(),
+  /** @private {!Map<string, !ScannerInfo>} */
+  scannerInfoMap_: new Map(),
 
   /** @private {?ScanningBrowserProxy}*/
   browserProxy_: null,
@@ -272,6 +273,17 @@
       type: String,
       computed: 'computeScanFailedDialogTextKey_(scanResult_)',
     },
+
+    /** @private {boolean} */
+    scanAppStickySettingsEnabled_: {
+      type: Boolean,
+      value: function() {
+        return loadTimeData.getBoolean('scanAppStickySettingsEnabled');
+      }
+    },
+
+    /** @private {?ScanSettings} */
+    savedScanSettings_: Object,
   },
 
   observers:
@@ -288,6 +300,17 @@
         /* @type {string} */ (myFilesPath) => {
           this.selectedFilePath = myFilesPath;
         });
+    if (this.scanAppStickySettingsEnabled_) {
+      this.browserProxy_.getScanSettings().then(
+          /* @type {string} */ (scanSettings) => {
+            if (!scanSettings) {
+              return;
+            }
+
+            this.savedScanSettings_ =
+                /** @type {!ScanSettings} */ (JSON.parse(scanSettings));
+          });
+    }
   },
 
   /** @override */
@@ -388,16 +411,19 @@
   },
 
   /**
-   * @param {!{capabilities: !ash.scanning.mojom.ScannerCapabilities}}
-   *     response
+   * @param {!ash.scanning.mojom.ScannerCapabilities} capabilities
    * @private
    */
-  onCapabilitiesReceived_(response) {
-    this.capabilities_ = response.capabilities;
+  onCapabilitiesReceived_(capabilities) {
+    this.capabilities_ = capabilities;
     this.capabilities_.sources.forEach(
         (source) => this.sourceTypeMap_.set(source.name, source.type));
     this.selectedFileType = ash.scanning.mojom.FileType.kPdf.toString();
-    this.setAppState_(AppState.READY);
+
+    this.setAppState_(
+        this.savedScanSettings_ && this.scanAppStickySettingsEnabled_ ?
+            AppState.SETTING_SAVED_SETTINGS :
+            AppState.READY);
   },
 
   /**
@@ -411,7 +437,7 @@
     }
 
     for (const scanner of response.scanners) {
-      this.scannerIds_.set(tokenToString(scanner.id), scanner.id);
+      this.setScannerInfo_(scanner);
     }
 
     this.setAppState_(AppState.GOT_SCANNERS);
@@ -420,23 +446,17 @@
 
   /** @private */
   onSelectedScannerIdChange_() {
-    if (!this.scannerIds_.has(this.selectedScannerId)) {
-      return;
-    }
+    assert(this.isSelectedScannerKnown_());
 
     // If |selectedScannerId| is changed when the app's in a non-READY state,
     // that change was triggered by the app's initial load so it's not counted.
     this.numScanSettingChanges_ = this.appState_ === AppState.READY ? 1 : 0;
     this.setAppState_(AppState.GETTING_CAPS);
 
-    this.scanService_
-        .getScannerCapabilities(this.scannerIds_.get(this.selectedScannerId))
-        .then(
-            /*@type {!{capabilities:
-                   !ash.scanning.mojom.ScannerCapabilities}}*/
-            (response) => {
-              this.onCapabilitiesReceived_(response);
-            });
+    this.getSelectedScannerCapabilities_().then(
+        /*@type {!ScannerCapabilitiesResponse}*/ (response) => {
+          this.onCapabilitiesReceived_(response.capabilities);
+        });
   },
 
   /** @private */
@@ -477,7 +497,7 @@
 
     this.scanService_
         .startScan(
-            this.scannerIds_.get(this.selectedScannerId), settings,
+            this.getSelectedScannerToken_(), settings,
             this.scanJobObserverReceiver_.$.bindNewPipeAndPassRemote())
         .then(
             /*@type {!{success: boolean}}*/ (response) => {
@@ -578,9 +598,13 @@
             this.appState_ === AppState.GOT_SCANNERS ||
             this.appState_ === AppState.READY);
         break;
+      case (AppState.SETTING_SAVED_SETTINGS):
+        assert(this.appState_ === AppState.GETTING_CAPS);
+        break;
       case (AppState.READY):
         assert(
             this.appState_ === AppState.GETTING_CAPS ||
+            this.appState_ === AppState.SETTING_SAVED_SETTINGS ||
             this.appState_ === AppState.SCANNING ||
             this.appState_ === AppState.DONE ||
             this.appState_ === AppState.CANCELING);
@@ -620,7 +644,10 @@
     // Need to wait for elements to render after updating their disabled and
     // hidden attributes before they can be focused.
     afterNextRender(this, () => {
-      if (this.appState_ === AppState.READY) {
+      if (this.appState_ === AppState.SETTING_SAVED_SETTINGS) {
+        this.setScanSettingsFromSavedSettings_();
+        this.setAppState_(AppState.READY);
+      } else if (this.appState_ === AppState.READY) {
         this.$$('#scannerSelect').$$('#scannerSelect').focus();
       } else if (this.appState_ === AppState.SCANNING) {
         this.$$('#cancelButton').focus();
@@ -726,4 +753,136 @@
         return 'scanFailedDialogUnknownErrorText';
     }
   },
+
+  /** @private */
+  setScanSettingsFromSavedSettings_() {
+    if (!this.savedScanSettings_) {
+      return;
+    }
+
+    const scannerSettings = this.getSelectedScannerSavedSettings_();
+    if (!scannerSettings) {
+      return;
+    }
+
+    this.setSelectedSourceTypeIfAvailable_(scannerSettings.sourceName);
+    this.setSelectedFileTypeIfAvailable_(scannerSettings.fileType);
+    this.setSelectedColorModeIfAvailable_(scannerSettings.colorMode);
+    this.setSelectedPageSizeIfAvailable_(scannerSettings.pageSize);
+    this.setSelectedResolutionIfAvailable_(scannerSettings.resolutionDpi);
+  },
+
+  /**
+   * @param {!ash.scanning.mojom.Scanner} scanner
+     @return {!ScannerInfo}
+   * @private
+   */
+  createScannerInfo_(scanner) {
+    return {
+      token: scanner.id,
+      displayName: getScannerDisplayName(scanner),
+    };
+  },
+
+  /**
+   * @param {!ash.scanning.mojom.Scanner} scanner
+   * @private
+   */
+  setScannerInfo_(scanner) {
+    this.scannerInfoMap_.set(
+        tokenToString(scanner.id), this.createScannerInfo_(scanner));
+  },
+
+  /**
+   * @return {boolean}
+   * @private
+   */
+  isSelectedScannerKnown_() {
+    return this.scannerInfoMap_.has(this.selectedScannerId);
+  },
+
+  /**
+   * @return {!mojoBase.mojom.UnguessableToken}
+   * @private
+   */
+  getSelectedScannerToken_() {
+    return this.scannerInfoMap_.get(this.selectedScannerId).token;
+  },
+
+  /**
+   * @return {string}
+   * @private
+   */
+  getSelectedScannerDisplayName_() {
+    return this.scannerInfoMap_.get(this.selectedScannerId).displayName;
+  },
+
+  /**
+   * @return {!Promise<!ScannerCapabilitiesResponse>}
+   * @private
+   */
+  getSelectedScannerCapabilities_() {
+    return this.scanService_.getScannerCapabilities(
+        this.getSelectedScannerToken_());
+  },
+
+  /**
+   * @return {!ScannerSetting|undefined}
+   * @private
+   */
+  getSelectedScannerSavedSettings_() {
+    const selectedScannerDisplayName = this.getSelectedScannerDisplayName_();
+    return this.savedScanSettings_.scanners.find(
+        scanner => scanner.name === selectedScannerDisplayName);
+  },
+
+  /**
+   * @param {string} sourceName
+   * @private
+   */
+  setSelectedSourceTypeIfAvailable_(sourceName) {
+    if (this.capabilities_.sources.find(source => source.name === sourceName)) {
+      this.selectedSource = sourceName;
+    }
+  },
+
+  /**
+   * @param {!ash.scanning.mojom.FileType} fileType
+   * @private
+   */
+  setSelectedFileTypeIfAvailable_(fileType) {
+    if (Object.values(ash.scanning.mojom.FileType).includes(fileType)) {
+      this.selectedFileType = fileType.toString();
+    }
+  },
+
+  /**
+   * @param {!ash.scanning.mojom.ColorMode} colorMode
+   * @private
+   */
+  setSelectedColorModeIfAvailable_(colorMode) {
+    if (this.capabilities_.colorModes.includes(colorMode)) {
+      this.selectedColorMode = colorMode.toString();
+    }
+  },
+
+  /**
+   * @param {!ash.scanning.mojom.PageSize} pageSize
+   * @private
+   */
+  setSelectedPageSizeIfAvailable_(pageSize) {
+    if (this.selectedSourcePageSizes_.includes(pageSize)) {
+      this.selectedPageSize = pageSize.toString();
+    }
+  },
+
+  /**
+   * @param {number} resolution
+   * @private
+   */
+  setSelectedResolutionIfAvailable_(resolution) {
+    if (this.capabilities_.resolutions.includes(resolution)) {
+      this.selectedResolution = resolution.toString();
+    }
+  },
 });
diff --git a/ash/content/scanning/resources/scanning_app_types.js b/ash/content/scanning/resources/scanning_app_types.js
index c12414e3..446ff7f 100644
--- a/ash/content/scanning/resources/scanning_app_types.js
+++ b/ash/content/scanning/resources/scanning_app_types.js
@@ -10,11 +10,12 @@
   GETTING_SCANNERS: 0,
   GOT_SCANNERS: 1,
   GETTING_CAPS: 2,
-  READY: 3,
-  SCANNING: 4,
-  DONE: 5,
-  CANCELING: 6,
-  NO_SCANNERS: 7,
+  SETTING_SAVED_SETTINGS: 3,
+  READY: 4,
+  SCANNING: 5,
+  DONE: 6,
+  CANCELING: 7,
+  NO_SCANNERS: 8,
 };
 
 /**
@@ -34,3 +35,38 @@
  * @typedef {!Array<!ash.scanning.mojom.Scanner>}
  */
 export let ScannerArr;
+
+/**
+ * @typedef {{capabilities: !ash.scanning.mojom.ScannerCapabilities}}
+ */
+export let ScannerCapabilitiesResponse;
+
+/**
+ * @typedef {{
+ *   token: !mojoBase.mojom.UnguessableToken,
+ *   displayName: string,
+ * }}
+ */
+export let ScannerInfo;
+
+/**
+ * @typedef {{
+ *   name: string,
+ *   lastScanDate: !Date,
+ *   sourceName: string,
+ *   fileType: ash.scanning.mojom.FileType,
+ *   colorMode: ash.scanning.mojom.ColorMode,
+ *   pageSize: ash.scanning.mojom.PageSize,
+ *   resolutionDpi: number,
+ * }}
+ */
+export let ScannerSetting;
+
+/**
+ * @typedef {{
+ *   lastUsedScannerName: string,
+ *   scanToPath: string,
+ *   scanners: !Array<ScannerSetting>,
+ * }}
+ */
+export let ScanSettings;
diff --git a/ash/content/scanning/resources/scanning_browser_proxy.js b/ash/content/scanning/resources/scanning_browser_proxy.js
index f2324b7..4b695849 100644
--- a/ash/content/scanning/resources/scanning_browser_proxy.js
+++ b/ash/content/scanning/resources/scanning_browser_proxy.js
@@ -86,6 +86,18 @@
    * @param {number} numChanges
    */
   recordNumScanSettingChanges(numChanges) {}
+
+  /**
+   * Saves scan settings to the Prefs service.
+   * @param {string} scanSettings
+   */
+  saveScanSettings(scanSettings) {}
+
+  /**
+   * Returns the saved scan settings from the Prefs service.
+   * @return {!Promise<string>}
+   */
+  getScanSettings() {}
 }
 
 /** @implements {ScanningBrowserProxy} */
@@ -134,6 +146,16 @@
   recordNumScanSettingChanges(numChanges) {
     chrome.send('recordNumScanSettingChanges', [numChanges]);
   }
+
+  /** @override */
+  saveScanSettings(scanSettings) {
+    chrome.send('saveScanSettings', [scanSettings]);
+  }
+
+  /** @override */
+  getScanSettings() {
+    return sendWithPromise('getScanSettings');
+  }
 }
 
 // The singleton instance_ can be replaced with a test version of this wrapper
diff --git a/ash/content/scanning/scanning_ui.cc b/ash/content/scanning/scanning_ui.cc
index e9c2d75e..aad0f2f 100644
--- a/ash/content/scanning/scanning_ui.cc
+++ b/ash/content/scanning/scanning_ui.cc
@@ -128,6 +128,9 @@
   html_source->AddBoolean(
       "scanAppMediaLinkEnabled",
       base::FeatureList::IsEnabled(chromeos::features::kScanAppMediaLink));
+  html_source->AddBoolean(
+      "scanAppStickySettingsEnabled",
+      base::FeatureList::IsEnabled(chromeos::features::kScanAppStickySettings));
 }
 
 }  // namespace
diff --git a/base/test/run_all_unittests.cc b/base/test/run_all_unittests.cc
index 07befc7d..3ab55cf 100644
--- a/base/test/run_all_unittests.cc
+++ b/base/test/run_all_unittests.cc
@@ -9,33 +9,54 @@
 #include "build/build_config.h"
 
 #if defined(OS_WIN)
+#include "base/win/com_init_util.h"
+#endif  // defined(OS_WIN)
+
+namespace base {
+
+namespace {
+
+#if defined(OS_WIN)
+class ComLeakCheck : public testing::EmptyTestEventListener {
+ public:
+  void OnTestEnd(const testing::TestInfo& test) override {
+    // Verify that COM has been reset to defaults by the test.
+    EXPECT_EQ(win::GetComApartmentTypeForThread(), win::ComApartmentType::NONE);
+  }
+};
+
 class TimerCheck : public testing::EmptyTestEventListener {
  public:
   void OnTestEnd(const testing::TestInfo& test_info) override {
-    EXPECT_FALSE(base::Time::IsHighResolutionTimerInUse());
+    EXPECT_FALSE(Time::IsHighResolutionTimerInUse());
   }
 };
-#endif
+#endif  // defined(OS_WIN)
 
-class BaseUnittestSuite : public base::TestSuite {
+class BaseUnittestSuite : public TestSuite {
  public:
-  BaseUnittestSuite(int argc, char** argv) : base::TestSuite(argc, argv) {}
+  BaseUnittestSuite(int argc, char** argv) : TestSuite(argc, argv) {}
 
  protected:
   void Initialize() override {
-    base::TestSuite::Initialize();
+    TestSuite::Initialize();
 
 #if defined(OS_WIN)
     // Add TestEventListeners to enforce certain properties across tests.
     testing::TestEventListeners& listeners =
         testing::UnitTest::GetInstance()->listeners();
+    listeners.Append(new ComLeakCheck);
     listeners.Append(new TimerCheck);
-#endif
+#endif  // defined(OS_WIN)
   }
 };
 
+}  // namespace
+
+}  // namespace base
+
 int main(int argc, char** argv) {
-  BaseUnittestSuite test_suite(argc, argv);
+  base::BaseUnittestSuite test_suite(argc, argv);
   return base::LaunchUnitTests(
       argc, argv,
       base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite)));
diff --git a/base/win/com_init_util.cc b/base/win/com_init_util.cc
index 9c1773c..696ab18 100644
--- a/base/win/com_init_util.cc
+++ b/base/win/com_init_util.cc
@@ -13,11 +13,11 @@
 namespace base {
 namespace win {
 
-#if DCHECK_IS_ON()
-
 namespace {
 
+#if DCHECK_IS_ON()
 const char kComNotInitialized[] = "COM is not initialized on this thread.";
+#endif  // DCHECK_IS_ON()
 
 // Derived from combase.dll.
 struct OleTlsData {
@@ -41,6 +41,8 @@
   return reinterpret_cast<OleTlsData*>(teb->ReservedForOle);
 }
 
+}  // namespace
+
 ComApartmentType GetComApartmentTypeForThread() {
   OleTlsData* ole_tls_data = GetOleTlsData();
   if (!ole_tls_data)
@@ -57,7 +59,7 @@
   return ComApartmentType::NONE;
 }
 
-}  // namespace
+#if DCHECK_IS_ON()
 
 void AssertComInitialized(const char* message) {
   if (GetComApartmentTypeForThread() != ComApartmentType::NONE)
diff --git a/base/win/com_init_util.h b/base/win/com_init_util.h
index 906244c..ebf134fe 100644
--- a/base/win/com_init_util.h
+++ b/base/win/com_init_util.h
@@ -20,6 +20,9 @@
   MTA,
 };
 
+// Get the current apartment type.
+BASE_EXPORT ComApartmentType GetComApartmentTypeForThread();
+
 #if DCHECK_IS_ON()
 
 // DCHECKs if COM is not initialized on this thread as an STA or MTA.
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index 6ecfe85..e9a6c60 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -149,7 +149,7 @@
   enable_wmax_tokens =
       !is_official_build &&
       ((is_mac && target_cpu == "x64" && !use_system_xcode) ||
-       ((is_linux || is_chromeos_lacros) && target_cpu == "x64") ||
+       (is_linux && !is_chromeos && target_cpu == "x64") ||
        (is_win && target_cpu == "x86") || (is_win && target_cpu == "x64") ||
        (is_android && target_cpu == "arm") ||
        (is_android && target_cpu == "arm64"))
diff --git a/build/config/fuchsia/generate_runner_scripts.gni b/build/config/fuchsia/generate_runner_scripts.gni
index 7fac16f..0f6cd35 100644
--- a/build/config/fuchsia/generate_runner_scripts.gni
+++ b/build/config/fuchsia/generate_runner_scripts.gni
@@ -142,12 +142,7 @@
       "//third_party/fuchsia-sdk/sdk/tools/${test_host_cpu}/fvm",
       "//third_party/fuchsia-sdk/sdk/tools/${test_host_cpu}/merkleroot",
       "//third_party/fuchsia-sdk/sdk/tools/${test_host_cpu}/pm",
-
-      # TODO(crbug.com/1162314) Remove "symbolize" when transition to
-      # "symbolizer" is complete.
       "//third_party/fuchsia-sdk/sdk/tools/${test_host_cpu}/symbolize",
-
-      "//third_party/fuchsia-sdk/sdk/tools/${test_host_cpu}/symbolizer",
       "//third_party/fuchsia-sdk/sdk/tools/${test_host_cpu}/zbi",
     ]
 
diff --git a/build/fuchsia/common.py b/build/fuchsia/common.py
index 5eaefa2b..99ced81 100644
--- a/build/fuchsia/common.py
+++ b/build/fuchsia/common.py
@@ -124,6 +124,10 @@
       process.send_signal(signal.SIGKILL)
 
     timeout_timer = threading.Timer(timeout_secs, interrupt_process)
+
+    # Ensure that keyboard interrupts are handled properly (crbug/1198113).
+    timeout_timer.daemon = True
+
     timeout_timer.start()
 
   out, err = process.communicate()
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 6e792779..5120beb 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-4.20210427.3.1
+4.20210428.2.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 6e792779..553c134 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-4.20210427.3.1
+4.20210428.1.1
diff --git a/build/fuchsia/symbolizer.py b/build/fuchsia/symbolizer.py
index c2d34de..7db9fc9f 100644
--- a/build/fuchsia/symbolizer.py
+++ b/build/fuchsia/symbolizer.py
@@ -4,12 +4,44 @@
 
 import logging
 import os
+import re
 import subprocess
 
 from common import SDK_ROOT
 from common import GetHostArchFromPlatform
 from common import GetHostToolPathFromPlatform
 
+# Paths to the llvm-symbolizer executable in different test hosts.
+X64_LLVM_SYMBOLIZER_PATH = os.path.join(SDK_ROOT, os.pardir, os.pardir,
+                                        'llvm-build', 'Release+Asserts', 'bin',
+                                        'llvm-symbolizer')
+ARM64_XENIAL_LLVM_SYMBOLIZER_PATH = os.path.join('/', 'usr', 'lib', 'llvm-3.8',
+                                                 'bin', 'llvm-symbolizer')
+ARM64_BIONIC_LLVM_SYMBOLIZER_PATH = os.path.join('/', 'usr', 'lib', 'llvm-6.0',
+                                                 'bin', 'llvm-symbolizer')
+
+
+def _GetLLVMSymbolizerPath():
+  """Determines the path to the LLVM symbolizer executable based on test host
+  architecture and Ubuntu distro."""
+
+  if GetHostArchFromPlatform() == 'x64':
+    return X64_LLVM_SYMBOLIZER_PATH
+
+  # Get distro codename from /etc/os-release.
+  with open(os.path.join('/', 'etc', 'os-release')) as os_release_file:
+    os_release_text = os_release_file.read()
+  version_codename_re = r'^VERSION_CODENAME=(?P<codename>[\w.-]+)$'
+  match = re.search(version_codename_re, os_release_text, re.MULTILINE)
+  codename = match.group('codename') if match else None
+
+  if codename == 'xenial':
+    return ARM64_XENIAL_LLVM_SYMBOLIZER_PATH
+  elif codename == 'bionic':
+    return ARM64_BIONIC_LLVM_SYMBOLIZER_PATH
+  else:
+    raise Exception('Unknown Ubuntu release "%s"' % codename)
+
 
 def BuildIdsPaths(package_paths):
   """Generates build ids paths for symbolizer processes."""
@@ -30,13 +62,13 @@
                   unstripped binaries on the filesystem.
   Returns a Popen object for the started process."""
 
-  symbolizer = GetHostToolPathFromPlatform('symbolizer')
-  symbolizer_cmd = [
-      symbolizer, '--build-id-dir',
-      os.path.join(SDK_ROOT, '.build-id')
-  ]
+  symbolizer = GetHostToolPathFromPlatform('symbolize')
+  llvm_symbolizer_path = _GetLLVMSymbolizerPath()
+  symbolizer_cmd = [symbolizer,
+                    '-ids-rel', '-llvm-symbolizer', llvm_symbolizer_path,
+                    '-build-id-dir', os.path.join(SDK_ROOT, '.build-id')]
   for build_ids_file in build_ids_files:
-    symbolizer_cmd.extend(['--ids-txt', build_ids_file])
+    symbolizer_cmd.extend(['-ids', build_ids_file])
 
   logging.info('Running "%s".' % ' '.join(symbolizer_cmd))
   return subprocess.Popen(symbolizer_cmd, stdin=input_file, stdout=output_file,
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index cadaf26..d7ffb02 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -2346,6 +2346,7 @@
   ]
   deps = [
     ":chrome_base_module_resources",
+    "$google_play_services_package:google_firebase_firebase_iid_java",
     "$google_play_services_package:google_firebase_firebase_messaging_java",
     "$google_play_services_package:google_play_services_gcm_java",
     "//base:base_java",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java
index 35f51c5..33020c47 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java
@@ -316,9 +316,13 @@
     }
 
     void updateStatusVisibility() {
+        // This logic doesn't apply to tablets.
+        if (mIsTablet) return;
+
         boolean shouldShowLogo = mSearchEngineLogoUtils.shouldShowSearchEngineLogo(
                 mLocationBarDataProvider.isIncognito());
         setShowIconsWhenUrlFocused(shouldShowLogo);
+        if (!shouldShowLogo) return;
 
         if (mLocationBarDataProvider.isInOverviewAndShowingOmnibox()) {
             setStatusIconShown(true);
@@ -338,11 +342,7 @@
         // On tablets, the status icon should always be shown so the following logic doesn't apply.
         assert !mIsTablet : "This logic shouldn't be called on tablets";
 
-        // Note: This uses mUrlFocusPercent rather than mUrlHasFocus because when the user scrolls
-        // the NTP we want the status icon to show.
-        if (mUrlFocusPercent > 0) {
-            setStatusIconShown(true);
-        }
+        updateStatusVisibility();
 
         // Only fade the animation on the new tab page.
         if (UrlUtilities.isCanonicalizedNTPUrl(mLocationBarDataProvider.getCurrentUrl())) {
@@ -504,12 +504,12 @@
     @VisibleForTesting
     boolean maybeUpdateStatusIconForSearchEngineIcon() {
         // Show the logo unfocused if we're on the NTP.
-        if (shouldUpdateStatusIconForSearchEngineIcon()) {
+        if (shouldDisplaySearchEngineIcon()) {
             getStatusIconResourceForSearchEngineIcon(
                     mLocationBarDataProvider.isIncognito(), (statusIconRes) -> {
                         // Check again in case the conditions have changed since this callback was
                         // created.
-                        if (shouldUpdateStatusIconForSearchEngineIcon()) {
+                        if (shouldDisplaySearchEngineIcon()) {
                             mModel.set(StatusProperties.STATUS_ICON_RESOURCE, statusIconRes);
                         }
                     });
@@ -520,16 +520,20 @@
         }
     }
 
-    private boolean shouldUpdateStatusIconForSearchEngineIcon() {
+    /**
+     * Returns whether the search engine icon should be displayed in the current context. This is
+     * independent from alpha/visibility.
+     */
+    @VisibleForTesting
+    boolean shouldDisplaySearchEngineIcon() {
         boolean showIconWhenFocused = mUrlHasFocus && mShowStatusIconWhenUrlFocused;
-        boolean showIconWhenScrollingOnNTP =
+        boolean showIconOnNTP =
                 UrlUtilities.isCanonicalizedNTPUrl(mLocationBarDataProvider.getCurrentUrl())
-                && mUrlFocusPercent > 0 && !mUrlHasFocus && !mLocationBarDataProvider.isLoading()
-                && mShowStatusIconWhenUrlFocused;
+                && !mLocationBarDataProvider.isLoading() && !mIsTablet;
 
         return mSearchEngineLogoUtils.shouldShowSearchEngineLogo(
                        mLocationBarDataProvider.isIncognito())
-                && (showIconWhenFocused || showIconWhenScrollingOnNTP);
+                && (showIconWhenFocused || showIconOnNTP);
     }
 
     /**
@@ -606,6 +610,7 @@
         mModel.set(StatusProperties.INCOGNITO_BADGE_VISIBLE, incognitoBadgeVisible);
         mModel.set(StatusProperties.STATUS_ICON_RESOURCE, null);
         setStatusIconAlpha(1f);
+        setStatusIconShown(false);
     }
 
     // PermissionDialogController.Observer interface
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/status/StatusMediatorUnitTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/status/StatusMediatorUnitTest.java
index 8da70de..baa80cce 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/status/StatusMediatorUnitTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/status/StatusMediatorUnitTest.java
@@ -115,21 +115,23 @@
                 .getSearchEngineLogo(
                         eq(mResources), /* inNightMode= */ eq(false), any(), any(), any());
 
+        mBitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
+        setupStatusMediator(/* isTablet= */ false);
+    }
+
+    private void setupStatusMediator(boolean isTablet) {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mTemplateUrlServiceSupplier = new OneshotSupplierImpl<>();
             mMediator = new StatusMediator(mModel, mResources, mContext,
-                    mUrlBarEditingTextStateProvider,
-                    /* isTablet */ false, mLocationBarDataProvider, mPermissionDialogController,
-                    mSearchEngineLogoUtils, mTemplateUrlServiceSupplier,
-                    () -> mProfile, mPageInfoIPHController, null);
+                    mUrlBarEditingTextStateProvider, isTablet, mLocationBarDataProvider,
+                    mPermissionDialogController, mSearchEngineLogoUtils,
+                    mTemplateUrlServiceSupplier, () -> mProfile, mPageInfoIPHController, null);
             mTemplateUrlServiceSupplier.set(mTemplateUrlService);
         });
-        mBitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
     }
 
     @Test
     @SmallTest
-
     @UiThreadTest
     public void searchEngineLogo_isGoogleLogo() {
         setupSearchEngineLogoForTesting(
@@ -143,7 +145,6 @@
 
     @Test
     @SmallTest
-
     @UiThreadTest
     public void searchEngineLogo_isGoogleLogo_hideAfterUnfocusFinished() {
         doReturn(UrlConstants.NTP_URL).when(mLocationBarDataProvider).getCurrentUrl();
@@ -173,15 +174,30 @@
     @Test
     @SmallTest
     @UiThreadTest
-    public void searchEngineLogo_isGoogleLogoOnNtpScroll() {
+    public void searchEngineLogo_isGoogleLogoOnNtp() {
         doReturn(UrlConstants.NTP_URL).when(mLocationBarDataProvider).getCurrentUrl();
         setupSearchEngineLogoForTesting(
                 /* showLogo= */ true, /* isGoogle= */ false, /* loupeEverywhere= */ false);
 
         mMediator.setUrlHasFocus(false);
         mMediator.setShowIconsWhenUrlFocused(true);
-        mMediator.setUrlFocusChangePercent(1f);
         Assert.assertTrue(mModel.get(StatusProperties.SHOW_STATUS_ICON));
+        Assert.assertTrue(mMediator.shouldDisplaySearchEngineIcon());
+    }
+
+    @Test
+    @SmallTest
+    @UiThreadTest
+    public void searchEngineLogo_isGoogleLogoOnNtpTablet() {
+        setupStatusMediator(/* isTablet= */ true);
+        doReturn(UrlConstants.NTP_URL).when(mLocationBarDataProvider).getCurrentUrl();
+        setupSearchEngineLogoForTesting(
+                /* showLogo= */ true, /* isGoogle= */ false, /* loupeEverywhere= */ false);
+
+        mMediator.setUrlHasFocus(false);
+        mMediator.setShowIconsWhenUrlFocused(true);
+        Assert.assertTrue(mModel.get(StatusProperties.SHOW_STATUS_ICON));
+        Assert.assertFalse(mMediator.shouldDisplaySearchEngineIcon());
     }
 
     @Test
@@ -237,7 +253,6 @@
 
     @Test
     @SmallTest
-
     @UiThreadTest
     public void searchEngineLogo_onTextChanged_noGlobeReplacementWhenUrlBarTextDoesNotMatch() {
         mMediator.setUrlHasFocus(true);
@@ -271,7 +286,16 @@
 
     @Test
     @SmallTest
+    @UiThreadTest
+    public void searchEngineLogo_incognitoStateChanged() {
+        mMediator.onIncognitoStateChanged();
 
+        Assert.assertEquals(false, mModel.get(StatusProperties.SHOW_STATUS_ICON));
+        Assert.assertEquals(1f, mModel.get(StatusProperties.STATUS_ICON_ALPHA), 0f);
+    }
+
+    @Test
+    @SmallTest
     @UiThreadTest
     public void searchEngineLogo_incognitoNoIcon() {
         doReturn(true).when(mLocationBarDataProvider).isIncognito();
@@ -287,7 +311,6 @@
 
     @Test
     @SmallTest
-
     @UiThreadTest
     public void searchEngineLogo_maybeUpdateStatusIconForSearchEngineIconChanges() {
         mMediator.setUrlHasFocus(true);
@@ -317,7 +340,6 @@
 
     @Test
     @SmallTest
-
     @UiThreadTest
     public void resolveUrlBarTextWithAutocomplete_urlBarTextEmpty() {
         Assert.assertEquals("Empty urlBarText should resolve to empty urlBarTextWithAutocomplete",
@@ -326,7 +348,6 @@
 
     @Test
     @SmallTest
-
     @UiThreadTest
     public void resolveUrlBarTextWithAutocomplete_urlBarTextMismatchesAutocompleteText() {
         doReturn("https://foo.com").when(mUrlBarEditingTextStateProvider).getTextWithAutocomplete();
@@ -339,7 +360,6 @@
 
     @Test
     @SmallTest
-
     @UiThreadTest
     public void testIncognitoStateChange_goingToIncognito() {
         mMediator.setShowIconsWhenUrlFocused(true);
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index e68e39d..77fe768 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -2544,6 +2544,9 @@
   <message name="IDS_SETTINGS_INTERNET_ESIM_NO_CONNECTION_ERROR_TOAST" desc="Message in toast displayed when user tries to perform an eSIM operation when not connected to a non-cellular network.">
     Establish an internet connection first
   </message>
+  <message name="IDS_SETTINGS_INTERNET_ESIM_MOBILE_DATA_NOT_ENABLED_ERROR_TOAST" desc="Message in toast displayed when user tries to set up an eSIM profile when mobile data is not enabled.">
+    Enable mobile data to install an eSIM profile
+  </message>
 
   <!-- Users Page (OS Settings) -->
   <message name="IDS_SETTINGS_USERS_MODIFIED_BY_OWNER_LABEL" desc="Label saying settings may only be modified by the device owner.">
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_ESIM_MOBILE_DATA_NOT_ENABLED_ERROR_TOAST.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_ESIM_MOBILE_DATA_NOT_ENABLED_ERROR_TOAST.png.sha1
new file mode 100644
index 0000000..e4daba3
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_ESIM_MOBILE_DATA_NOT_ENABLED_ERROR_TOAST.png.sha1
@@ -0,0 +1 @@
+bebb523ffce0b24f2e4d58e3c1b8063bf7f6c4ae
\ No newline at end of file
diff --git a/chrome/browser/after_startup_task_utils.cc b/chrome/browser/after_startup_task_utils.cc
index b094d97..ca2cc1df 100644
--- a/chrome/browser/after_startup_task_utils.cc
+++ b/chrome/browser/after_startup_task_utils.cc
@@ -4,34 +4,21 @@
 
 #include "chrome/browser/after_startup_task_utils.h"
 
-#include <memory>
-#include <utility>
-
 #include "base/containers/circular_deque.h"
 #include "base/lazy_instance.h"
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/process/process.h"
-#include "base/rand_util.h"
-#include "base/sequence_checker.h"
 #include "base/sequenced_task_runner.h"
 #include "base/synchronization/atomic_flag.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
+#include "components/performance_manager/performance_manager_impl.h"
+#include "components/performance_manager/public/graph/graph.h"
+#include "components/performance_manager/public/graph/page_node.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
-#include "content/public/browser/navigation_handle.h"
-#include "content/public/browser/render_frame_host.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/browser/web_contents_observer.h"
-#include "content/public/common/page_visibility_state.h"
-
-#if !defined(OS_ANDROID)
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/browser_list.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
-#endif
 
 // TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
 // of lacros-chrome is complete.
@@ -40,8 +27,6 @@
 #endif
 
 using content::BrowserThread;
-using content::WebContents;
-using content::WebContentsObserver;
 
 namespace {
 
@@ -113,6 +98,11 @@
 
 void SetBrowserStartupIsComplete() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  if (IsBrowserStartupComplete())
+    return;
+
+  g_startup_complete_flag.Get().Set();
 #if defined(OS_MAC) || defined(OS_WIN) || defined(OS_LINUX) || \
     defined(OS_CHROMEOS)
   // Process::Current().CreationTime() is not available on all platforms.
@@ -126,7 +116,6 @@
         // defined(OS_CHROMEOS)
   UMA_HISTOGRAM_COUNTS_10000("Startup.AfterStartupTaskCount",
                              g_after_startup_tasks.Get().size());
-  g_startup_complete_flag.Get().Set();
   for (AfterStartupTask* queued_task : g_after_startup_tasks.Get())
     ScheduleTask(base::WrapUnique(queued_task));
   g_after_startup_tasks.Get().clear();
@@ -144,95 +133,98 @@
 }
 
 // Observes the first visible page load and sets the startup complete
-// flag accordingly.
-class StartupObserver : public WebContentsObserver {
+// flag accordingly. Ownership is passed to the Performance Manager
+// after creation.
+class StartupObserver
+    : public performance_manager::GraphOwned,
+      public performance_manager::PageNode::ObserverDefaultImpl {
  public:
-  StartupObserver() {}
-  ~StartupObserver() override {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    DCHECK(IsBrowserStartupComplete());
-  }
+  ~StartupObserver() override = default;
 
-  void Start();
+  static void Start();
 
  private:
+  StartupObserver() = default;
+
   void OnStartupComplete() {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    SetBrowserStartupIsComplete();
-    delete this;
-  }
-
-  void OnFailsafeTimeout() { OnStartupComplete(); }
-
-  // WebContentsObserver overrides
-  void DidFailLoad(content::RenderFrameHost* render_frame_host,
-                   const GURL& validated_url,
-                   int error_code) override {
-    if (!render_frame_host->GetParent())
-      OnStartupComplete();
-  }
-
-  // Starting the browser with a file download url will not result in
-  // DidFirstVisuallyNonEmptyPaint firing, so watch for this case too.
-  // crbug.com/1006954
-  void DidFinishNavigation(
-      content::NavigationHandle* navigation_handle) override {
-    if (navigation_handle->IsInMainFrame() && navigation_handle->IsDownload())
-      OnStartupComplete();
-  }
-
-  void DidFirstVisuallyNonEmptyPaint() override { OnStartupComplete(); }
-
-  void WebContentsDestroyed() override { OnStartupComplete(); }
-
-  SEQUENCE_CHECKER(sequence_checker_);
-
-  base::WeakPtrFactory<StartupObserver> weak_factory_{this};
-
-  DISALLOW_COPY_AND_ASSIGN(StartupObserver);
-};
-
-void StartupObserver::Start() {
-  // Signal completion quickly when there is no first page to load.
-  const int kShortDelaySecs = 3;
-  base::TimeDelta delay = base::TimeDelta::FromSeconds(kShortDelaySecs);
-
-#if !defined(OS_ANDROID)
-  WebContents* contents = nullptr;
-  for (auto* browser : *BrowserList::GetInstance()) {
-    contents = browser->tab_strip_model()->GetActiveWebContents();
-    if (contents && contents->GetMainFrame() &&
-        contents->GetMainFrame()->GetVisibilityState() ==
-            content::PageVisibilityState::kVisible) {
-      break;
+    // This should only be called once.
+    if (!startup_complete_) {
+      startup_complete_ = true;
+      content::GetUIThreadTaskRunner({})->PostTask(
+          FROM_HERE, base::BindOnce(&SetBrowserStartupIsComplete));
+      // This will result in delete getting called.
+      TakeFromGraph();
     }
   }
 
-  if (contents) {
-    // Give the page time to finish loading.
-    const int kLongerDelayMins = 3;
-    Observe(contents);
-    delay = base::TimeDelta::FromMinutes(kLongerDelayMins);
+  // GraphOwned overrides
+  void OnPassedToGraph(performance_manager::Graph* graph) override {
+    graph->AddPageNodeObserver(this);
   }
-#else
-  // Startup completion is signaled via AfterStartupTaskUtils.java,
-  // this is just a failsafe timeout.
-  const int kLongerDelayMins = 3;
-  delay = base::TimeDelta::FromMinutes(kLongerDelayMins);
-#endif  // !defined(OS_ANDROID)
 
-  content::GetUIThreadTaskRunner({})->PostDelayedTask(
-      FROM_HERE,
-      base::BindOnce(&StartupObserver::OnFailsafeTimeout,
-                     weak_factory_.GetWeakPtr()),
-      delay);
+  void OnTakenFromGraph(performance_manager::Graph* graph) override {
+    graph->RemovePageNodeObserver(this);
+  }
+
+  // PageNodeObserver overrides
+  void OnLoadingStateChanged(
+      const performance_manager::PageNode* page_node) override {
+    // Only interested in visible PageNodes
+    if (page_node->IsVisible()) {
+      if (page_node->GetLoadingState() ==
+              performance_manager::PageNode::LoadingState::kLoadedIdle ||
+          page_node->GetLoadingState() ==
+              performance_manager::PageNode::LoadingState::kLoadingTimedOut)
+        OnStartupComplete();
+    }
+  }
+
+  void PassToGraph() {
+    // Pass to the performance manager so we can get notified when
+    // loading completes.  Ownership of this object is passed to the
+    // performance manager.
+    DCHECK(performance_manager::PerformanceManagerImpl::IsAvailable());
+    performance_manager::PerformanceManagerImpl::PassToGraph(
+        FROM_HERE, base::WrapUnique(this));
+  }
+
+  void TakeFromGraph() {
+    // Remove this object from the performance manager.  This will
+    // cause the object to be deleted.
+    DCHECK(performance_manager::PerformanceManagerImpl::IsAvailable());
+    performance_manager::PerformanceManager::CallOnGraph(
+        FROM_HERE, base::BindOnce(
+                       [](performance_manager::GraphOwned* observer,
+                          performance_manager::Graph* graph) {
+                         graph->TakeFromGraph(observer);
+                       },
+                       base::Unretained(this)));
+  }
+
+  bool startup_complete_ = false;
+  DISALLOW_COPY_AND_ASSIGN(StartupObserver);
+};
+
+// static
+void StartupObserver::Start() {
+  // Create the StartupObserver and pass it to the Performance Manager which
+  // will own it going forward.
+  (new StartupObserver)->PassToGraph();
 }
 
 }  // namespace
 
 void AfterStartupTaskUtils::StartMonitoringStartup() {
-  // The observer is self-deleting.
-  (new StartupObserver)->Start();
+  // For Android, startup completion is signaled via
+  // AfterStartupTaskUtils.java. We do not use the StartupObserver.
+#if !defined(OS_ANDROID)
+  StartupObserver::Start();
+#endif  // !defined(OS_ANDROID)
+
+  // Add failsafe timeout
+  content::GetUIThreadTaskRunner({})->PostDelayedTask(
+      FROM_HERE, base::BindOnce(&SetBrowserStartupIsComplete),
+      base::TimeDelta::FromMinutes(3));
 }
 
 void AfterStartupTaskUtils::PostTask(
diff --git a/chrome/browser/ash/login/easy_unlock/easy_unlock_service_regular.cc b/chrome/browser/ash/login/easy_unlock/easy_unlock_service_regular.cc
index 26abf502..f58e7d4c 100644
--- a/chrome/browser/ash/login/easy_unlock/easy_unlock_service_regular.cc
+++ b/chrome/browser/ash/login/easy_unlock/easy_unlock_service_regular.cc
@@ -172,11 +172,23 @@
   if (remote_devices.size() != 1u) {
     PA_LOG(ERROR) << "There should only be 1 Smart Lock host, but there are: "
                   << remote_devices.size();
+    SetProximityAuthDevices(GetAccountId(), multidevice::RemoteDeviceRefList(),
+                            base::nullopt);
     NOTREACHED();
+    return;
   }
 
-  SetProximityAuthDevices(GetAccountId(), remote_devices,
-                          device_sync_client_->GetLocalDeviceMetadata());
+  base::Optional<multidevice::RemoteDeviceRef> local_device =
+      device_sync_client_->GetLocalDeviceMetadata();
+  if (!local_device) {
+    PA_LOG(ERROR) << "EasyUnlockServiceRegular::" << __func__
+                  << ": Local device unexpectedly null.";
+    SetProximityAuthDevices(GetAccountId(), multidevice::RemoteDeviceRefList(),
+                            base::nullopt);
+    return;
+  }
+
+  SetProximityAuthDevices(GetAccountId(), remote_devices, local_device);
 
   // We need to store a copy of `local_and_remote_devices` in the TPM, so it can
   // be retrieved on the sign-in screen when a user session has not been started
@@ -187,8 +199,7 @@
   // be persisted in a dictionary.
   multidevice::RemoteDeviceRefList local_and_remote_devices;
   local_and_remote_devices.push_back(remote_devices[0]);
-  local_and_remote_devices.push_back(
-      *device_sync_client_->GetLocalDeviceMetadata());
+  local_and_remote_devices.push_back(*local_device);
 
   std::unique_ptr<base::ListValue> device_list(new base::ListValue());
   for (const auto& device : local_and_remote_devices) {
diff --git a/chrome/browser/chromeos/exo/chrome_data_exchange_delegate.cc b/chrome/browser/chromeos/exo/chrome_data_exchange_delegate.cc
index 8395925..43c0efd1 100644
--- a/chrome/browser/chromeos/exo/chrome_data_exchange_delegate.cc
+++ b/chrome/browser/chromeos/exo/chrome_data_exchange_delegate.cc
@@ -396,24 +396,20 @@
   return pickle;
 }
 
-std::vector<ui::FileInfo>
-ChromeDataExchangeDelegate::ParseClipboardFilenamesPickle(
-    ui::EndpointType target,
-    const ui::Clipboard& data) const {
+std::vector<ui::FileInfo> ChromeDataExchangeDelegate::ParseFileSystemSources(
+    const ui::DataTransferEndpoint* source,
+    const base::Pickle& pickle) const {
   std::vector<ui::FileInfo> file_info;
   // We only promote 'fs/sources' custom data pickle to be filenames which can
   // be shared and read by clients if it came from the trusted FilesApp.
-  const ui::DataTransferEndpoint* data_src =
-      data.GetSource(ui::ClipboardBuffer::kCopyPaste);
-  if (!data_src || !data_src->IsSameOriginWith(ui::DataTransferEndpoint(
-                       file_manager::util::GetFilesAppOrigin()))) {
+  if (!source || !source->IsSameOriginWith(ui::DataTransferEndpoint(
+                     file_manager::util::GetFilesAppOrigin()))) {
     return file_info;
   }
 
-  const ui::DataTransferEndpoint data_dst(target);
   std::u16string file_system_url_list;
-  data.ReadCustomData(ui::ClipboardBuffer::kCopyPaste, kFilesAppMimeSources,
-                      &data_dst, &file_system_url_list);
+  ui::ReadCustomDataForType(pickle.data(), pickle.size(), kFilesAppMimeSources,
+                            &file_system_url_list);
   if (file_system_url_list.empty())
     return file_info;
 
diff --git a/chrome/browser/chromeos/exo/chrome_data_exchange_delegate.h b/chrome/browser/chromeos/exo/chrome_data_exchange_delegate.h
index 68557a8..2f994845 100644
--- a/chrome/browser/chromeos/exo/chrome_data_exchange_delegate.h
+++ b/chrome/browser/chromeos/exo/chrome_data_exchange_delegate.h
@@ -34,9 +34,9 @@
   base::Pickle CreateClipboardFilenamesPickle(
       ui::EndpointType source,
       const std::vector<uint8_t>& data) const override;
-  std::vector<ui::FileInfo> ParseClipboardFilenamesPickle(
-      ui::EndpointType target,
-      const ui::Clipboard& data) const override;
+  std::vector<ui::FileInfo> ParseFileSystemSources(
+      const ui::DataTransferEndpoint* source,
+      const base::Pickle& pickle) const override;
 };
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/exo/chrome_data_exchange_delegate_unittest.cc b/chrome/browser/chromeos/exo/chrome_data_exchange_delegate_unittest.cc
index b8ab974c..23316554 100644
--- a/chrome/browser/chromeos/exo/chrome_data_exchange_delegate_unittest.cc
+++ b/chrome/browser/chromeos/exo/chrome_data_exchange_delegate_unittest.cc
@@ -436,7 +436,7 @@
   EXPECT_EQ(true, data_exchange_delegate.HasUrlsInPickle(valid));
 }
 
-TEST_F(ChromeDataExchangeDelegateTest, ClipboardFilenamesPickle) {
+TEST_F(ChromeDataExchangeDelegateTest, ParseFileSystemSources) {
   ChromeDataExchangeDelegate data_exchange_delegate;
   base::FilePath shared_path = myfiles_dir_.Append("shared");
   auto* guest_os_share_path =
@@ -459,33 +459,19 @@
       "Downloads-test%2540example.com-hash/shared/file2",
       base::UTF16ToUTF8(m[u"fs/sources"]));
 
-  ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
-  {
-    auto files_app = std::make_unique<ui::DataTransferEndpoint>(
-        file_manager::util::GetFilesAppOrigin());
-    ui::ScopedClipboardWriter writer(ui::ClipboardBuffer::kCopyPaste,
-                                     std::move(files_app));
-    writer.WritePickledData(pickle,
-                            ui::ClipboardFormatType::GetWebCustomDataType());
-  }
-
+  ui::DataTransferEndpoint files_app(url::Origin::Create(
+      GURL("chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj")));
   std::vector<ui::FileInfo> file_info =
-      data_exchange_delegate.ParseClipboardFilenamesPickle(
-          ui::EndpointType::kDefault, *clipboard);
+      data_exchange_delegate.ParseFileSystemSources(&files_app, pickle);
   EXPECT_EQ(2, file_info.size());
   EXPECT_EQ(shared_path.Append("file1"), file_info[0].path);
   EXPECT_EQ(shared_path.Append("file2"), file_info[1].path);
   EXPECT_EQ(base::FilePath(), file_info[0].display_name);
   EXPECT_EQ(base::FilePath(), file_info[1].display_name);
 
-  // Should return empty if data_src is not FilesApp.
-  {
-    ui::ScopedClipboardWriter writer(ui::ClipboardBuffer::kCopyPaste);
-    writer.WritePickledData(pickle,
-                            ui::ClipboardFormatType::GetWebCustomDataType());
-  }
-  file_info = data_exchange_delegate.ParseClipboardFilenamesPickle(
-      ui::EndpointType::kDefault, *clipboard);
+  // Should return empty if source is not FilesApp.
+  ui::DataTransferEndpoint crostini(ui::EndpointType::kCrostini);
+  file_info = data_exchange_delegate.ParseFileSystemSources(&crostini, pickle);
   EXPECT_TRUE(file_info.empty());
 }
 
diff --git a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinatorTest.java b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinatorTest.java
index 6471715..90f2afd 100644
--- a/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinatorTest.java
+++ b/chrome/browser/commerce/merchant_viewer/android/javatests/src/org/chromium/chrome/browser/merchant_viewer/MerchantTrustSignalsCoordinatorTest.java
@@ -16,6 +16,7 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.os.Build;
 import android.view.View;
 
 import androidx.test.filters.SmallTest;
@@ -37,6 +38,7 @@
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.UiThreadTest;
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.DisableIf;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.merchant_viewer.MerchantTrustMetrics.MessageClearReason;
@@ -59,11 +61,16 @@
 
 /**
  * Tests for {@link MerchantTrustSignalsCoordinator}.
+ *
+ * NOTE: This test is temporarily skipped for SDK version < 23 (M) since the test attempts to mock
+ * {@link WindowAndroid} which has a dependency on android.View.Display.Mode which is not supported
+ * on versions prior to Android M.
  */
 @RunWith(BaseJUnit4ClassRunner.class)
 @EnableFeatures({ChromeFeatureList.COMMERCE_MERCHANT_VIEWER + "<Study"})
 @CommandLineFlags.
 Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, "force-fieldtrials=Study/Group"})
+@DisableIf.Build(sdk_is_less_than = Build.VERSION_CODES.M)
 public class MerchantTrustSignalsCoordinatorTest {
     @Rule
     public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule();
@@ -254,7 +261,6 @@
         setMockTrustSignalsData(null);
         setMockTrustSignalsEventData("fake_host", null);
 
-        // doReturn(mDummyMerchantTrustSignalsEvent)
         coordinator.maybeDisplayMessage(
                 new MerchantTrustMessageContext(mMockGurl, mMockWebContents));
 
diff --git a/chrome/browser/download/chrome_download_manager_delegate.cc b/chrome/browser/download/chrome_download_manager_delegate.cc
index a5b6e9ab4..28602cb 100644
--- a/chrome/browser/download/chrome_download_manager_delegate.cc
+++ b/chrome/browser/download/chrome_download_manager_delegate.cc
@@ -366,8 +366,8 @@
     router->OnDangerousDownloadEvent(
         download->GetURL(), download_path,
         base::HexEncode(raw_digest_sha256.data(), raw_digest_sha256.size()),
-        danger_type, download->GetMimeType(), download->GetTotalBytes(),
-        safe_browsing::EventResult::BLOCKED);
+        danger_type, download->GetMimeType(), /*scan_id*/ "",
+        download->GetTotalBytes(), safe_browsing::EventResult::BLOCKED);
   }
 #endif
 }
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate_browsertest.cc b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate_browsertest.cc
index f4febea..8766b50 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate_browsertest.cc
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate_browsertest.cc
@@ -41,6 +41,9 @@
 
 constexpr char kUserName[] = "test@chromium.org";
 
+constexpr char kScanId1[] = "scan id 1";
+constexpr char kScanId2[] = "scan id 2";
+
 std::u16string text() {
   return base::UTF8ToUTF16(std::string(100, 'a'));
 }
@@ -381,14 +384,16 @@
       /*size*/ std::string("bad file content").size(),
       /*result*/
       safe_browsing::EventResultToString(safe_browsing::EventResult::BLOCKED),
-      /*username*/ kUserName);
+      /*username*/ kUserName, /*scan_id*/ kScanId2);
 
   ContentAnalysisResponse ok_response;
+  ok_response.set_request_token(kScanId1);
   auto* ok_result = ok_response.add_results();
   ok_result->set_status(ContentAnalysisResponse::Result::SUCCESS);
   ok_result->set_tag("malware");
 
   ContentAnalysisResponse bad_response;
+  bad_response.set_request_token(kScanId2);
   auto* bad_result = bad_response.add_results();
   bad_result->set_status(ContentAnalysisResponse::Result::SUCCESS);
   bad_result->set_tag("malware");
@@ -445,6 +450,7 @@
   // Prepare a complex DLP response to test that the verdict is reported
   // correctly in the sensitive data event.
   ContentAnalysisResponse response;
+  response.set_request_token(kScanId1);
   auto* result = response.add_results();
   result->set_status(ContentAnalysisResponse::Result::SUCCESS);
   result->set_tag("dlp");
@@ -476,7 +482,8 @@
       /*size*/ 400,
       /*result*/
       safe_browsing::EventResultToString(safe_browsing::EventResult::BLOCKED),
-      /*username*/ kUserName);
+      /*username*/ kUserName,
+      /*scan_id*/ kScanId1);
 
   bool called = false;
   base::RunLoop run_loop;
@@ -896,6 +903,7 @@
   // The file should be reported as malware and sensitive content.
   safe_browsing::EventReportValidator validator(client());
   ContentAnalysisResponse response;
+  response.set_request_token(kScanId1);
 
   auto* malware_result = response.add_results();
   malware_result->set_status(ContentAnalysisResponse::Result::SUCCESS);
@@ -932,7 +940,8 @@
       safe_browsing::EventResultToString(
           expected_result() ? safe_browsing::EventResult::ALLOWED
                             : safe_browsing::EventResult::BLOCKED),
-      /*username*/ kUserName);
+      /*username*/ kUserName,
+      /*scan_id*/ kScanId1);
 
   bool called = false;
   base::RunLoop run_loop;
diff --git a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.cc b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.cc
index aff4f66..e55ba13 100644
--- a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.cc
+++ b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.cc
@@ -164,6 +164,7 @@
     "malwareCategory";
 const char SafeBrowsingPrivateEventRouter::kKeyEvidenceLockerFilePath[] =
     "evidenceLockerFilepath";
+const char SafeBrowsingPrivateEventRouter::kKeyScanId[] = "scanId";
 
 // All new event names should be added to the kAllEvents array below!
 const char SafeBrowsingPrivateEventRouter::kKeyPasswordReuseEvent[] =
@@ -284,6 +285,7 @@
     const std::string& file_name,
     const std::string& download_digest_sha256,
     const std::string& mime_type,
+    const std::string& scan_id,
     const download::DownloadDangerType danger_type,
     const int64_t content_size) {
   api::safe_browsing_private::DangerousDownloadInfo params;
@@ -326,6 +328,10 @@
       safe_browsing::EventResultToString(safe_browsing::EventResult::BYPASSED));
   event.SetBoolKey(kKeyClickedThrough, true);
   event.SetStringKey(kKeyThreatType, DangerTypeToThreatType(danger_type));
+  // The scan ID can be empty when the reported dangerous download is from a
+  // Safe Browsing verdict.
+  if (!scan_id.empty())
+    event.SetStringKey(kKeyScanId, scan_id);
 
   ReportRealtimeEvent(kKeyDangerousDownloadEvent, std::move(settings.value()),
                       std::move(event));
@@ -431,6 +437,7 @@
     const std::string& download_digest_sha256,
     const std::string& mime_type,
     const std::string& trigger,
+    const std::string& scan_id,
     safe_browsing::DeepScanAccessPoint /* access_point */,
     const enterprise_connectors::ContentAnalysisResponse::Result& result,
     const int64_t content_size,
@@ -441,10 +448,10 @@
         url, file_name, download_digest_sha256,
         MalwareRuleToThreatType(result.triggered_rules(0).rule_name()),
         mime_type, trigger, content_size, event_result, result.malware_family(),
-        result.malware_category(), result.evidence_locker_filepath());
+        result.malware_category(), result.evidence_locker_filepath(), scan_id);
   } else if (result.tag() == "dlp") {
     OnSensitiveDataEvent(url, file_name, download_digest_sha256, mime_type,
-                         trigger, result, content_size, event_result);
+                         trigger, scan_id, result, content_size, event_result);
   }
 }
 
@@ -459,7 +466,8 @@
     safe_browsing::EventResult event_result,
     const std::string& malware_family,
     const std::string& malware_category,
-    const std::string& evidence_locker_filepath) {
+    const std::string& evidence_locker_filepath,
+    const std::string& scan_id) {
   auto settings = GetReportingSettings();
   if (!settings.has_value() ||
       settings->enabled_event_names.count(kKeyDangerousDownloadEvent) == 0) {
@@ -489,6 +497,10 @@
   if (!evidence_locker_filepath.empty()) {
     event.SetStringKey(kKeyEvidenceLockerFilePath, evidence_locker_filepath);
   }
+  // The scan ID can be empty when the reported dangerous download is from a
+  // Safe Browsing verdict.
+  if (!scan_id.empty())
+    event.SetStringKey(kKeyScanId, scan_id);
 
   ReportRealtimeEvent(kKeyDangerousDownloadEvent, std::move(settings.value()),
                       std::move(event));
@@ -500,6 +512,7 @@
     const std::string& download_digest_sha256,
     const std::string& mime_type,
     const std::string& trigger,
+    const std::string& scan_id,
     const enterprise_connectors::ContentAnalysisResponse::Result& result,
     const int64_t content_size,
     safe_browsing::EventResult event_result) {
@@ -528,6 +541,7 @@
     event.SetStringKey(kKeyEvidenceLockerFilePath,
                        result.evidence_locker_filepath());
   }
+  event.SetStringKey(kKeyScanId, scan_id);
 
   AddAnalysisConnectorVerdictToEvent(result, &event);
 
@@ -541,6 +555,7 @@
     const std::string& download_digest_sha256,
     const std::string& mime_type,
     const std::string& trigger,
+    const std::string& scan_id,
     safe_browsing::DeepScanAccessPoint access_point,
     const enterprise_connectors::ContentAnalysisResponse::Result& result,
     const int64_t content_size) {
@@ -569,6 +584,7 @@
     event.SetStringKey(kKeyEvidenceLockerFilePath,
                        result.evidence_locker_filepath());
   }
+  event.SetStringKey(kKeyScanId, scan_id);
 
   AddAnalysisConnectorVerdictToEvent(result, &event);
 
@@ -619,11 +635,12 @@
     const std::string& download_digest_sha256,
     const download::DownloadDangerType danger_type,
     const std::string& mime_type,
+    const std::string& scan_id,
     const int64_t content_size,
     safe_browsing::EventResult event_result) {
   OnDangerousDownloadEvent(url, file_name, download_digest_sha256,
                            DangerTypeToThreatType(danger_type), mime_type,
-                           content_size, event_result);
+                           scan_id, content_size, event_result);
 }
 
 void SafeBrowsingPrivateEventRouter::OnDangerousDownloadEvent(
@@ -632,6 +649,7 @@
     const std::string& download_digest_sha256,
     const std::string& threat_type,
     const std::string& mime_type,
+    const std::string& scan_id,
     const int64_t content_size,
     safe_browsing::EventResult event_result) {
   auto settings = GetReportingSettings();
@@ -656,6 +674,11 @@
   event.SetStringKey(kKeyEventResult,
                      safe_browsing::EventResultToString(event_result));
 
+  // The scan ID can be empty when the reported dangerous download is from a
+  // Safe Browsing verdict.
+  if (!scan_id.empty())
+    event.SetStringKey(kKeyScanId, scan_id);
+
   ReportRealtimeEvent(kKeyDangerousDownloadEvent, std::move(settings.value()),
                       std::move(event));
 }
@@ -666,10 +689,11 @@
     const std::string& download_digest_sha256,
     const download::DownloadDangerType danger_type,
     const std::string& mime_type,
+    const std::string& scan_id,
     const int64_t content_size) {
   OnDangerousDownloadWarningBypassed(url, file_name, download_digest_sha256,
                                      DangerTypeToThreatType(danger_type),
-                                     mime_type, content_size);
+                                     mime_type, scan_id, content_size);
 }
 
 void SafeBrowsingPrivateEventRouter::OnDangerousDownloadWarningBypassed(
@@ -678,6 +702,7 @@
     const std::string& download_digest_sha256,
     const std::string& threat_type,
     const std::string& mime_type,
+    const std::string& scan_id,
     const int64_t content_size) {
   auto settings = GetReportingSettings();
   if (!settings.has_value() ||
@@ -701,6 +726,10 @@
   event.SetStringKey(
       kKeyEventResult,
       safe_browsing::EventResultToString(safe_browsing::EventResult::BYPASSED));
+  // The scan ID can be empty when the reported dangerous download is from a
+  // Safe Browsing verdict.
+  if (!scan_id.empty())
+    event.SetStringKey(kKeyScanId, scan_id);
 
   ReportRealtimeEvent(kKeyDangerousDownloadEvent, std::move(settings.value()),
                       std::move(event));
diff --git a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h
index 86aae29d..ee7fdb5 100644
--- a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h
+++ b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h
@@ -85,6 +85,7 @@
   static const char kKeyMalwareFamily[];
   static const char kKeyMalwareCategory[];
   static const char kKeyEvidenceLockerFilePath[];
+  static const char kKeyScanId[];
 
   static const char kKeyPasswordReuseEvent[];
   static const char kKeyPasswordChangedEvent[];
@@ -120,6 +121,7 @@
                                  const std::string& file_name,
                                  const std::string& download_digest_sha256,
                                  const std::string& mime_type,
+                                 const std::string& scan_id,
                                  const download::DownloadDangerType danger_type,
                                  const int64_t content_size);
 
@@ -140,6 +142,7 @@
       const std::string& download_digest_sha256,
       const std::string& mime_type,
       const std::string& trigger,
+      const std::string& scan_id,
       safe_browsing::DeepScanAccessPoint access_point,
       const enterprise_connectors::ContentAnalysisResponse::Result& result,
       const int64_t content_size,
@@ -152,6 +155,7 @@
       const std::string& download_digest_sha256,
       const std::string& mime_type,
       const std::string& trigger,
+      const std::string& scan_id,
       safe_browsing::DeepScanAccessPoint access_point,
       const enterprise_connectors::ContentAnalysisResponse::Result& result,
       const int64_t content_size);
@@ -177,6 +181,7 @@
                                 const std::string& download_digest_sha256,
                                 const std::string& threat_type,
                                 const std::string& mime_type,
+                                const std::string& scan_id,
                                 const int64_t content_size,
                                 safe_browsing::EventResult event_result);
   void OnDangerousDownloadEvent(const GURL& url,
@@ -184,6 +189,7 @@
                                 const std::string& download_digest_sha256,
                                 const download::DownloadDangerType danger_type,
                                 const std::string& mime_type,
+                                const std::string& scan_id,
                                 const int64_t content_size,
                                 safe_browsing::EventResult event_result);
 
@@ -198,6 +204,7 @@
       const std::string& download_digest_sha256,
       const std::string& threat_type,
       const std::string& mime_type,
+      const std::string& scan_id,
       const int64_t content_size);
   void OnDangerousDownloadWarningBypassed(
       const GURL& url,
@@ -205,6 +212,7 @@
       const std::string& download_digest_sha256,
       const download::DownloadDangerType danger_type,
       const std::string& mime_type,
+      const std::string& scan_id,
       const int64_t content_size);
 
   // Returns true if enterprise real-time reporting should be initialized,
@@ -297,7 +305,8 @@
       safe_browsing::EventResult event_result,
       const std::string& malware_family,
       const std::string& malware_category,
-      const std::string& evidence_locker_filepath);
+      const std::string& evidence_locker_filepath,
+      const std::string& scan_id);
 
   // Notifies listeners that the analysis connector detected a violation.
   void OnSensitiveDataEvent(
@@ -306,6 +315,7 @@
       const std::string& download_digest_sha256,
       const std::string& mime_type,
       const std::string& trigger,
+      const std::string& scan_id,
       const enterprise_connectors::ContentAnalysisResponse::Result& result,
       const int64_t content_size,
       safe_browsing::EventResult event_result);
diff --git a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_unittest.cc b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_unittest.cc
index 9038ff25..c636e9d 100644
--- a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_unittest.cc
+++ b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_unittest.cc
@@ -137,7 +137,7 @@
     SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile_)
         ->OnDangerousDownloadOpened(
             GURL("https://evil.com/malware.exe"), "/path/to/malware.exe",
-            "sha256_of_malware_exe", "exe",
+            "sha256_of_malware_exe", "exe", "scan_id",
             download::DownloadDangerType::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE,
             1234);
   }
@@ -158,15 +158,15 @@
     SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile_)
         ->OnDangerousDownloadEvent(
             GURL("https://maybevil.com/warning.exe"), "/path/to/warning.exe",
-            "sha256_of_warning_exe", "POTENTIALLY_UNWANTED", "exe", 567,
-            safe_browsing::EventResult::WARNED);
+            "sha256_of_warning_exe", "POTENTIALLY_UNWANTED", "exe", "scan_id",
+            567, safe_browsing::EventResult::WARNED);
   }
 
   void TriggerOnDangerousDownloadEventBypass() {
     SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile_)
         ->OnDangerousDownloadWarningBypassed(
             GURL("https://bypassevil.com/bypass.exe"), "/path/to/bypass.exe",
-            "sha256_of_bypass_exe", "BYPASSED_WARNING", "exe", 890);
+            "sha256_of_bypass_exe", "BYPASSED_WARNING", "exe", "scan_id", 890);
   }
 
   void TriggerOnSensitiveDataEvent(safe_browsing::EventResult event_result) {
@@ -183,7 +183,7 @@
         ->OnAnalysisConnectorResult(
             GURL("https://evil.com/sensitive_data.txt"), "sensitive_data.txt",
             "sha256_of_data", "text/plain",
-            SafeBrowsingPrivateEventRouter::kTriggerFileUpload,
+            SafeBrowsingPrivateEventRouter::kTriggerFileUpload, "scan_id",
             safe_browsing::DeepScanAccessPoint::UPLOAD, result, 12345,
             event_result);
   }
@@ -369,6 +369,8 @@
   EXPECT_EQ(
       safe_browsing::EventResultToString(safe_browsing::EventResult::BYPASSED),
       *event->FindStringKey(SafeBrowsingPrivateEventRouter::kKeyEventResult));
+  EXPECT_EQ("scan_id",
+            *event->FindStringKey(SafeBrowsingPrivateEventRouter::kKeyScanId));
 }
 
 TEST_F(SafeBrowsingPrivateEventRouterTest,
@@ -491,6 +493,8 @@
   EXPECT_EQ(
       safe_browsing::EventResultToString(safe_browsing::EventResult::WARNED),
       *event->FindStringKey(SafeBrowsingPrivateEventRouter::kKeyEventResult));
+  EXPECT_EQ("scan_id",
+            *event->FindStringKey(SafeBrowsingPrivateEventRouter::kKeyScanId));
 }
 
 TEST_F(SafeBrowsingPrivateEventRouterTest,
@@ -533,6 +537,8 @@
   EXPECT_EQ(
       safe_browsing::EventResultToString(safe_browsing::EventResult::BYPASSED),
       *event->FindStringKey(SafeBrowsingPrivateEventRouter::kKeyEventResult));
+  EXPECT_EQ("scan_id",
+            *event->FindStringKey(SafeBrowsingPrivateEventRouter::kKeyScanId));
 }
 
 TEST_F(SafeBrowsingPrivateEventRouterTest, PolicyControlOnToOffIsDynamic) {
@@ -741,6 +747,8 @@
   EXPECT_EQ("fake rule",
             *triggered_rule.FindStringKey(
                 SafeBrowsingPrivateEventRouter::kKeyTriggeredRuleName));
+  EXPECT_EQ("scan_id",
+            *event->FindStringKey(SafeBrowsingPrivateEventRouter::kKeyScanId));
 }
 
 TEST_F(SafeBrowsingPrivateEventRouterTest, TestOnSensitiveDataEvent_Blocked) {
@@ -791,6 +799,8 @@
   EXPECT_EQ("fake rule",
             *triggered_rule.FindStringKey(
                 SafeBrowsingPrivateEventRouter::kKeyTriggeredRuleName));
+  EXPECT_EQ("scan_id",
+            *event->FindStringKey(SafeBrowsingPrivateEventRouter::kKeyScanId));
 }
 
 TEST_F(SafeBrowsingPrivateEventRouterTest, TestOnUnscannedFileEvent_Allowed) {
diff --git a/chrome/browser/federated_learning/floc_eligibility_browsertest.cc b/chrome/browser/federated_learning/floc_eligibility_browsertest.cc
index 460037e..230aa9f 100644
--- a/chrome/browser/federated_learning/floc_eligibility_browsertest.cc
+++ b/chrome/browser/federated_learning/floc_eligibility_browsertest.cc
@@ -56,8 +56,11 @@
     : public subresource_filter::SubresourceFilterBrowserTest {
  public:
   FlocEligibilityBrowserTest() {
-    scoped_feature_list_.InitAndEnableFeature(
-        blink::features::kInterestCohortFeaturePolicy);
+    scoped_feature_list_.InitWithFeatures(
+        /*enabled_features=*/{blink::features::kInterestCohortFeaturePolicy},
+        /*disabled_features=*/{
+            federated_learning::
+                kFlocPagesWithAdResourcesDefaultIncludedInFlocComputation});
   }
 
   void SetUpOnMainThread() override {
diff --git a/chrome/browser/media/encrypted_media_supported_types_browsertest.cc b/chrome/browser/media/encrypted_media_supported_types_browsertest.cc
index 7fe399cb..69a2da9c 100644
--- a/chrome/browser/media/encrypted_media_supported_types_browsertest.cc
+++ b/chrome/browser/media/encrypted_media_supported_types_browsertest.cc
@@ -1301,8 +1301,10 @@
   EXPECT_WV(IsVideoEncryptionSchemeSupported(kWidevine, "cbcs-1-9",
                                              "SW_SECURE_DECODE"));
 
-  // For HW_SECURE* robustness levels. 'cenc' is always supported. 'cbcs' is
-  // supported on ChromeOS, but not on other platforms.
+  // For HW_SECURE* robustness levels, 'cenc' is always supported by setting
+  // the command line switch kOverrideHardwareSecureCodecsForTesting.
+  // ChromeOS has special handling for hardware support, so 'cbcs' is supported
+  // on ChromeOS, but not on other platforms.
   EXPECT_WV(
       IsAudioEncryptionSchemeSupported(kWidevine, "cenc", "HW_SECURE_CRYPTO"));
   EXPECT_WV(
diff --git a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_browsertest.cc b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_browsertest.cc
index 35bebcc..0a397ec 100644
--- a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_browsertest.cc
+++ b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_browsertest.cc
@@ -81,6 +81,7 @@
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/service_worker_context.h"
 #include "content/public/browser/storage_partition.h"
+#include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/network_service_util.h"
 #include "content/public/common/page_type.h"
@@ -447,6 +448,15 @@
                             base::Unretained(this)));
     EXPECT_TRUE(origin_server_->Start());
 
+    referring_page_server_ = std::make_unique<net::EmbeddedTestServer>(
+        net::EmbeddedTestServer::TYPE_HTTPS);
+    referring_page_server_->SetSSLConfig(
+        net::EmbeddedTestServer::CERT_TEST_NAMES);
+    referring_page_server_->ServeFilesFromSourceDirectory("chrome/test/data");
+    referring_page_server_->SetSSLConfig(
+        net::EmbeddedTestServer::CERT_TEST_NAMES);
+    EXPECT_TRUE(referring_page_server_->Start());
+
     proxy_server_ = std::make_unique<net::EmbeddedTestServer>(
         net::EmbeddedTestServer::TYPE_HTTPS);
     proxy_server_->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
@@ -539,7 +549,9 @@
 
   void InsertSpeculation(bool subresources,
                          const std::vector<GURL>& prefetch_urls) {
-    ui_test_utils::NavigateToURL(browser(), GURL("about:blank"));
+    // Make sure we are on a valid referring page.
+    ui_test_utils::NavigateToURL(browser(),
+                                 GetReferringPageServerURL("/search/q=blah"));
 
     std::string speculation_script = R"(
       var script = document.createElement('script');
@@ -781,6 +793,10 @@
     return origin_server_->GetURL("a.test", path);
   }
 
+  GURL GetReferringPageServerURL(const std::string& path) const {
+    return referring_page_server_->GetURL("www.google.com", path);
+  }
+
   GURL GetCanaryServerURL() const { return canary_server_->GetURL("/"); }
 
  private:
@@ -959,6 +975,7 @@
   std::unique_ptr<net::EmbeddedTestServer> origin_server_;
   std::unique_ptr<net::EmbeddedTestServer> http_server_;
   std::unique_ptr<net::EmbeddedTestServer> canary_server_;
+  std::unique_ptr<net::EmbeddedTestServer> referring_page_server_;
 
   std::vector<net::test_server::HttpRequest> origin_server_requests_;
   std::vector<net::test_server::HttpRequest> proxy_server_requests_;
@@ -4126,7 +4143,7 @@
           }},
          {blink::features::kLightweightNoStatePrefetch, {}},
          {blink::features::kSpeculationRulesPrefetchProxy, {}}},
-        {});
+        {{features::kLazyImageLoading, {}}});
   }
 
  private:
@@ -4138,8 +4155,6 @@
   base::HistogramTester histogram_tester;
 
   SetDataSaverEnabled(true);
-  GURL starting_page = GetOriginServerURL("/simple.html");
-  ui_test_utils::NavigateToURL(browser(), starting_page);
   WaitForUpdatedCustomProxyConfig();
 
   ui_test_utils::WaitForHistoryToLoad(HistoryServiceFactory::GetForProfile(
@@ -4161,7 +4176,6 @@
 
   tab_helper_observer.SetOnNSPFinishedClosure(nsp_run_loop.QuitClosure());
 
-  GURL doc_url("https://www.google.com/search?q=test");
   InsertSpeculation(true, {eligible_link});
 
   // This run loop will quit when all the prefetch responses have been
@@ -4264,7 +4278,7 @@
       origin_server_requests();
 
   // Only one request for the image is expected, and it should have cookies.
-  ASSERT_EQ(origin_requests_after_prerender.size() + 1,
+  EXPECT_EQ(origin_requests_after_prerender.size() + 1,
             origin_requests_after_click.size());
   net::test_server::HttpRequest request =
       origin_requests_after_click[origin_requests_after_click.size() - 1];
@@ -4311,7 +4325,6 @@
 IN_PROC_BROWSER_TEST_F(SpeculationPrefetchProxyTest,
                        DISABLE_ON_WIN_MAC_CHROMEOS(ConnectProxyEndtoEnd)) {
   SetDataSaverEnabled(true);
-  ui_test_utils::NavigateToURL(browser(), GetOriginServerURL("/simple.html"));
   WaitForUpdatedCustomProxyConfig();
 
   PrefetchProxyTabHelper* tab_helper =
diff --git a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_tab_helper.cc b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_tab_helper.cc
index 4111739..d2f44874 100644
--- a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_tab_helper.cc
+++ b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_tab_helper.cc
@@ -1153,11 +1153,20 @@
 
 void PrefetchProxyTabHelper::PrefetchSpeculationCandidates(
     const std::vector<GURL>& private_prefetches_with_subresources,
-    const std::vector<GURL>& private_prefetches) {
+    const std::vector<GURL>& private_prefetches,
+    const GURL& source_document_url) {
   // Use navigation predictor by default.
   if (!PrefetchProxyUseSpeculationRules())
     return;
 
+  // For IP-private prefetches, using the Google proxy needs to be restricted to
+  // first party sites until we understand the benefit and determine interest
+  // from other sites.
+  if (!IsGoogleDomainUrl(source_document_url, google_util::DISALLOW_SUBDOMAIN,
+                         google_util::ALLOW_NON_STANDARD_PORTS)) {
+    return;
+  }
+
   std::vector<GURL> prefetches = private_prefetches;
   std::set<GURL> allowed_to_prefetch_subresources;
   for (auto url : private_prefetches_with_subresources) {
diff --git a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_tab_helper.h b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_tab_helper.h
index 0dcb3da0..46bc939 100644
--- a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_tab_helper.h
+++ b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_tab_helper.h
@@ -180,7 +180,8 @@
   // |private_prefetches_with_subresources| (up to a limit).
   void PrefetchSpeculationCandidates(
       const std::vector<GURL>& private_prefetches_with_subresources,
-      const std::vector<GURL>& private_prefetches);
+      const std::vector<GURL>& private_prefetches,
+      const GURL& source_document_url);
 
   // content::WebContentsObserver implementation.
   void DidStartNavigation(
diff --git a/chrome/browser/prefetch/speculation_host_impl.cc b/chrome/browser/prefetch/speculation_host_impl.cc
index 13645ce..c985d7c 100644
--- a/chrome/browser/prefetch/speculation_host_impl.cc
+++ b/chrome/browser/prefetch/speculation_host_impl.cc
@@ -32,7 +32,8 @@
 SpeculationHostImpl::SpeculationHostImpl(
     content::RenderFrameHost* frame_host,
     mojo::PendingReceiver<blink::mojom::SpeculationHost> receiver)
-    : FrameServiceBase(frame_host, std::move(receiver)) {}
+    : FrameServiceBase(frame_host, std::move(receiver)),
+      document_url_(frame_host->GetLastCommittedURL()) {}
 
 SpeculationHostImpl::~SpeculationHostImpl() = default;
 
@@ -85,5 +86,5 @@
 
   // TODO(ryansturm): Handle CSP prefetch-src. https://crbug.com/1192857
   prefetch_proxy_tab_helper->PrefetchSpeculationCandidates(
-      private_prefetches_with_subresources, private_prefetches);
+      private_prefetches_with_subresources, private_prefetches, document_url_);
 }
diff --git a/chrome/browser/prefetch/speculation_host_impl.h b/chrome/browser/prefetch/speculation_host_impl.h
index 9882d76..0f294c4 100644
--- a/chrome/browser/prefetch/speculation_host_impl.h
+++ b/chrome/browser/prefetch/speculation_host_impl.h
@@ -10,6 +10,7 @@
 #include "content/public/browser/frame_service_base.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "third_party/blink/public/mojom/speculation_rules/speculation_rules.mojom.h"
+#include "url/gurl.h"
 
 namespace content {
 class RenderFrameHost;
@@ -39,6 +40,9 @@
   // processes one update per document. At present, updates after the first are
   // ignored.
   bool received_update_ = false;
+
+  // The URL of the document that this object was created for.
+  const GURL document_url_;
 };
 
 #endif  // CHROME_BROWSER_PREFETCH_SPECULATION_HOST_IMPL_H_
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js
index 5bb64259..1121635 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js
@@ -2449,7 +2449,7 @@
   `,
           function(root) {
             const contentEditable =
-                root.find({attributes: {editableRoot: true}});
+                root.find({attributes: {contentEditableRoot: true}});
             mockFeedback.call(contentEditable.focus.bind(contentEditable))
                 .expectSpeech(/Testing testing\s+one two three/)
                 .call(doCmd('nextLine'))
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing_test.js
index 71d2e011..71af871 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing_test.js
@@ -1195,7 +1195,7 @@
     </div>
   `,
       function(root) {
-        const input = root.find({attributes: {editableRoot: true}});
+        const input = root.find({attributes: {contentEditableRoot: true}});
         this.listenOnce(input, 'focus', function() {
           mockFeedback.call(this.press(KeyCode.END, {ctrl: true}))
               .expectSpeech('test')
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode.js
index 4d82ac0f..b74e3fd 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode.js
@@ -130,7 +130,7 @@
     }
 
     let editable = this.getEditableOrRelatedEditable_(range.start.node);
-    while (editable && !editable.editableRoot) {
+    while (editable && !editable.contentEditableRoot) {
       if (!editable.parent ||
           editable.parent.state[chrome.automation.StateType.EDITABLE]) {
         // Not all editables from all trees (e.g. Android, views) set the
diff --git a/chrome/browser/resources/chromeos/accessibility/common/automation_predicate.js b/chrome/browser/resources/chromeos/accessibility/common/automation_predicate.js
index 67046a51..f396e6d 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/automation_predicate.js
+++ b/chrome/browser/resources/chromeos/accessibility/common/automation_predicate.js
@@ -783,8 +783,7 @@
   Role.ALERT_DIALOG, Role.CLIENT, Role.DIALOG, Role.LAYOUT_TABLE,
   Role.LAYOUT_TABLE_CELL, Role.LAYOUT_TABLE_ROW, Role.ROOT_WEB_AREA,
   Role.WEB_VIEW, Role.WINDOW, Role.EMBEDDED_OBJECT, Role.IFRAME,
-  Role.IFRAME_PRESENTATIONAL, Role.PLUGIN_OBJECT, Role.NONE, Role.UNKNOWN,
-  Role.PANE
+  Role.IFRAME_PRESENTATIONAL, Role.PLUGIN_OBJECT, Role.UNKNOWN, Role.PANE
 ]);
 
 
diff --git a/chrome/browser/resources/chromeos/arc_graphics_tracing/OWNERS b/chrome/browser/resources/chromeos/arc_graphics_tracing/OWNERS
new file mode 100644
index 0000000..f35d9cb9
--- /dev/null
+++ b/chrome/browser/resources/chromeos/arc_graphics_tracing/OWNERS
@@ -0,0 +1,2 @@
+alanding@chromium.org
+khmel@chromium.org
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_page.js b/chrome/browser/resources/settings/chromeos/internet_page/internet_page.js
index 8d6b74b..a48fc735 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_page.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/internet_page.js
@@ -119,6 +119,16 @@
       value: false,
     },
 
+    /**
+     * Page name, if defined, indicating that the next deviceStates update
+     * should call attemptShowCellularSetupDialog_().
+     * @private {cellularSetup.CellularSetupPageName|null}
+     */
+    pendingShowCellularSetupDialogAttemptPageName_: {
+      type: String,
+      value: null,
+    },
+
     /** @private {boolean} */
     showCellularSetupDialog_: {
       type: Boolean,
@@ -285,7 +295,11 @@
         const pageName = queryParams.get('showPsimFlow') === 'true' ?
             cellularSetup.CellularSetupPageName.PSIM_FLOW_UI :
             cellularSetup.CellularSetupPageName.ESIM_FLOW_UI;
-        this.attemptShowCellularSetupDialog_(pageName);
+        // If the page just loaded, deviceStates will not be fully initialized
+        // yet. Set pendingShowCellularSetupDialogAttemptPageName_ to indicate
+        // showCellularSetupDialogAttempt_() should be called next deviceStates
+        // update.
+        this.pendingShowCellularSetupDialogAttemptPageName_ = pageName;
       }
 
       this.showSimLockDialog_ = !!queryParams.get('showSimLockDialog') &&
@@ -413,6 +427,14 @@
    * @private
    */
   attemptShowCellularSetupDialog_(pageName) {
+    const cellularDeviceState =
+        this.getDeviceState_(mojom.NetworkType.kCellular, this.deviceStates);
+    if (!cellularDeviceState ||
+        cellularDeviceState.deviceState !== mojom.DeviceStateType.kEnabled) {
+      this.showErrorToast_(this.i18n('eSimMobileDataNotEnabledErrorToast'));
+      return;
+    }
+
     if (pageName === cellularSetup.CellularSetupPageName.PSIM_FLOW_UI) {
       this.showCellularSetupDialog_ = true;
       this.cellularSetupDialogPageName_ = pageName;
@@ -615,6 +637,12 @@
         detailPage.close();
       }
     }
+
+    if (this.pendingShowCellularSetupDialogAttemptPageName_) {
+      this.attemptShowCellularSetupDialog_(
+          this.pendingShowCellularSetupDialogAttemptPageName_);
+      this.pendingShowCellularSetupDialogAttemptPageName_ = null;
+    }
   },
 
   /**
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.html b/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.html
index 456bae6..5fc0a9a 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.html
+++ b/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.html
@@ -66,6 +66,7 @@
 
         <template is="dom-if" if="[[showSimInfo_(deviceState)]]" restamp>
           <network-siminfo
+              on-click="doNothing_"
               network-state="[[activeNetworkState]]"
               device-state="[[deviceState]]">
           </network-siminfo>
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.cc b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.cc
index e3a0c8b..f0b9c33a 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.cc
@@ -106,7 +106,8 @@
     const std::set<std::string>* expected_mimetypes,
     int expected_content_size,
     const std::string& expected_result,
-    const std::string& expected_username) {
+    const std::string& expected_username,
+    const base::Optional<std::string>& expected_scan_id) {
   event_key_ = SafeBrowsingPrivateEventRouter::kKeyDangerousDownloadEvent;
   url_ = expected_url;
   filenames_and_hashes_[expected_filename] = expected_sha256;
@@ -116,6 +117,7 @@
   content_size_ = expected_content_size;
   result_ = expected_result;
   username_ = expected_username;
+  scan_id_ = expected_scan_id;
   EXPECT_CALL(*client_, UploadSecurityEventReport_(_, _, _, _))
       .WillOnce([this](content::BrowserContext* context,
                        bool include_device_info, base::Value& report,
@@ -136,7 +138,8 @@
     const std::set<std::string>* expected_mimetypes,
     int expected_content_size,
     const std::string& expected_result,
-    const std::string& expected_username) {
+    const std::string& expected_username,
+    const std::string& expected_scan_id) {
   event_key_ = SafeBrowsingPrivateEventRouter::kKeySensitiveDataEvent;
   url_ = expected_url;
   dlp_verdict_ = expected_dlp_verdict;
@@ -146,6 +149,7 @@
   content_size_ = expected_content_size;
   result_ = expected_result;
   username_ = expected_username;
+  scan_id_ = expected_scan_id;
   EXPECT_CALL(*client_, UploadSecurityEventReport_(_, _, _, _))
       .WillOnce([this](content::BrowserContext* context,
                        bool include_device_info, base::Value& report,
@@ -168,7 +172,8 @@
         const std::set<std::string>* expected_mimetypes,
         int expected_content_size,
         const std::string& expected_result,
-        const std::string& expected_username) {
+        const std::string& expected_username,
+        const std::string& expected_scan_id) {
   event_key_ = SafeBrowsingPrivateEventRouter::kKeyDangerousDownloadEvent;
   url_ = expected_url;
   filenames_and_hashes_[expected_filename] = expected_sha256;
@@ -178,6 +183,7 @@
   content_size_ = expected_content_size;
   result_ = expected_result;
   username_ = expected_username;
+  scan_id_ = expected_scan_id;
   EXPECT_CALL(*client_, UploadSecurityEventReport_(_, _, _, _))
       .WillOnce([this](content::BrowserContext* context,
                        bool include_device_info, base::Value& report,
@@ -209,7 +215,8 @@
         const std::set<std::string>* expected_mimetypes,
         int expected_content_size,
         const std::string& expected_result,
-        const std::string& expected_username) {
+        const std::string& expected_username,
+        const std::string& expected_scan_id) {
   event_key_ = SafeBrowsingPrivateEventRouter::kKeySensitiveDataEvent;
   url_ = expected_url;
   filenames_and_hashes_[expected_filename] = expected_sha256;
@@ -219,6 +226,7 @@
   result_ = expected_result;
   dlp_verdict_ = expected_dlp_verdict;
   username_ = expected_username;
+  scan_id_ = expected_scan_id;
   EXPECT_CALL(*client_, UploadSecurityEventReport_(_, _, _, _))
       .WillOnce([this](content::BrowserContext* context,
                        bool include_device_info, base::Value& report,
@@ -232,6 +240,7 @@
         event_key_ = SafeBrowsingPrivateEventRouter::kKeyDangerousDownloadEvent;
         threat_type_ = expected_threat_type;
         dlp_verdict_ = base::nullopt;
+        scan_id_ = base::nullopt;
         ValidateReport(&report);
         if (!done_closure_.is_null())
           done_closure_.Run();
@@ -247,7 +256,8 @@
     const std::set<std::string>* expected_mimetypes,
     int expected_content_size,
     const std::string& expected_result,
-    const std::string& expected_username) {
+    const std::string& expected_username,
+    const base::Optional<std::string>& expected_scan_id) {
   event_key_ = SafeBrowsingPrivateEventRouter::kKeyDangerousDownloadEvent;
   url_ = expected_url;
   filenames_and_hashes_[expected_filename] = expected_sha256;
@@ -257,6 +267,7 @@
   content_size_ = expected_content_size;
   result_ = expected_result;
   username_ = expected_username;
+  scan_id_ = expected_scan_id;
   EXPECT_CALL(*client_, UploadSecurityEventReport_(_, _, _, _))
       .WillOnce([this](content::BrowserContext* context,
                        bool include_device_info, base::Value& report,
@@ -299,6 +310,7 @@
                 unscanned_reason_);
   ValidateField(event, SafeBrowsingPrivateEventRouter::kKeyProfileUserName,
                 username_);
+  ValidateField(event, SafeBrowsingPrivateEventRouter::kKeyScanId, scan_id_);
   ValidateMimeType(event);
   ValidateDlpVerdict(event);
 }
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.h b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.h
index 56bfd500c..4343714 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.h
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.h
@@ -44,7 +44,8 @@
       const std::set<std::string>* expected_mimetypes,
       int expected_content_size,
       const std::string& expected_result,
-      const std::string& expected_username);
+      const std::string& expected_username,
+      const base::Optional<std::string>& expected_scan_id);
 
   void ExpectSensitiveDataEvent(
       const std::string& expected_url,
@@ -56,7 +57,8 @@
       const std::set<std::string>* expected_mimetypes,
       int expected_content_size,
       const std::string& expected_result,
-      const std::string& expected_username);
+      const std::string& expected_username,
+      const std::string& expected_scan_id);
 
   void ExpectDangerousDeepScanningResultAndSensitiveDataEvent(
       const std::string& expected_url,
@@ -69,7 +71,8 @@
       const std::set<std::string>* expected_mimetypes,
       int expected_content_size,
       const std::string& expected_result,
-      const std::string& expected_username);
+      const std::string& expected_username,
+      const std::string& expected_scan_id);
 
   void ExpectSensitiveDataEventAndDangerousDeepScanningResult(
       const std::string& expected_url,
@@ -82,7 +85,8 @@
       const std::set<std::string>* expected_mimetypes,
       int expected_content_size,
       const std::string& expected_result,
-      const std::string& expected_username);
+      const std::string& expected_username,
+      const std::string& expected_scan_id);
 
   void ExpectUnscannedFileEvent(const std::string& expected_url,
                                 const std::string& expected_filename,
@@ -114,7 +118,8 @@
       const std::set<std::string>* expected_mimetypes,
       int expected_content_size,
       const std::string& expected_result,
-      const std::string& expected_username);
+      const std::string& expected_username,
+      const base::Optional<std::string>& expected_scan_id);
 
   void ExpectNoReport();
 
@@ -152,6 +157,7 @@
   const std::set<std::string>* mimetypes_ = nullptr;
   base::Optional<std::string> result_ = base::nullopt;
   std::string username_;
+  base::Optional<std::string> scan_id_ = base::nullopt;
 
   // When multiple files generate events, we don't necessarily know in which
   // order they will be reported. As such, we use a map to ensure all of them
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.cc b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.cc
index 104a5e5..6246405 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.cc
@@ -99,7 +99,8 @@
                                    event_result);
     } else if (result.triggered_rules_size() > 0) {
       router->OnAnalysisConnectorResult(url, file_name, download_digest_sha256,
-                                        mime_type, trigger, access_point,
+                                        mime_type, trigger,
+                                        response.request_token(), access_point,
                                         result, content_size, event_result);
     }
   }
@@ -132,7 +133,7 @@
 
     router->OnAnalysisConnectorWarningBypassed(
         url, file_name, download_digest_sha256, mime_type, trigger,
-        access_point, result, content_size);
+        response.request_token(), access_point, result, content_size);
   }
 }
 
diff --git a/chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc b/chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc
index a0786df..2aae9e1 100644
--- a/chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc
+++ b/chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc
@@ -718,7 +718,8 @@
       /*mimetypes*/ &zip_types,
       /*size*/ 276,
       /*result*/ EventResultToString(EventResult::WARNED),
-      /*username*/ kUserName);
+      /*username*/ kUserName,
+      /*scan_id*/ last_enterprise_request().request_token());
 
   // The DLP scan finishes asynchronously, and finds nothing. The malware result
   // is attached to the response again.
@@ -808,7 +809,8 @@
       /*mimetypes*/ &zip_types,
       /*size*/ 276,
       /*result*/ EventResultToString(EventResult::WARNED),
-      /*username*/ kUserName);
+      /*username*/ kUserName,
+      /*scan_id*/ last_enterprise_request().request_token());
   WaitForDownloadToFinish();
 
   // The file should be blocked.
@@ -898,7 +900,7 @@
       /*mimetypes*/ &zip_types,
       /*size*/ 276,
       /*result*/ EventResultToString(EventResult::BLOCKED),
-      /*username*/ kUserName);
+      /*username*/ kUserName, /*scan_id*/ base::nullopt);
 
   WaitForDownloadToFinish();
 
@@ -1152,6 +1154,14 @@
   if (threat_type.empty()) {
     validator.ExpectNoReport();
   } else {
+    // A scan ID is only expected when a deep scan is performed and when its
+    // result will be reported over the metadata check one.
+    auto scan_id =
+        deep_scan_needed() && scanning_verdict() != ScanningVerdict::SAFE
+            ? base::Optional<std::string>(
+                  last_enterprise_request().request_token())
+            : base::nullopt;
+
     validator.ExpectDangerousDeepScanningResult(
         /*url*/ url.spec(),
         /*filename*/
@@ -1166,7 +1176,8 @@
         /*mimetypes*/ &zip_types,
         /*size*/ 276,
         /*result*/ EventResultToString(EventResult::WARNED),
-        /*username*/ kUserName);
+        /*username*/ kUserName,
+        /*scan_id*/ scan_id);
   }
 
   // The deep scanning malware verdict is returned asynchronously. It is not
diff --git a/chrome/browser/safe_browsing/download_protection/deep_scanning_request_unittest.cc b/chrome/browser/safe_browsing/download_protection/deep_scanning_request_unittest.cc
index 163c08b..d0d3499 100644
--- a/chrome/browser/safe_browsing/download_protection/deep_scanning_request_unittest.cc
+++ b/chrome/browser/safe_browsing/download_protection/deep_scanning_request_unittest.cc
@@ -99,6 +99,8 @@
   return &set;
 }
 
+constexpr char kScanId[] = "scan_id";
+
 }  // namespace
 
 class FakeBinaryUploadService : public BinaryUploadService {
@@ -480,6 +482,7 @@
         &download_protection_service_, settings().value());
 
     enterprise_connectors::ContentAnalysisResponse response;
+    response.set_request_token(kScanId);
 
     auto* malware_result = response.add_results();
     malware_result->set_tag("malware");
@@ -516,7 +519,8 @@
         /*mimetypes*/ ExeMimeTypes(),
         /*size*/ std::string("download contents").size(),
         /*result*/ EventResultToString(EventResult::WARNED),
-        /*username*/ kUserName);
+        /*username*/ kUserName,
+        /*scan_id*/ kScanId);
 
     request.Start();
 
@@ -531,6 +535,7 @@
         &download_protection_service_, settings().value());
 
     enterprise_connectors::ContentAnalysisResponse response;
+    response.set_request_token(kScanId);
 
     auto* malware_result = response.add_results();
     malware_result->set_tag("malware");
@@ -567,7 +572,8 @@
         /*mimetypes*/ ExeMimeTypes(),
         /*size*/ std::string("download contents").size(),
         /*result*/ EventResultToString(EventResult::WARNED),
-        /*username*/ kUserName);
+        /*username*/ kUserName,
+        /*scan_id*/ kScanId);
 
     request.Start();
 
@@ -582,6 +588,7 @@
         &download_protection_service_, settings().value());
 
     enterprise_connectors::ContentAnalysisResponse response;
+    response.set_request_token(kScanId);
 
     auto* dlp_result = response.add_results();
     dlp_result->set_tag("dlp");
@@ -608,7 +615,8 @@
         /*mimetypes*/ ExeMimeTypes(),
         /*size*/ std::string("download contents").size(),
         EventResultToString(EventResult::BLOCKED),
-        /*username*/ kUserName);
+        /*username*/ kUserName,
+        /*scan_id*/ kScanId);
 
     request.Start();
 
@@ -623,6 +631,7 @@
         &download_protection_service_, settings().value());
 
     enterprise_connectors::ContentAnalysisResponse response;
+    response.set_request_token(kScanId);
 
     auto* dlp_result = response.add_results();
     dlp_result->set_tag("dlp");
@@ -649,7 +658,8 @@
         /*mimetypes*/ ExeMimeTypes(),
         /*size*/ std::string("download contents").size(),
         EventResultToString(EventResult::WARNED),
-        /*username*/ kUserName);
+        /*username*/ kUserName,
+        /*scan_id*/ kScanId);
 
     request.Start();
 
@@ -664,6 +674,7 @@
         &download_protection_service_, settings().value());
 
     enterprise_connectors::ContentAnalysisResponse response;
+    response.set_request_token(kScanId);
 
     auto* dlp_result = response.add_results();
     dlp_result->set_tag("dlp");
@@ -694,7 +705,7 @@
         /*mimetypes*/ ExeMimeTypes(),
         /*size*/ std::string("download contents").size(),
         EventResultToString(EventResult::BLOCKED),
-        /*username*/ kUserName);
+        /*username*/ kUserName, /*scan_id*/ kScanId);
 
     request.Start();
 
@@ -826,6 +837,7 @@
         &download_protection_service_, settings().value());
 
     enterprise_connectors::ContentAnalysisResponse response;
+    response.set_request_token(kScanId);
 
     auto* malware_result = response.add_results();
     malware_result->set_tag("malware");
@@ -852,7 +864,7 @@
         /*mimetypes*/ ExeMimeTypes(),
         /*size*/ std::string("download contents").size(),
         /*result*/ EventResultToString(expected_event_result_for_malware()),
-        /*username*/ kUserName);
+        /*username*/ kUserName, /*scan_id*/ kScanId);
 
     request.Start();
 
@@ -866,6 +878,7 @@
         &download_protection_service_, settings().value());
 
     enterprise_connectors::ContentAnalysisResponse response;
+    response.set_request_token(kScanId);
 
     auto* malware_result = response.add_results();
     malware_result->set_tag("malware");
@@ -892,7 +905,7 @@
         /*mimetypes*/ ExeMimeTypes(),
         /*size*/ std::string("download contents").size(),
         /*result*/ EventResultToString(EventResult::WARNED),
-        /*username*/ kUserName);
+        /*username*/ kUserName, /*scan_id*/ kScanId);
 
     request.Start();
 
diff --git a/chrome/browser/safe_browsing/download_protection/download_protection_service.cc b/chrome/browser/safe_browsing/download_protection/download_protection_service.cc
index 14f716f..c254dc33 100644
--- a/chrome/browser/safe_browsing/download_protection/download_protection_service.cc
+++ b/chrome/browser/safe_browsing/download_protection/download_protection_service.cc
@@ -579,10 +579,14 @@
   auto* router =
       extensions::SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile);
   if (router) {
+    auto* scan_result = static_cast<enterprise_connectors::ScanResult*>(
+        item->GetUserData(enterprise_connectors::ScanResult::kKey));
     router->OnDangerousDownloadOpened(
         item->GetURL(), item->GetTargetFilePath().AsUTF8Unsafe(),
         base::HexEncode(raw_digest_sha256.data(), raw_digest_sha256.size()),
-        item->GetMimeType(), item->GetDangerType(), item->GetTotalBytes());
+        item->GetMimeType(),
+        scan_result ? scan_result->response.request_token() : "",
+        item->GetDangerType(), item->GetTotalBytes());
   }
 }
 
diff --git a/chrome/browser/safe_browsing/download_protection/download_reporter.cc b/chrome/browser/safe_browsing/download_protection/download_reporter.cc
index cb387c1d9..b262e07 100644
--- a/chrome/browser/safe_browsing/download_protection/download_reporter.cc
+++ b/chrome/browser/safe_browsing/download_protection/download_reporter.cc
@@ -57,7 +57,7 @@
       router->OnDangerousDownloadEvent(
           download->GetURL(), download->GetTargetFilePath().AsUTF8Unsafe(),
           base::HexEncode(raw_digest_sha256.data(), raw_digest_sha256.size()),
-          download->GetDangerType(), download->GetMimeType(),
+          download->GetDangerType(), download->GetMimeType(), /*scan_id*/ "",
           download->GetTotalBytes(), EventResult::WARNED);
     }
   }
@@ -75,10 +75,15 @@
         extensions::SafeBrowsingPrivateEventRouterFactory::GetForProfile(
             profile);
     if (router) {
+      enterprise_connectors::ScanResult* stored_result =
+          static_cast<enterprise_connectors::ScanResult*>(
+              download->GetUserData(enterprise_connectors::ScanResult::kKey));
       router->OnDangerousDownloadWarningBypassed(
           download->GetURL(), download->GetTargetFilePath().AsUTF8Unsafe(),
           base::HexEncode(raw_digest_sha256.data(), raw_digest_sha256.size()),
           original_danger_type, download->GetMimeType(),
+          /*scan_id*/
+          stored_result ? stored_result->response.request_token() : "",
           download->GetTotalBytes());
     }
   }
diff --git a/chrome/browser/ui/app_list/app_list_client_impl.cc b/chrome/browser/ui/app_list/app_list_client_impl.cc
index b120bdd..32dc029 100644
--- a/chrome/browser/ui/app_list/app_list_client_impl.cc
+++ b/chrome/browser/ui/app_list/app_list_client_impl.cc
@@ -332,7 +332,7 @@
     current_model_updater_ = nullptr;
   }
 
-  template_url_service_observer_.RemoveAll();
+  template_url_service_observation_.Reset();
 
   profile_ = new_profile;
   if (!profile_)
@@ -345,7 +345,7 @@
   DCHECK(!profile_->IsGuestSession() || profile_->IsOffTheRecord())
       << "Guest mode must use OffTheRecord profile";
 
-  template_url_service_observer_.Add(
+  template_url_service_observation_.Observe(
       TemplateURLServiceFactory::GetForProfile(profile_));
 
   app_list::AppListSyncableService* syncable_service =
diff --git a/chrome/browser/ui/app_list/app_list_client_impl.h b/chrome/browser/ui/app_list/app_list_client_impl.h
index 7d252c1..f44f8983 100644
--- a/chrome/browser/ui/app_list/app_list_client_impl.h
+++ b/chrome/browser/ui/app_list/app_list_client_impl.h
@@ -17,7 +17,7 @@
 #include "ash/public/cpp/shelf_types.h"
 #include "base/callback_forward.h"
 #include "base/memory/weak_ptr.h"
-#include "base/scoped_observer.h"
+#include "base/scoped_observation.h"
 #include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
 #include "components/search_engines/template_url_service.h"
 #include "components/search_engines/template_url_service_observer.h"
@@ -163,8 +163,8 @@
   std::unique_ptr<app_list::SearchController> search_controller_;
   std::unique_ptr<AppSyncUIStateWatcher> app_sync_ui_state_watcher_;
 
-  ScopedObserver<TemplateURLService, TemplateURLServiceObserver>
-      template_url_service_observer_{this};
+  base::ScopedObservation<TemplateURLService, TemplateURLServiceObserver>
+      template_url_service_observation_{this};
 
   ash::AppListController* app_list_controller_ = nullptr;
 
diff --git a/chrome/browser/ui/app_list/arc/arc_app_utils.cc b/chrome/browser/ui/app_list/arc/arc_app_utils.cc
index a456cdfe..41eaed20 100644
--- a/chrome/browser/ui/app_list/arc/arc_app_utils.cc
+++ b/chrome/browser/ui/app_list/arc/arc_app_utils.cc
@@ -16,7 +16,7 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/no_destructor.h"
 #include "base/observer_list.h"
-#include "base/scoped_observer.h"
+#include "base/scoped_multi_source_observation.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
@@ -721,17 +721,18 @@
     ~ProfileDestroyedObserver() override = default;
 
     void Observe(Profile* profile) {
-      if (!observed_profiles_.IsObserving(profile))
-        observed_profiles_.Add(profile);
+      if (!observed_profiles_.IsObservingSource(profile))
+        observed_profiles_.AddObservation(profile);
     }
 
     void OnProfileWillBeDestroyed(Profile* profile) override {
-      observed_profiles_.Remove(profile);
+      observed_profiles_.RemoveObservation(profile);
       GetAppLaunchObserverMap()->erase(profile);
     }
 
    private:
-    ScopedObserver<Profile, ProfileObserver> observed_profiles_{this};
+    base::ScopedMultiSourceObservation<Profile, ProfileObserver>
+        observed_profiles_{this};
 
     DISALLOW_COPY_AND_ASSIGN(ProfileDestroyedObserver);
   };
diff --git a/chrome/browser/ui/app_list/search/assistant_search_provider.cc b/chrome/browser/ui/app_list/search/assistant_search_provider.cc
index 1f80e0f..826081ba 100644
--- a/chrome/browser/ui/app_list/search/assistant_search_provider.cc
+++ b/chrome/browser/ui/app_list/search/assistant_search_provider.cc
@@ -115,8 +115,8 @@
   UpdateResults();
 
   // Bind observers.
-  assistant_controller_observer_.Add(ash::AssistantController::Get());
-  assistant_state_observer_.Add(ash::AssistantState::Get());
+  assistant_controller_observation_.Observe(ash::AssistantController::Get());
+  assistant_state_observation_.Observe(ash::AssistantState::Get());
   ash::AssistantSuggestionsController::Get()->GetModel()->AddObserver(this);
 }
 
@@ -133,8 +133,12 @@
 
 void AssistantSearchProvider::OnAssistantControllerDestroying() {
   ash::AssistantSuggestionsController::Get()->GetModel()->RemoveObserver(this);
-  assistant_state_observer_.Remove(ash::AssistantState::Get());
-  assistant_controller_observer_.Remove(ash::AssistantController::Get());
+  DCHECK(assistant_state_observation_.IsObservingSource(
+      ash::AssistantState::Get()));
+  assistant_state_observation_.Reset();
+  DCHECK(assistant_controller_observation_.IsObservingSource(
+      ash::AssistantController::Get()));
+  assistant_controller_observation_.Reset();
 }
 
 void AssistantSearchProvider::OnAssistantFeatureAllowedChanged(
diff --git a/chrome/browser/ui/app_list/search/assistant_search_provider.h b/chrome/browser/ui/app_list/search/assistant_search_provider.h
index d731547d..f8dbaf4 100644
--- a/chrome/browser/ui/app_list/search/assistant_search_provider.h
+++ b/chrome/browser/ui/app_list/search/assistant_search_provider.h
@@ -11,7 +11,7 @@
 #include "ash/public/cpp/assistant/assistant_state.h"
 #include "ash/public/cpp/assistant/controller/assistant_controller.h"
 #include "ash/public/cpp/assistant/controller/assistant_controller_observer.h"
-#include "base/scoped_observer.h"
+#include "base/scoped_observation.h"
 #include "chrome/browser/ui/app_list/search/search_provider.h"
 
 namespace app_list {
@@ -50,11 +50,12 @@
   // Invoke to update results based on current state.
   void UpdateResults();
 
-  ScopedObserver<ash::AssistantController, ash::AssistantControllerObserver>
-      assistant_controller_observer_{this};
+  base::ScopedObservation<ash::AssistantController,
+                          ash::AssistantControllerObserver>
+      assistant_controller_observation_{this};
 
-  ScopedObserver<ash::AssistantStateBase, ash::AssistantStateObserver>
-      assistant_state_observer_{this};
+  base::ScopedObservation<ash::AssistantStateBase, ash::AssistantStateObserver>
+      assistant_state_observation_{this};
 };
 
 }  // namespace app_list
diff --git a/chrome/browser/ui/app_list/search/assistant_text_search_provider.cc b/chrome/browser/ui/app_list/search/assistant_text_search_provider.cc
index 20469d4..31de5ca 100644
--- a/chrome/browser/ui/app_list/search/assistant_text_search_provider.cc
+++ b/chrome/browser/ui/app_list/search/assistant_text_search_provider.cc
@@ -87,8 +87,8 @@
   UpdateResults();
 
   // Bind observers.
-  assistant_controller_observer_.Add(ash::AssistantController::Get());
-  assistant_state_observer_.Add(ash::AssistantState::Get());
+  assistant_controller_observation_.Observe(ash::AssistantController::Get());
+  assistant_state_observation_.Observe(ash::AssistantState::Get());
 }
 
 AssistantTextSearchProvider::~AssistantTextSearchProvider() = default;
@@ -103,8 +103,12 @@
 }
 
 void AssistantTextSearchProvider::OnAssistantControllerDestroying() {
-  assistant_state_observer_.Remove(ash::AssistantState::Get());
-  assistant_controller_observer_.Remove(ash::AssistantController::Get());
+  DCHECK(assistant_state_observation_.IsObservingSource(
+      ash::AssistantState::Get()));
+  assistant_state_observation_.Reset();
+  DCHECK(assistant_controller_observation_.IsObservingSource(
+      ash::AssistantController::Get()));
+  assistant_controller_observation_.Reset();
 }
 
 void AssistantTextSearchProvider::OnAssistantFeatureAllowedChanged(
diff --git a/chrome/browser/ui/app_list/search/assistant_text_search_provider.h b/chrome/browser/ui/app_list/search/assistant_text_search_provider.h
index e39ca44..f240bbb0a 100644
--- a/chrome/browser/ui/app_list/search/assistant_text_search_provider.h
+++ b/chrome/browser/ui/app_list/search/assistant_text_search_provider.h
@@ -8,7 +8,7 @@
 #include "ash/public/cpp/assistant/assistant_state.h"
 #include "ash/public/cpp/assistant/controller/assistant_controller.h"
 #include "ash/public/cpp/assistant/controller/assistant_controller_observer.h"
-#include "base/scoped_observer.h"
+#include "base/scoped_observation.h"
 #include "chrome/browser/ui/app_list/search/search_provider.h"
 
 namespace app_list {
@@ -49,11 +49,12 @@
 
   std::u16string query_;
 
-  ScopedObserver<ash::AssistantController, ash::AssistantControllerObserver>
-      assistant_controller_observer_{this};
+  base::ScopedObservation<ash::AssistantController,
+                          ash::AssistantControllerObserver>
+      assistant_controller_observation_{this};
 
-  ScopedObserver<ash::AssistantStateBase, ash::AssistantStateObserver>
-      assistant_state_observer_{this};
+  base::ScopedObservation<ash::AssistantStateBase, ash::AssistantStateObserver>
+      assistant_state_observation_{this};
 };
 
 }  // namespace app_list
diff --git a/chrome/browser/ui/app_list/search/search_metrics_observer.cc b/chrome/browser/ui/app_list/search/search_metrics_observer.cc
index 56aabbf..68a778ed 100644
--- a/chrome/browser/ui/app_list/search/search_metrics_observer.cc
+++ b/chrome/browser/ui/app_list/search/search_metrics_observer.cc
@@ -85,7 +85,7 @@
 
 SearchMetricsObserver::SearchMetricsObserver(ash::AppListNotifier* notifier) {
   if (notifier) {
-    observer_.Add(notifier);
+    observation_.Observe(notifier);
   } else {
     LogError(Error::kMissingNotifier);
   }
diff --git a/chrome/browser/ui/app_list/search/search_metrics_observer.h b/chrome/browser/ui/app_list/search/search_metrics_observer.h
index 51e8291..678e0aa 100644
--- a/chrome/browser/ui/app_list/search/search_metrics_observer.h
+++ b/chrome/browser/ui/app_list/search/search_metrics_observer.h
@@ -11,7 +11,7 @@
 #include "ash/public/cpp/app_list/app_list_metrics.h"
 #include "ash/public/cpp/app_list/app_list_notifier.h"
 #include "base/macros.h"
-#include "base/scoped_observer.h"
+#include "base/scoped_observation.h"
 
 namespace app_list {
 
@@ -44,8 +44,8 @@
   void OnQueryChanged(const std::u16string& query) override;
 
  private:
-  ScopedObserver<ash::AppListNotifier, ash::AppListNotifier::Observer>
-      observer_{this};
+  base::ScopedObservation<ash::AppListNotifier, ash::AppListNotifier::Observer>
+      observation_{this};
 
   // Whether the search box currently contains an empty query.
   bool last_query_empty_ = true;
diff --git a/chrome/browser/ui/ash/assistant/assistant_client_impl.cc b/chrome/browser/ui/ash/assistant/assistant_client_impl.cc
index 5e29ee0..f89b391 100644
--- a/chrome/browser/ui/ash/assistant/assistant_client_impl.cc
+++ b/chrome/browser/ui/ash/assistant/assistant_client_impl.cc
@@ -188,9 +188,9 @@
 }
 
 void AssistantClientImpl::OnUserProfileLoaded(const AccountId& account_id) {
-  if (!assistant_state_observation_.IsObserving() && !initialized_ &&
+  if (!assistant_state_observer_.IsObservingSources() && !initialized_ &&
       ash::AssistantState::Get()) {
-    assistant_state_observation_.Observe(ash::AssistantState::Get());
+    assistant_state_observer_.Add(ash::AssistantState::Get());
   }
 }
 
diff --git a/chrome/browser/ui/ash/assistant/assistant_client_impl.h b/chrome/browser/ui/ash/assistant/assistant_client_impl.h
index e255d727..7f30c27b 100644
--- a/chrome/browser/ui/ash/assistant/assistant_client_impl.h
+++ b/chrome/browser/ui/ash/assistant/assistant_client_impl.h
@@ -11,7 +11,7 @@
 #include "ash/public/cpp/assistant/assistant_client.h"
 #include "ash/public/cpp/assistant/assistant_state.h"
 #include "base/macros.h"
-#include "base/scoped_observation.h"
+#include "base/scoped_observer.h"
 #include "chrome/browser/ui/ash/assistant/device_actions.h"
 #include "chromeos/assistant/buildflags.h"
 #include "chromeos/services/assistant/public/cpp/assistant_client.h"
@@ -116,8 +116,8 @@
   Profile* profile_ = nullptr;
   signin::IdentityManager* identity_manager_ = nullptr;
 
-  base::ScopedObservation<ash::AssistantStateBase, ash::AssistantStateObserver>
-      assistant_state_observation_{this};
+  ScopedObserver<ash::AssistantStateBase, ash::AssistantStateObserver>
+      assistant_state_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(AssistantClientImpl);
 };
diff --git a/chrome/browser/ui/ash/assistant/assistant_timers_browsertest.cc b/chrome/browser/ui/ash/assistant/assistant_timers_browsertest.cc
index 8eebf65..9b9b97b 100644
--- a/chrome/browser/ui/ash/assistant/assistant_timers_browsertest.cc
+++ b/chrome/browser/ui/ash/assistant/assistant_timers_browsertest.cc
@@ -13,7 +13,7 @@
 #include "ash/system/message_center/unified_message_center_view.h"
 #include "ash/system/status_area_widget.h"
 #include "ash/system/unified/unified_system_tray.h"
-#include "base/scoped_observation.h"
+#include "base/scoped_observer.h"
 #include "base/strings/string_util.h"
 #include "base/test/bind.h"
 #include "base/test/icu_test_util.h"
@@ -52,9 +52,8 @@
       return;                                                                 \
     }                                                                         \
     MockMessageCenterObserver mock;                                           \
-    base::ScopedObservation<MessageCenter, MessageCenterObserver>             \
-        observation_{&mock};                                                  \
-    observation_.Observe(MessageCenter::Get());                               \
+    ScopedObserver<MessageCenter, MessageCenterObserver> observer_{&mock};    \
+    observer_.Add(MessageCenter::Get());                                      \
                                                                               \
     base::RunLoop run_loop;                                                   \
     EXPECT_CALL(mock, OnNotificationAdded)                                    \
@@ -291,7 +290,7 @@
   // Observe notifications.
   MockMessageCenterObserver mock;
   ScopedObserver<MessageCenter, MessageCenterObserver> scoped_observer{&mock};
-  scoped_observer.Observe(MessageCenter::Get());
+  scoped_observer.Add(MessageCenter::Get());
 
   // Show Assistant UI (once ready).
   tester()->StartAssistantAndWaitForReady();
diff --git a/chrome/browser/ui/ash/assistant/assistant_web_view_impl_browsertest.cc b/chrome/browser/ui/ash/assistant/assistant_web_view_impl_browsertest.cc
index 84e8856e..c424a35 100644
--- a/chrome/browser/ui/ash/assistant/assistant_web_view_impl_browsertest.cc
+++ b/chrome/browser/ui/ash/assistant/assistant_web_view_impl_browsertest.cc
@@ -10,7 +10,7 @@
 #include "ash/public/cpp/assistant/assistant_web_view.h"
 #include "ash/public/cpp/assistant/assistant_web_view_factory.h"
 #include "base/run_loop.h"
-#include "base/scoped_observation.h"
+#include "base/scoped_observer.h"
 #include "base/strings/stringprintf.h"
 #include "chrome/browser/ui/ash/assistant/assistant_test_mixin.h"
 #include "chrome/test/base/mixin_based_in_process_browser_test.h"
@@ -41,7 +41,7 @@
   {                                                                      \
     MockViewObserver mock;                                               \
     ScopedObserver<views::View, views::ViewObserver> observer{&mock};    \
-    observer.Observe(static_cast<views::View*>(web_view_));              \
+    observer.Add(static_cast<views::View*>(web_view_));                  \
                                                                          \
     base::RunLoop run_loop;                                              \
     EXPECT_CALL(mock, OnViewPreferredSizeChanged)                        \
@@ -56,7 +56,7 @@
   {                                                                            \
     MockAssistantWebViewObserver mock;                                         \
     ScopedObserver<AssistantWebView, AssistantWebView::Observer> obs{&mock};   \
-    obs.Observe(web_view_);                                                    \
+    obs.Add(web_view_);                                                        \
                                                                                \
     base::RunLoop run_loop;                                                    \
     EXPECT_CALL(mock, DidStopLoading).WillOnce(testing::Invoke([&run_loop]() { \
@@ -71,7 +71,7 @@
   {                                                                          \
     MockAssistantWebViewObserver mock;                                       \
     ScopedObserver<AssistantWebView, AssistantWebView::Observer> obs{&mock}; \
-    obs.Observe(web_view_);                                                  \
+    obs.Add(web_view_);                                                      \
                                                                              \
     base::RunLoop run_loop;                                                  \
     EXPECT_CALL(mock, DidSuppressNavigation)                                 \
@@ -90,7 +90,7 @@
   {                                                                          \
     MockAssistantWebViewObserver mock;                                       \
     ScopedObserver<AssistantWebView, AssistantWebView::Observer> obs{&mock}; \
-    obs.Observe(web_view_);                                                  \
+    obs.Add(web_view_);                                                      \
                                                                              \
     base::RunLoop run_loop;                                                  \
     EXPECT_CALL(mock, DidChangeCanGoBack)                                    \
diff --git a/chrome/browser/ui/ash/assistant/device_actions.cc b/chrome/browser/ui/ash/assistant/device_actions.cc
index 5cb48fe..23e8bb1 100644
--- a/chrome/browser/ui/ash/assistant/device_actions.cc
+++ b/chrome/browser/ui/ash/assistant/device_actions.cc
@@ -218,8 +218,8 @@
 
   app_list_subscribers_.AddObserver(subscriber);
 
-  if (prefs && !scoped_prefs_observations_.IsObservingSource(prefs))
-    scoped_prefs_observations_.AddObservation(prefs);
+  if (prefs && !scoped_prefs_observer_.IsObserving(prefs))
+    scoped_prefs_observer_.Add(prefs);
 }
 
 void DeviceActions::RemoveAppListEventSubscriber(
diff --git a/chrome/browser/ui/ash/assistant/device_actions.h b/chrome/browser/ui/ash/assistant/device_actions.h
index 4cc685c..73d1e57 100644
--- a/chrome/browser/ui/ash/assistant/device_actions.h
+++ b/chrome/browser/ui/ash/assistant/device_actions.h
@@ -10,7 +10,7 @@
 #include <vector>
 
 #include "ash/public/cpp/android_intent_helper.h"
-#include "base/scoped_multi_source_observation.h"
+#include "base/scoped_observer.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
 #include "chrome/browser/ui/ash/assistant/device_actions_delegate.h"
 #include "chromeos/services/assistant/public/cpp/assistant_service.h"
@@ -57,8 +57,8 @@
 
   std::unique_ptr<DeviceActionsDelegate> delegate_;
 
-  base::ScopedMultiSourceObservation<ArcAppListPrefs, ArcAppListPrefs::Observer>
-      scoped_prefs_observations_{this};
+  ScopedObserver<ArcAppListPrefs, ArcAppListPrefs::Observer>
+      scoped_prefs_observer_{this};
   base::ObserverList<chromeos::assistant::AppListEventSubscriber>
       app_list_subscribers_;
   DISALLOW_COPY_AND_ASSIGN(DeviceActions);
diff --git a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_arc_tracker.cc b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_arc_tracker.cc
index 06b04933..177d870 100644
--- a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_arc_tracker.cc
+++ b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_arc_tracker.cc
@@ -59,7 +59,7 @@
   ArcAppListPrefs* const prefs = ArcAppListPrefs::Get(observed_profile_);
   DCHECK(prefs);
   prefs->RemoveObserver(this);
-  observed_windows_.RemoveAll();
+  observed_windows_.RemoveAllObservations();
 }
 
 void AppServiceAppWindowArcTracker::ActiveUserChanged(
@@ -110,8 +110,8 @@
   if (it != task_id_to_arc_app_window_info_.end())
     it->second->set_window(nullptr);
 
-  if (observed_windows_.IsObserving(window))
-    observed_windows_.Remove(window);
+  if (observed_windows_.IsObservingSource(window))
+    observed_windows_.RemoveObservation(window);
 }
 
 void AppServiceAppWindowArcTracker::OnAppStatesChanged(
@@ -391,7 +391,7 @@
           chromeos::features::kArcPreImeKeyEventSupport)) {
     window->SetProperty(aura::client::kSkipImeProcessing, true);
   }
-  observed_windows_.Add(window);
+  observed_windows_.AddObservation(window);
 
   if (info->app_shelf_id().app_id() == arc::kPlayStoreAppId)
     HandlePlayStoreLaunch(info);
@@ -404,8 +404,8 @@
 void AppServiceAppWindowArcTracker::RemoveCandidateWindow(
     aura::Window* window) {
   arc_window_candidates_.erase(window);
-  if (observed_windows_.IsObserving(window))
-    observed_windows_.Remove(window);
+  if (observed_windows_.IsObservingSource(window))
+    observed_windows_.RemoveObservation(window);
 }
 
 void AppServiceAppWindowArcTracker::OnItemDelegateDiscarded(
diff --git a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_arc_tracker.h b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_arc_tracker.h
index 31226e4..5288264 100644
--- a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_arc_tracker.h
+++ b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_arc_tracker.h
@@ -12,7 +12,7 @@
 #include <vector>
 
 #include "ash/public/cpp/shelf_types.h"
-#include "base/scoped_observer.h"
+#include "base/scoped_multi_source_observation.h"
 #include "chrome/browser/ash/arc/session/arc_session_manager_observer.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
 #include "components/arc/arc_util.h"
@@ -166,7 +166,8 @@
   // ARC container is actually started.
   base::Time opt_in_management_check_start_time_;
 
-  ScopedObserver<aura::Window, aura::WindowObserver> observed_windows_{this};
+  base::ScopedMultiSourceObservation<aura::Window, aura::WindowObserver>
+      observed_windows_{this};
 
   base::WeakPtrFactory<AppServiceAppWindowArcTracker> weak_ptr_factory_{this};
 };
diff --git a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_shelf_controller.cc b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_shelf_controller.cc
index a733f1de..c5d68ff2 100644
--- a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_shelf_controller.cc
+++ b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_shelf_controller.cc
@@ -88,7 +88,7 @@
 
   for (auto* browser : *BrowserList::GetInstance()) {
     if (browser && browser->window() && browser->window()->GetNativeWindow()) {
-      observed_windows_.Add(browser->window()->GetNativeWindow());
+      observed_windows_.AddObservation(browser->window()->GetNativeWindow());
 
       // Observe the browser tabs
       TabStripModel* tab_strip = browser->tab_strip_model();
@@ -98,7 +98,7 @@
           continue;
         aura::Window* window = tab->GetNativeView();
         if (window) {
-          observed_windows_.Add(window);
+          observed_windows_.AddObservation(window);
         }
       }
     }
@@ -116,7 +116,7 @@
   }
 
   app_service_instance_helper_.reset();
-  observed_windows_.RemoveAll();
+  observed_windows_.RemoveAllObservations();
 }
 
 AppWindowShelfItemController*
@@ -176,7 +176,7 @@
   if (!widget || !widget->is_top_level())
     return;
 
-  observed_windows_.Add(window);
+  observed_windows_.AddObservation(window);
   if (arc_tracker_)
     arc_tracker_->AddCandidateWindow(window);
 }
@@ -207,7 +207,7 @@
     aura::Window* window,
     bool visible) {
   // Skip OnWindowVisibilityChanged for ancestors/descendants.
-  if (!observed_windows_.IsObserving(window))
+  if (!observed_windows_.IsObservingSource(window))
     return;
 
   if (arc_tracker_)
@@ -254,8 +254,8 @@
 
 void AppServiceAppWindowShelfController::OnWindowDestroying(
     aura::Window* window) {
-  DCHECK(observed_windows_.IsObserving(window));
-  observed_windows_.Remove(window);
+  DCHECK(observed_windows_.IsObservingSource(window));
+  observed_windows_.RemoveObservation(window);
   if (arc_tracker_)
     arc_tracker_->RemoveCandidateWindow(window);
   if (crostini_tracker_)
@@ -332,7 +332,7 @@
   }
 
   aura::Window* window = update.Window();
-  if (!observed_windows_.IsObserving(window))
+  if (!observed_windows_.IsObservingSource(window))
     return;
 
   ash::ShelfID shelf_id(update.AppId(), update.LaunchId());
@@ -452,15 +452,15 @@
 }
 
 void AppServiceAppWindowShelfController::ObserveWindow(aura::Window* window) {
-  if (!window || observed_windows_.IsObserving(window))
+  if (!window || observed_windows_.IsObservingSource(window))
     return;
-  observed_windows_.Add(window);
+  observed_windows_.AddObservation(window);
 }
 
 bool AppServiceAppWindowShelfController::IsObservingWindow(
     aura::Window* window) {
   DCHECK(window);
-  return observed_windows_.IsObserving(window);
+  return observed_windows_.IsObservingSource(window);
 }
 
 std::vector<aura::Window*> AppServiceAppWindowShelfController::GetArcWindows() {
@@ -474,7 +474,7 @@
 void AppServiceAppWindowShelfController::SetWindowActivated(
     aura::Window* window,
     bool active) {
-  if (!window || !observed_windows_.IsObserving(window))
+  if (!window || !observed_windows_.IsObservingSource(window))
     return;
 
   const ash::ShelfID shelf_id = GetShelfId(window);
diff --git a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_shelf_controller.h b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_shelf_controller.h
index f637144..7f2d869 100644
--- a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_shelf_controller.h
+++ b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_shelf_controller.h
@@ -11,7 +11,7 @@
 
 #include "ash/public/cpp/shelf_types.h"
 #include "base/macros.h"
-#include "base/scoped_observer.h"
+#include "base/scoped_multi_source_observation.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/ui/ash/launcher/app_service/app_service_instance_registry_helper.h"
 #include "chrome/browser/ui/ash/launcher/app_window_shelf_controller.h"
@@ -140,7 +140,8 @@
                                  content::BrowserContext* browser_context);
 
   AuraWindowToAppWindow aura_window_to_app_window_;
-  ScopedObserver<aura::Window, aura::WindowObserver> observed_windows_{this};
+  base::ScopedMultiSourceObservation<aura::Window, aura::WindowObserver>
+      observed_windows_{this};
 
   apps::AppServiceProxyChromeOs* proxy_ = nullptr;
   std::unique_ptr<AppServiceInstanceRegistryHelper>
diff --git a/chrome/browser/ui/ash/launcher/app_window_shelf_item_controller.cc b/chrome/browser/ui/ash/launcher/app_window_shelf_item_controller.cc
index 8a72080..1380f4c 100644
--- a/chrome/browser/ui/ash/launcher/app_window_shelf_item_controller.cc
+++ b/chrome/browser/ui/ash/launcher/app_window_shelf_item_controller.cc
@@ -74,8 +74,8 @@
 
 void AppWindowShelfItemController::AddWindow(AppWindowBase* app_window) {
   aura::Window* window = app_window->GetNativeWindow();
-  if (window && !observed_windows_.IsObserving(window))
-    observed_windows_.Add(window);
+  if (window && !observed_windows_.IsObservingSource(window))
+    observed_windows_.AddObservation(window);
   if (window && window->GetProperty(ash::kHideInShelfKey))
     hidden_windows_.push_front(app_window);
   else
@@ -95,8 +95,8 @@
 void AppWindowShelfItemController::RemoveWindow(AppWindowBase* app_window) {
   DCHECK(app_window);
   aura::Window* window = app_window->GetNativeWindow();
-  if (window && observed_windows_.IsObserving(window))
-    observed_windows_.Remove(window);
+  if (window && observed_windows_.IsObservingSource(window))
+    observed_windows_.RemoveObservation(window);
   if (app_window == last_active_window_)
     last_active_window_ = nullptr;
   auto iter = std::find(windows_.begin(), windows_.end(), app_window);
diff --git a/chrome/browser/ui/ash/launcher/app_window_shelf_item_controller.h b/chrome/browser/ui/ash/launcher/app_window_shelf_item_controller.h
index ab3c5f7..4dbc3bb3 100644
--- a/chrome/browser/ui/ash/launcher/app_window_shelf_item_controller.h
+++ b/chrome/browser/ui/ash/launcher/app_window_shelf_item_controller.h
@@ -11,7 +11,7 @@
 
 #include "ash/public/cpp/shelf_item_delegate.h"
 #include "base/macros.h"
-#include "base/scoped_observer.h"
+#include "base/scoped_multi_source_observation.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_observer.h"
 
@@ -100,7 +100,8 @@
   AppWindowBase* last_active_window_ = nullptr;
 
   // Scoped list of observed windows (for removal on destruction)
-  ScopedObserver<aura::Window, aura::WindowObserver> observed_windows_{this};
+  base::ScopedMultiSourceObservation<aura::Window, aura::WindowObserver>
+      observed_windows_{this};
 
   std::unique_ptr<ShelfContextMenu> context_menu_;
 
diff --git a/chrome/browser/ui/commander/simple_command_source.cc b/chrome/browser/ui/commander/simple_command_source.cc
index 9603e33..985a5e2 100644
--- a/chrome/browser/ui/commander/simple_command_source.cc
+++ b/chrome/browser/ui/commander/simple_command_source.cc
@@ -68,6 +68,7 @@
       {IDC_SELECT_PREVIOUS_TAB, u"Previous tab"},
       {IDC_MOVE_TAB_NEXT, u"Move tab forward"},
       {IDC_MOVE_TAB_PREVIOUS, u"Move tab backward"},
+      {IDC_QRCODE_GENERATOR, u"Create QR code"},
   };
   CommandSource::CommandResults results;
   FuzzyFinder finder(input);
diff --git a/chrome/browser/ui/commander/tab_command_source.cc b/chrome/browser/ui/commander/tab_command_source.cc
index d5dd3a47d..3c0390a 100644
--- a/chrome/browser/ui/commander/tab_command_source.cc
+++ b/chrome/browser/ui/commander/tab_command_source.cc
@@ -9,6 +9,7 @@
 
 #include "base/bind.h"
 #include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/send_tab_to_self/send_tab_to_self_util.h"
 #include "chrome/browser/ui/accelerator_utils.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
@@ -516,6 +517,15 @@
     results.push_back(std::move(item));
   }
 
+  if (send_tab_to_self::ShouldOfferFeature(
+          tab_strip_model->GetActiveWebContents())) {
+    if (auto item = ItemForTitle(u"Send tab to self...", finder, &ranges)) {
+      item->command = base::BindOnce(&chrome::SendTabToSelfFromPageAction,
+                                     base::Unretained(browser));
+      results.push_back(std::move(item));
+    }
+  }
+
   return results;
 }
 
diff --git a/chrome/browser/ui/cookie_controls/cookie_controls_service.cc b/chrome/browser/ui/cookie_controls/cookie_controls_service.cc
index cc90041e..58d97240 100644
--- a/chrome/browser/ui/cookie_controls/cookie_controls_service.cc
+++ b/chrome/browser/ui/cookie_controls/cookie_controls_service.cc
@@ -33,10 +33,10 @@
 
 void CookieControlsService::Init() {
   incongito_cookie_settings_ = CookieSettingsFactory::GetForProfile(profile_);
-  cookie_observer_.Add(incongito_cookie_settings_.get());
+  cookie_observations_.AddObservation(incongito_cookie_settings_.get());
   regular_cookie_settings_ =
       CookieSettingsFactory::GetForProfile(profile_->GetOriginalProfile());
-  cookie_observer_.Add(regular_cookie_settings_.get());
+  cookie_observations_.AddObservation(regular_cookie_settings_.get());
 
   if (profile_->GetProfilePolicyConnector()) {
     policy_registrar_ = std::make_unique<policy::PolicyChangeRegistrar>(
@@ -51,7 +51,7 @@
 }
 
 void CookieControlsService::Shutdown() {
-  cookie_observer_.RemoveAll();
+  cookie_observations_.RemoveAllObservations();
   policy_registrar_.reset();
 }
 
diff --git a/chrome/browser/ui/cookie_controls/cookie_controls_service.h b/chrome/browser/ui/cookie_controls/cookie_controls_service.h
index 4d2be7eb..e4d1b4c 100644
--- a/chrome/browser/ui/cookie_controls/cookie_controls_service.h
+++ b/chrome/browser/ui/cookie_controls/cookie_controls_service.h
@@ -8,7 +8,7 @@
 #include <memory>
 
 #include "base/observer_list.h"
-#include "base/scoped_observer.h"
+#include "base/scoped_multi_source_observation.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
 #include "components/content_settings/core/common/content_settings.h"
@@ -66,9 +66,9 @@
   std::unique_ptr<policy::PolicyChangeRegistrar> policy_registrar_;
   scoped_refptr<content_settings::CookieSettings> incongito_cookie_settings_;
   scoped_refptr<content_settings::CookieSettings> regular_cookie_settings_;
-  ScopedObserver<content_settings::CookieSettings,
-                 content_settings::CookieSettings::Observer>
-      cookie_observer_{this};
+  base::ScopedMultiSourceObservation<content_settings::CookieSettings,
+                                     content_settings::CookieSettings::Observer>
+      cookie_observations_{this};
 
   base::ObserverList<Observer> observers_;
 
diff --git a/chrome/browser/ui/exclusive_access/exclusive_access_test.cc b/chrome/browser/ui/exclusive_access/exclusive_access_test.cc
index b24ba9f3..60796dda 100644
--- a/chrome/browser/ui/exclusive_access/exclusive_access_test.cc
+++ b/chrome/browser/ui/exclusive_access/exclusive_access_test.cc
@@ -36,7 +36,8 @@
 
 FullscreenNotificationObserver::FullscreenNotificationObserver(
     Browser* browser) {
-  observer_.Add(browser->exclusive_access_manager()->fullscreen_controller());
+  observation_.Observe(
+      browser->exclusive_access_manager()->fullscreen_controller());
 }
 
 FullscreenNotificationObserver::~FullscreenNotificationObserver() = default;
diff --git a/chrome/browser/ui/exclusive_access/exclusive_access_test.h b/chrome/browser/ui/exclusive_access/exclusive_access_test.h
index 9f4803d9..c2836b8c 100644
--- a/chrome/browser/ui/exclusive_access/exclusive_access_test.h
+++ b/chrome/browser/ui/exclusive_access/exclusive_access_test.h
@@ -11,7 +11,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/run_loop.h"
-#include "base/scoped_observer.h"
+#include "base/scoped_observation.h"
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "chrome/browser/ui/exclusive_access/exclusive_access_bubble_hide_callback.h"
@@ -46,7 +46,8 @@
 
  protected:
   bool observed_change_ = false;
-  ScopedObserver<FullscreenController, FullscreenObserver> observer_{this};
+  base::ScopedObservation<FullscreenController, FullscreenObserver>
+      observation_{this};
   base::RunLoop run_loop_;
 
   DISALLOW_COPY_AND_ASSIGN(FullscreenNotificationObserver);
diff --git a/chrome/browser/ui/extensions/extension_action_view_controller.cc b/chrome/browser/ui/extensions/extension_action_view_controller.cc
index 62418ea..d0f640c 100644
--- a/chrome/browser/ui/extensions/extension_action_view_controller.cc
+++ b/chrome/browser/ui/extensions/extension_action_view_controller.cc
@@ -29,6 +29,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "components/sessions/content/session_tab_helper.h"
 #include "extensions/browser/extension_action.h"
+#include "extensions/browser/extension_action_manager.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/common/api/extension_action/action_info.h"
 #include "extensions/common/extension.h"
@@ -42,13 +43,39 @@
 using extensions::CommandService;
 using extensions::ExtensionActionRunner;
 
+// static
+std::unique_ptr<ExtensionActionViewController>
+ExtensionActionViewController::Create(
+    const extensions::ExtensionId& extension_id,
+    Browser* browser,
+    ExtensionsContainer* extensions_container,
+    bool in_overflow_mode) {
+  DCHECK(browser);
+  DCHECK(extensions_container);
+
+  auto* registry = extensions::ExtensionRegistry::Get(browser->profile());
+  scoped_refptr<const extensions::Extension> extension =
+      registry->enabled_extensions().GetByID(extension_id);
+  DCHECK(extension);
+  extensions::ExtensionAction* extension_action =
+      extensions::ExtensionActionManager::Get(browser->profile())
+          ->GetExtensionAction(*extension);
+  DCHECK(extension_action);
+
+  // WrapUnique() because the constructor is private.
+  return base::WrapUnique(new ExtensionActionViewController(
+      std::move(extension), browser, extension_action, registry,
+      extensions_container, in_overflow_mode));
+}
+
 ExtensionActionViewController::ExtensionActionViewController(
-    const extensions::Extension* extension,
+    scoped_refptr<const extensions::Extension> extension,
     Browser* browser,
     extensions::ExtensionAction* extension_action,
+    extensions::ExtensionRegistry* extension_registry,
     ExtensionsContainer* extensions_container,
     bool in_overflow_mode)
-    : extension_(extension),
+    : extension_(std::move(extension)),
       browser_(browser),
       in_overflow_mode_(in_overflow_mode),
       extension_action_(extension_action),
@@ -56,13 +83,11 @@
       popup_host_(nullptr),
       view_delegate_(nullptr),
       platform_delegate_(ExtensionActionPlatformDelegate::Create(this)),
-      icon_factory_(browser->profile(), extension, extension_action, this),
-      extension_registry_(
-          extensions::ExtensionRegistry::Get(browser_->profile())) {
-  DCHECK(extensions_container);
-  DCHECK(extension_action);
-  DCHECK(extension);
-}
+      icon_factory_(browser->profile(),
+                    extension_.get(),
+                    extension_action,
+                    this),
+      extension_registry_(extension_registry) {}
 
 ExtensionActionViewController::~ExtensionActionViewController() {
   DCHECK(!IsShowingPopup());
diff --git a/chrome/browser/ui/extensions/extension_action_view_controller.h b/chrome/browser/ui/extensions/extension_action_view_controller.h
index d32ec0b..39d74808 100644
--- a/chrome/browser/ui/extensions/extension_action_view_controller.h
+++ b/chrome/browser/ui/extensions/extension_action_view_controller.h
@@ -13,6 +13,8 @@
 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
 #include "extensions/browser/extension_host.h"
 #include "extensions/browser/extension_host_observer.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_id.h"
 #include "ui/gfx/image/image.h"
 
 class Browser;
@@ -43,11 +45,12 @@
   // The different options for showing a popup.
   enum PopupShowAction { SHOW_POPUP, SHOW_POPUP_AND_INSPECT };
 
-  ExtensionActionViewController(const extensions::Extension* extension,
-                                Browser* browser,
-                                extensions::ExtensionAction* extension_action,
-                                ExtensionsContainer* extensions_container,
-                                bool in_overflow_mode);
+  static std::unique_ptr<ExtensionActionViewController> Create(
+      const extensions::ExtensionId& extension_id,
+      Browser* browser,
+      ExtensionsContainer* extensions_container,
+      bool in_overflow_mode);
+
   ~ExtensionActionViewController() override;
 
   // ToolbarActionViewController:
@@ -101,6 +104,15 @@
   bool HasBeenBlockedForTesting(content::WebContents* web_contents) const;
 
  private:
+  // New instances should be instantiated with Create().
+  ExtensionActionViewController(
+      scoped_refptr<const extensions::Extension> extension,
+      Browser* browser,
+      extensions::ExtensionAction* extension_action,
+      extensions::ExtensionRegistry* extension_registry,
+      ExtensionsContainer* extensions_container,
+      bool in_overflow_mode);
+
   // ExtensionActionIconFactory::Observer:
   void OnIconUpdated() override;
 
diff --git a/chrome/browser/ui/find_bar/find_bar_controller.cc b/chrome/browser/ui/find_bar/find_bar_controller.cc
index d2012291..9b2a72a0 100644
--- a/chrome/browser/ui/find_bar/find_bar_controller.cc
+++ b/chrome/browser/ui/find_bar/find_bar_controller.cc
@@ -111,7 +111,8 @@
         find_in_page::FindTabHelper::FromWebContents(web_contents_);
     if (find_tab_helper) {
       find_tab_helper->set_selected_range(find_bar_->GetSelectedRange());
-      find_tab_observer_.Remove(find_tab_helper);
+      DCHECK(find_tab_observation_.IsObservingSource(find_tab_helper));
+      find_tab_observation_.Reset();
     }
   }
 
@@ -121,7 +122,7 @@
           ? find_in_page::FindTabHelper::FromWebContents(web_contents_)
           : nullptr;
   if (find_tab_helper)
-    find_tab_observer_.Add(find_tab_helper);
+    find_tab_observation_.Observe(find_tab_helper);
 
   // Hide any visible find window from the previous tab if a NULL tab contents
   // is passed in or if the find UI is not active in the new tab.
diff --git a/chrome/browser/ui/find_bar/find_bar_controller.h b/chrome/browser/ui/find_bar/find_bar_controller.h
index e67c028f..bc8d820 100644
--- a/chrome/browser/ui/find_bar/find_bar_controller.h
+++ b/chrome/browser/ui/find_bar/find_bar_controller.h
@@ -9,7 +9,7 @@
 #include <string>
 
 #include "base/macros.h"
-#include "base/scoped_observer.h"
+#include "base/scoped_observation.h"
 #include "chrome/browser/ui/find_bar/find_bar_platform_helper.h"
 #include "components/find_in_page/find_result_observer.h"
 #include "components/find_in_page/find_tab_helper.h"
@@ -109,8 +109,9 @@
   // replacing user-entered text with selection.
   bool has_user_modified_text_ = false;
 
-  ScopedObserver<find_in_page::FindTabHelper, find_in_page::FindResultObserver>
-      find_tab_observer_{this};
+  base::ScopedObservation<find_in_page::FindTabHelper,
+                          find_in_page::FindResultObserver>
+      find_tab_observation_{this};
 
   DISALLOW_COPY_AND_ASSIGN(FindBarController);
 };
diff --git a/chrome/browser/ui/global_error/global_error_browsertest.cc b/chrome/browser/ui/global_error/global_error_browsertest.cc
index 2662c14..283365bd 100644
--- a/chrome/browser/ui/global_error/global_error_browsertest.cc
+++ b/chrome/browser/ui/global_error/global_error_browsertest.cc
@@ -9,6 +9,7 @@
 #include "base/files/file_util.h"
 #include "base/path_service.h"
 #include "base/run_loop.h"
+#include "base/scoped_observation.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
@@ -85,7 +86,7 @@
  public:
   explicit GlobalErrorWaiter(Profile* profile)
       : service_(GlobalErrorServiceFactory::GetForProfile(profile)) {
-    scoped_observer_.Add(service_);
+    scoped_observation_.Observe(service_);
   }
 
   ~GlobalErrorWaiter() override = default;
@@ -101,8 +102,8 @@
  private:
   base::RunLoop run_loop_;
   GlobalErrorService* service_;
-  ScopedObserver<GlobalErrorService, GlobalErrorObserver> scoped_observer_{
-      this};
+  base::ScopedObservation<GlobalErrorService, GlobalErrorObserver>
+      scoped_observation_{this};
 
   DISALLOW_COPY_AND_ASSIGN(GlobalErrorWaiter);
 };
diff --git a/chrome/browser/ui/global_error/global_error_waiter.cc b/chrome/browser/ui/global_error/global_error_waiter.cc
index 74e2eb6..cc34a2c 100644
--- a/chrome/browser/ui/global_error/global_error_waiter.cc
+++ b/chrome/browser/ui/global_error/global_error_waiter.cc
@@ -8,7 +8,8 @@
 namespace test {
 
 GlobalErrorWaiter::GlobalErrorWaiter(Profile* profile) {
-  scoped_observer_.Add(GlobalErrorServiceFactory::GetForProfile(profile));
+  scoped_observation_.Observe(
+      GlobalErrorServiceFactory::GetForProfile(profile));
 }
 
 GlobalErrorWaiter::~GlobalErrorWaiter() = default;
diff --git a/chrome/browser/ui/global_error/global_error_waiter.h b/chrome/browser/ui/global_error/global_error_waiter.h
index 00ae5f8..81a4c5ae 100644
--- a/chrome/browser/ui/global_error/global_error_waiter.h
+++ b/chrome/browser/ui/global_error/global_error_waiter.h
@@ -7,7 +7,7 @@
 
 #include "base/macros.h"
 #include "base/run_loop.h"
-#include "base/scoped_observer.h"
+#include "base/scoped_observation.h"
 #include "chrome/browser/ui/global_error/global_error_observer.h"
 #include "chrome/browser/ui/global_error/global_error_service.h"
 
@@ -32,8 +32,8 @@
  private:
   bool errors_changed_ = false;
   base::RunLoop run_loop_;
-  ScopedObserver<GlobalErrorService, GlobalErrorObserver> scoped_observer_{
-      this};
+  base::ScopedObservation<GlobalErrorService, GlobalErrorObserver>
+      scoped_observation_{this};
 
   DISALLOW_COPY_AND_ASSIGN(GlobalErrorWaiter);
 };
diff --git a/chrome/browser/ui/hung_plugin_tab_helper.cc b/chrome/browser/ui/hung_plugin_tab_helper.cc
index 6df3e936..f12026c 100644
--- a/chrome/browser/ui/hung_plugin_tab_helper.cc
+++ b/chrome/browser/ui/hung_plugin_tab_helper.cc
@@ -130,8 +130,8 @@
 
   if (!infobar_service)
     return;
-  if (!infobar_observer_.IsObserving(infobar_service))
-    infobar_observer_.Add(infobar_service);
+  if (!infobar_observations_.IsObservingSource(infobar_service))
+    infobar_observations_.AddObservation(infobar_service);
 
   std::u16string plugin_name =
       content::PluginService::GetInstance()->GetPluginDisplayNameByPath(
@@ -163,7 +163,7 @@
 
 void HungPluginTabHelper::OnManagerShuttingDown(
     infobars::InfoBarManager* manager) {
-  infobar_observer_.Remove(manager);
+  infobar_observations_.RemoveObservation(manager);
 }
 
 void HungPluginTabHelper::KillPlugin(int child_id) {
diff --git a/chrome/browser/ui/hung_plugin_tab_helper.h b/chrome/browser/ui/hung_plugin_tab_helper.h
index e6718eb8..48684c5 100644
--- a/chrome/browser/ui/hung_plugin_tab_helper.h
+++ b/chrome/browser/ui/hung_plugin_tab_helper.h
@@ -10,7 +10,7 @@
 #include <string>
 
 #include "base/macros.h"
-#include "base/scoped_observer.h"
+#include "base/scoped_multi_source_observation.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "components/infobars/core/infobar_manager.h"
@@ -71,8 +71,9 @@
   // All currently hung plugins.
   std::map<int, std::unique_ptr<PluginState>> hung_plugins_;
 
-  ScopedObserver<infobars::InfoBarManager, infobars::InfoBarManager::Observer>
-      infobar_observer_{this};
+  base::ScopedMultiSourceObservation<infobars::InfoBarManager,
+                                     infobars::InfoBarManager::Observer>
+      infobar_observations_{this};
 
   WEB_CONTENTS_USER_DATA_KEY_DECL();
 
diff --git a/chrome/browser/ui/serial/serial_chooser_controller.cc b/chrome/browser/ui/serial/serial_chooser_controller.cc
index c385d115..421935d 100644
--- a/chrome/browser/ui/serial/serial_chooser_controller.cc
+++ b/chrome/browser/ui/serial/serial_chooser_controller.cc
@@ -42,7 +42,7 @@
 
   chooser_context_->GetPortManager()->GetDevices(base::BindOnce(
       &SerialChooserController::OnGetDevices, weak_factory_.GetWeakPtr()));
-  observer_.Add(chooser_context_.get());
+  observation_.Observe(chooser_context_.get());
 }
 
 SerialChooserController::~SerialChooserController() {
@@ -152,7 +152,7 @@
 }
 
 void SerialChooserController::OnPortManagerConnectionError() {
-  observer_.RemoveAll();
+  observation_.Reset();
 }
 
 void SerialChooserController::OnGetDevices(
diff --git a/chrome/browser/ui/serial/serial_chooser_controller.h b/chrome/browser/ui/serial/serial_chooser_controller.h
index 7a8491f..7674330 100644
--- a/chrome/browser/ui/serial/serial_chooser_controller.h
+++ b/chrome/browser/ui/serial/serial_chooser_controller.h
@@ -10,7 +10,7 @@
 
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
-#include "base/scoped_observer.h"
+#include "base/scoped_observation.h"
 #include "chrome/browser/chooser_controller/chooser_controller.h"
 #include "chrome/browser/serial/serial_chooser_context.h"
 #include "content/public/browser/serial_chooser.h"
@@ -63,11 +63,11 @@
   const int frame_tree_node_id_;
 
   base::WeakPtr<SerialChooserContext> chooser_context_;
-  ScopedObserver<SerialChooserContext,
-                 SerialChooserContext::PortObserver,
-                 &SerialChooserContext::AddPortObserver,
-                 &SerialChooserContext::RemovePortObserver>
-      observer_{this};
+  base::ScopedObservation<SerialChooserContext,
+                          SerialChooserContext::PortObserver,
+                          &SerialChooserContext::AddPortObserver,
+                          &SerialChooserContext::RemovePortObserver>
+      observation_{this};
 
   std::vector<device::mojom::SerialPortInfoPtr> ports_;
 
diff --git a/chrome/browser/ui/signin_reauth_view_controller.cc b/chrome/browser/ui/signin_reauth_view_controller.cc
index f97849154..8488f37 100644
--- a/chrome/browser/ui/signin_reauth_view_controller.cc
+++ b/chrome/browser/ui/signin_reauth_view_controller.cc
@@ -114,7 +114,8 @@
 }
 
 void SigninReauthViewController::OnModalSigninClosed() {
-  dialog_delegate_observer_.Remove(dialog_delegate_);
+  DCHECK(dialog_delegate_observation_.IsObservingSource(dialog_delegate_));
+  dialog_delegate_observation_.Reset();
   dialog_delegate_ = nullptr;
 
   DCHECK(ui_state_ == UIState::kConfirmationDialog ||
@@ -215,7 +216,8 @@
   }
 
   if (dialog_delegate_) {
-    dialog_delegate_observer_.Remove(dialog_delegate_);
+    DCHECK(dialog_delegate_observation_.IsObservingSource(dialog_delegate_));
+    dialog_delegate_observation_.Reset();
     dialog_delegate_->CloseModalSignin();
     dialog_delegate_ = nullptr;
   }
@@ -307,7 +309,7 @@
   dialog_delegate_ =
       SigninViewControllerDelegate::CreateReauthConfirmationDelegate(
           browser_, account_id_, access_point_);
-  dialog_delegate_observer_.Add(dialog_delegate_);
+  dialog_delegate_observation_.Observe(dialog_delegate_);
 
   SigninReauthUI* web_dialog_ui = dialog_delegate_->GetWebContents()
                                       ->GetWebUI()
@@ -340,7 +342,8 @@
   ui_state_ = UIState::kGaiaReauthTab;
   // Remove the observer to not trigger OnModalSigninClosed() that will abort
   // the reauth flow.
-  dialog_delegate_observer_.Remove(dialog_delegate_);
+  DCHECK(dialog_delegate_observation_.IsObservingSource(dialog_delegate_));
+  dialog_delegate_observation_.Reset();
   dialog_delegate_->CloseModalSignin();
   dialog_delegate_ = nullptr;
 
diff --git a/chrome/browser/ui/signin_reauth_view_controller.h b/chrome/browser/ui/signin_reauth_view_controller.h
index b256ea7..9a6fb84 100644
--- a/chrome/browser/ui/signin_reauth_view_controller.h
+++ b/chrome/browser/ui/signin_reauth_view_controller.h
@@ -9,7 +9,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list_types.h"
 #include "base/optional.h"
-#include "base/scoped_observer.h"
+#include "base/scoped_observation.h"
 #include "base/time/time.h"
 #include "chrome/browser/ui/signin_view_controller_delegate.h"
 #include "components/signin/public/base/signin_metrics.h"
@@ -188,9 +188,9 @@
 
   // Delegate displaying the dialog.
   SigninViewControllerDelegate* dialog_delegate_ = nullptr;
-  ScopedObserver<SigninViewControllerDelegate,
-                 SigninViewControllerDelegate::Observer>
-      dialog_delegate_observer_{this};
+  base::ScopedObservation<SigninViewControllerDelegate,
+                          SigninViewControllerDelegate::Observer>
+      dialog_delegate_observation_{this};
 
   // WebContents of the Gaia reauth page.
   std::unique_ptr<content::WebContents> reauth_web_contents_;
diff --git a/chrome/browser/ui/signin_view_controller.cc b/chrome/browser/ui/signin_view_controller.cc
index 93e76ad..cb14c5c4 100644
--- a/chrome/browser/ui/signin_view_controller.cc
+++ b/chrome/browser/ui/signin_view_controller.cc
@@ -219,7 +219,7 @@
   // is closed.
   delegate_ = new SigninReauthViewController(
       browser_, account_id, access_point, std::move(wrapped_reauth_callback));
-  delegate_observer_.Add(delegate_);
+  delegate_observation_.Observe(delegate_);
   chrome::RecordDialogCreation(chrome::DialogIdentifier::SIGNIN_REAUTH);
   return abort_handle;
 }
@@ -231,7 +231,7 @@
   // is closed.
   delegate_ =
       SigninViewControllerDelegate::CreateSyncConfirmationDelegate(browser_);
-  delegate_observer_.Add(delegate_);
+  delegate_observation_.Observe(delegate_);
   chrome::RecordDialogCreation(
       chrome::DialogIdentifier::SIGN_IN_SYNC_CONFIRMATION);
 }
@@ -241,7 +241,7 @@
   // The delegate will delete itself on request of the UI code when the widget
   // is closed.
   delegate_ = SigninViewControllerDelegate::CreateSigninErrorDelegate(browser_);
-  delegate_observer_.Add(delegate_);
+  delegate_observation_.Observe(delegate_);
   chrome::RecordDialogCreation(chrome::DialogIdentifier::SIGN_IN_ERROR);
 }
 
@@ -262,7 +262,8 @@
 }
 
 void SigninViewController::OnModalSigninClosed() {
-  delegate_observer_.Remove(delegate_);
+  DCHECK(delegate_observation_.IsObservingSource(delegate_));
+  delegate_observation_.Reset();
   delegate_ = nullptr;
 }
 
@@ -412,7 +413,7 @@
   delegate_ = SigninEmailConfirmationDialog::AskForConfirmation(
       active_contents, browser_->profile(), last_email, email,
       std::move(callback));
-  delegate_observer_.Add(delegate_);
+  delegate_observation_.Observe(delegate_);
   chrome::RecordDialogCreation(
       chrome::DialogIdentifier::SIGN_IN_EMAIL_CONFIRMATION);
 }
diff --git a/chrome/browser/ui/signin_view_controller.h b/chrome/browser/ui/signin_view_controller.h
index ddbd83b..14d290a1 100644
--- a/chrome/browser/ui/signin_view_controller.h
+++ b/chrome/browser/ui/signin_view_controller.h
@@ -10,7 +10,7 @@
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
-#include "base/scoped_observer.h"
+#include "base/scoped_observation.h"
 #include "build/build_config.h"
 #include "chrome/browser/ui/profile_chooser_constants.h"
 #include "chrome/browser/ui/signin_view_controller_delegate.h"
@@ -169,9 +169,9 @@
   // |delegate_| owns itself and calls OnModalSigninClosed() before being
   // destroyed.
   SigninViewControllerDelegate* delegate_ = nullptr;
-  ScopedObserver<SigninViewControllerDelegate,
-                 SigninViewControllerDelegate::Observer>
-      delegate_observer_{this};
+  base::ScopedObservation<SigninViewControllerDelegate,
+                          SigninViewControllerDelegate::Observer>
+      delegate_observation_{this};
 
   base::WeakPtrFactory<SigninViewController> weak_ptr_factory_{this};
 
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_model.cc b/chrome/browser/ui/toolbar/toolbar_actions_model.cc
index 5f00853f..930289e 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_model.cc
+++ b/chrome/browser/ui/toolbar/toolbar_actions_model.cc
@@ -153,40 +153,6 @@
   }
 }
 
-std::vector<std::unique_ptr<ToolbarActionViewController>>
-ToolbarActionsModel::CreateActions(Browser* browser,
-                                   ExtensionsContainer* main_bar,
-                                   bool in_overflow_mode) {
-  DCHECK(browser);
-  DCHECK(main_bar);
-  std::vector<std::unique_ptr<ToolbarActionViewController>> action_list;
-
-  for (const ActionId& action_id : action_ids_) {
-    action_list.push_back(
-        CreateActionForId(browser, main_bar, in_overflow_mode, action_id));
-  }
-
-  return action_list;
-}
-
-std::unique_ptr<ToolbarActionViewController>
-ToolbarActionsModel::CreateActionForId(Browser* browser,
-                                       ExtensionsContainer* main_bar,
-                                       bool in_overflow_mode,
-                                       const ActionId& action_id) {
-  // We should never have uninitialized actions in action_ids().
-  DCHECK(!action_id.empty());
-  // Get the extension.
-  const extensions::Extension* extension = GetExtensionById(action_id);
-  DCHECK(extension);
-
-  // Create and add an ExtensionActionViewController for the extension.
-  return std::make_unique<ExtensionActionViewController>(
-      extension, browser,
-      extension_action_manager_->GetExtensionAction(*extension), main_bar,
-      in_overflow_mode);
-}
-
 void ToolbarActionsModel::OnExtensionLoaded(
     content::BrowserContext* browser_context,
     const extensions::Extension* extension) {
@@ -521,7 +487,7 @@
   // proper order insertion), holes can be present if there isn't an action
   // for each id. This is handled below when we add the actions to
   // |action_ids_| to ensure that there are never any holes in
-  // |action_ids_| itself (or, relatedly, CreateActions()).
+  // |action_ids_| itself.
   for (const ActionId& action : all_actions) {
     std::vector<ActionId>::const_iterator pos = std::find(
         last_known_positions_.begin(), last_known_positions_.end(), action);
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_model.h b/chrome/browser/ui/toolbar/toolbar_actions_model.h
index 1c49f56..36dfee1 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_model.h
+++ b/chrome/browser/ui/toolbar/toolbar_actions_model.h
@@ -27,7 +27,6 @@
 class PrefService;
 class Profile;
 class ExtensionsContainer;
-class ToolbarActionViewController;
 
 namespace extensions {
 class ExtensionActionManager;
@@ -128,16 +127,6 @@
 
   bool actions_initialized() const { return actions_initialized_; }
 
-  std::vector<std::unique_ptr<ToolbarActionViewController>> CreateActions(
-      Browser* browser,
-      ExtensionsContainer* main_bar,
-      bool in_overflow_menu);
-  std::unique_ptr<ToolbarActionViewController> CreateActionForId(
-      Browser* browser,
-      ExtensionsContainer* main_bar,
-      bool in_overflow_menu,
-      const ActionId& action_id);
-
   const std::vector<ActionId>& action_ids() const { return action_ids_; }
 
   bool has_active_bubble() const { return has_active_bubble_; }
diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views_unittest.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views_unittest.cc
index 8d6b1e2..002cbc21 100644
--- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views_unittest.cc
+++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views_unittest.cc
@@ -123,6 +123,9 @@
     if (browser) {
       browser->tab_strip_model()->CloseAllTabs();
       delete browser;
+      // Browser holds a ScopedProfileKeepAlive, which might post a task to the
+      // UI thread on destruction.
+      base::RunLoop().RunUntilIdle();
     }
     extension_environment_.DeleteProfile();
 
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_view.cc b/chrome/browser/ui/views/extensions/extensions_menu_view.cc
index 3affd5a1..a514f66e 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_view.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_view.cc
@@ -10,6 +10,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/chrome_pages.h"
+#include "chrome/browser/ui/extensions/extension_action_view_controller.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
 #include "chrome/browser/ui/views/bubble_menu_item_factory.h"
@@ -286,9 +287,14 @@
 
 void ExtensionsMenuView::CreateAndInsertNewItem(
     const ToolbarActionsModel::ActionId& id) {
-  std::unique_ptr<ToolbarActionViewController> controller =
-      toolbar_model_->CreateActionForId(browser_, extensions_container_, false,
-                                        id);
+  // For the extensions menu UI, we pretend the the overflow menu isn't
+  // "overflow", because the UI shouldn't react differently.
+  // TODO(https://crbug.com/1197766): Remove the is_in_overflow_menu bool
+  // entirely.
+  constexpr bool kIsInOverflowMenu = false;
+  std::unique_ptr<ExtensionActionViewController> controller =
+      ExtensionActionViewController::Create(id, browser_, extensions_container_,
+                                            kIsInOverflowMenu);
 
   // The bare `new` is safe here, because InsertMenuItem is guaranteed to
   // be added to the view hierarchy, which takes ownership.
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
index 4842e02..ed64ddd 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
@@ -8,6 +8,7 @@
 #include "build/build_config.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/extensions/extension_action_view_controller.h"
 #include "chrome/browser/ui/extensions/settings_api_bubble_helpers.h"
 #include "chrome/browser/ui/layout_constants.h"
 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
@@ -501,8 +502,9 @@
 
 void ExtensionsToolbarContainer::CreateActionForId(
     const ToolbarActionsModel::ActionId& action_id) {
-  actions_.push_back(
-      model_->CreateActionForId(browser_, this, false, action_id));
+  constexpr bool kIsInOverflowMenu = false;
+  actions_.push_back(ExtensionActionViewController::Create(
+      action_id, browser_, this, kIsInOverflowMenu));
   auto icon = std::make_unique<ToolbarActionView>(actions_.back().get(), this);
   // Set visibility before adding to prevent extraneous animation.
   icon->SetVisible(CanShowIconInToolbar() && model_->IsActionPinned(action_id));
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.h b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.h
index 1963b498..c95075b8 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.h
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.h
@@ -63,6 +63,10 @@
  private:
   FRIEND_TEST_ALL_PREFIXES(BrowserNonClientFrameViewMacTest,
                            GetCenteredTitleBounds);
+  FRIEND_TEST_ALL_PREFIXES(BrowserNonClientFrameViewMacTest,
+                           GetWebAppFrameToolbarAvailableBounds);
+  FRIEND_TEST_ALL_PREFIXES(BrowserNonClientFrameViewMacTest,
+                           GetCaptionButtonPlaceholderBounds);
 
   static gfx::Rect GetCenteredTitleBounds(int frame_width,
                                           int frame_height,
@@ -70,6 +74,17 @@
                                           int right_inset_x,
                                           int title_width);
 
+  static gfx::Rect GetWebAppFrameToolbarAvailableBounds(
+      bool is_rtl,
+      const gfx::Size& frame,
+      int y,
+      int caption_button_container_width);
+  static gfx::Rect GetCaptionButtonPlaceholderBounds(bool is_rtl,
+                                                     const gfx::Size& frame,
+                                                     int y,
+                                                     int width,
+                                                     int extra_padding);
+
   void PaintThemedFrame(gfx::Canvas* canvas);
 
   CGFloat FullscreenBackingBarHeight() const;
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm
index 6c2a889..60eb3bf 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm
@@ -36,10 +36,10 @@
 #include "ui/base/hit_test.h"
 #include "ui/base/theme_provider.h"
 #include "ui/gfx/canvas.h"
-#include "ui/gfx/scoped_canvas.h"
 
 namespace {
 
+constexpr int kFrameExtraPaddingForWindowControlsOverlay = 10;
 constexpr int kFramePaddingLeft = 75;
 // Keep in sync with web_app_frame_toolbar_browsertest.cc
 constexpr double kTitlePaddingWidthFraction = 0.1;
@@ -467,38 +467,73 @@
       window_title_->CalculatePreferredSize().width()));
 }
 
+gfx::Rect BrowserNonClientFrameViewMac::GetWebAppFrameToolbarAvailableBounds(
+    bool is_rtl,
+    const gfx::Size& frame,
+    int y,
+    int caption_button_container_width) {
+  if (is_rtl) {
+    return gfx::Rect(0, 0, frame.width() - caption_button_container_width,
+                     frame.height());
+  } else {
+    return gfx::Rect(caption_button_container_width, 0,
+                     frame.width() - caption_button_container_width,
+                     frame.height());
+  }
+}
+
+gfx::Rect BrowserNonClientFrameViewMac::GetCaptionButtonPlaceholderBounds(
+    bool is_rtl,
+    const gfx::Size& frame,
+    int y,
+    int width,
+    int extra_padding) {
+  if (is_rtl)
+    return gfx::Rect(frame.width() - width, y, width, frame.height());
+  else {
+    // Add extra width to caption_button_placeholder_container_overlay_ so the
+    // maximize button does not look like it is touching the border of the
+    // overlay and there is padding between the two.
+    return gfx::Rect(0, y, width + extra_padding, frame.height());
+  }
+}
+
 void BrowserNonClientFrameViewMac::LayoutWindowControlsOverlay() {
-  // Add extra width to caption_button_placeholder_container_overlay_ so the
-  // maximize button does not look like it is touching the border of the overlay
-  // and there is padding between the two.
-  int caption_button_placeholder_container_overlay_width =
-      kFramePaddingLeft + 10;
+  const bool is_rtl = CaptionButtonsOnLeadingEdge() && base::i18n::IsRTL();
+  const gfx::Size frame(width(), GetTopInset(false));
+  gfx::Rect caption_button_container_bounds = GetCaptionButtonPlaceholderBounds(
+      is_rtl, frame, 0, kFramePaddingLeft,
+      kFrameExtraPaddingForWindowControlsOverlay);
+  gfx::Rect web_app_frame_toolbar_available_bounds =
+      GetWebAppFrameToolbarAvailableBounds(
+          is_rtl, frame, 0, caption_button_container_bounds.width());
 
   // Layout CaptionButtonDummyContainerMac which would have the traffic lights.
-  auto caption_button_placeholder_container_overlay_bounds =
-      gfx::Rect(0, 0, caption_button_placeholder_container_overlay_width,
-                GetTopInset(false));
   caption_button_placeholder_container_->LayoutForWindowControlsOverlay(
-      caption_button_placeholder_container_overlay_bounds);
+      caption_button_container_bounds);
 
   // Layout WebAppFrameToolbarView.
-  const int web_app_toolbar_width =
-      web_app_frame_toolbar()->GetPreferredSize().width();
-  const int web_app_toolbar_x = width() - web_app_toolbar_width;
-  auto available_space = gfx::Rect(web_app_toolbar_x, 0, web_app_toolbar_width,
-                                   GetTopInset(false));
-
-  web_app_frame_toolbar()->LayoutForWindowControlsOverlay(available_space);
+  web_app_frame_toolbar()->LayoutForWindowControlsOverlay(
+      web_app_frame_toolbar_available_bounds);
 
   content::WebContents* web_contents = browser_view()->GetActiveWebContents();
   // WebContents can be null when an app window is first launched.
   if (web_contents) {
-    int overlay_width =
-        width() - (caption_button_placeholder_container_overlay_width +
-                   web_app_toolbar_width);
-    auto bounding_rect =
-        gfx::Rect(caption_button_placeholder_container_overlay_width, 0,
-                  overlay_width, GetTopInset(false));
+    const int overlay_width =
+        width() - (caption_button_placeholder_container_->size().width() +
+                   web_app_frame_toolbar()->size().width());
+    gfx::Rect bounding_rect;
+
+    if (CaptionButtonsOnLeadingEdge() && base::i18n::IsRTL()) {
+      bounding_rect =
+          gfx::Rect(caption_button_placeholder_container_->size().width() +
+                        web_app_frame_toolbar()->size().width(),
+                    0, overlay_width, GetTopInset(false));
+    } else {
+      bounding_rect = GetMirroredRect(
+          gfx::Rect(caption_button_placeholder_container_->size().width(), 0,
+                    overlay_width, GetTopInset(false)));
+    }
     web_contents->UpdateWindowControlsOverlay(bounding_rect);
   }
 }
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac_unittest.mm b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac_unittest.mm
index fc3786c2..1c21c28 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac_unittest.mm
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac_unittest.mm
@@ -40,3 +40,48 @@
     index++;
   }
 }
+
+TEST(BrowserNonClientFrameViewMacTest, GetCaptionButtonPlaceholderBounds) {
+  const gfx::Size frame(800, 40);
+  const int width = 75;
+  const int y = 0;
+  const int extra_padding = 10;
+
+  const gfx::Rect ltr_bounds =
+      BrowserNonClientFrameViewMac::GetCaptionButtonPlaceholderBounds(
+          false /* is_rtl */, frame, y, 75, extra_padding);
+  const gfx::Rect expected_ltr_bounds = gfx::Rect(0, 0, 85, 40);
+
+  EXPECT_EQ(ltr_bounds, expected_ltr_bounds);
+
+  const gfx::Rect rtl_bounds =
+      BrowserNonClientFrameViewMac::GetCaptionButtonPlaceholderBounds(
+          true /* is_rtl */, frame, y, width, extra_padding);
+  const gfx::Rect expected_rtl_bounds =
+      gfx::Rect(frame.width() - width, y, width, frame.height());
+
+  EXPECT_EQ(rtl_bounds, expected_rtl_bounds);
+}
+
+TEST(BrowserNonClientFrameViewMacTest, GetWebAppFrameToolbarAvailableBounds) {
+  const gfx::Size frame(800, 40);
+  const int y = 0;
+  const int caption_button_container_width = 75;
+
+  const gfx::Rect ltr_available_bounds =
+      BrowserNonClientFrameViewMac::GetWebAppFrameToolbarAvailableBounds(
+          false /* is_rtl */, frame, y, caption_button_container_width);
+  const gfx::Rect expected_ltr_available_bounds =
+      gfx::Rect(caption_button_container_width, y,
+                frame.width() - caption_button_container_width, frame.height());
+
+  EXPECT_EQ(ltr_available_bounds, expected_ltr_available_bounds);
+
+  const gfx::Rect rtl_available_bounds =
+      BrowserNonClientFrameViewMac::GetWebAppFrameToolbarAvailableBounds(
+          true /* is_rtl */, frame, y, caption_button_container_width);
+  const gfx::Rect expected_rtl_available_bounds = gfx::Rect(
+      0, y, frame.width() - caption_button_container_width, frame.height());
+
+  EXPECT_EQ(rtl_available_bounds, expected_rtl_available_bounds);
+}
\ No newline at end of file
diff --git a/chrome/browser/ui/views/frame/contents_web_view.cc b/chrome/browser/ui/views/frame/contents_web_view.cc
index 2d61d3a2..ebc96bf 100644
--- a/chrome/browser/ui/views/frame/contents_web_view.cc
+++ b/chrome/browser/ui/views/frame/contents_web_view.cc
@@ -136,7 +136,8 @@
   // is now the new parent of the cloned layer). Convert coordinates so that the
   // cloned layer appears at the right location.
   gfx::PointF origin;
-  ui::Layer::ConvertPointToLayer(cloned_layer_tree_->root(), layer(), &origin);
+  ui::Layer::ConvertPointToLayer(cloned_layer_tree_->root(), layer(),
+                                 /*use_target_transform=*/true, &origin);
   cloned_layer_tree_->root()->SetBounds(
       gfx::Rect(gfx::ToFlooredPoint(origin),
                 cloned_layer_tree_->root()->bounds().size()));
diff --git a/chrome/browser/ui/webui/chromeos/arc_graphics_tracing/OWNER b/chrome/browser/ui/webui/chromeos/arc_graphics_tracing/OWNER
new file mode 100644
index 0000000..f35d9cb9
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/arc_graphics_tracing/OWNER
@@ -0,0 +1,2 @@
+alanding@chromium.org
+khmel@chromium.org
diff --git a/chrome/browser/ui/webui/chromeos/edu_coexistence/edu_coexistence_login_handler_chromeos.cc b/chrome/browser/ui/webui/chromeos/edu_coexistence/edu_coexistence_login_handler_chromeos.cc
index 38bfdf36..89302e3d 100644
--- a/chrome/browser/ui/webui/chromeos/edu_coexistence/edu_coexistence_login_handler_chromeos.cc
+++ b/chrome/browser/ui/webui/chromeos/edu_coexistence/edu_coexistence_login_handler_chromeos.cc
@@ -273,7 +273,7 @@
   base::Value params(base::Value::Type::DICTIONARY);
 
   const std::string& app_locale = g_browser_process->GetApplicationLocale();
-  params.SetStringKey("h1", app_locale);
+  params.SetStringKey("hl", app_locale);
 
   params.SetStringKey("url", GetEduCoexistenceURL());
 
diff --git a/chrome/browser/ui/webui/settings/chromeos/internet_section.cc b/chrome/browser/ui/webui/settings/chromeos/internet_section.cc
index 4f0d387c..867cac38 100644
--- a/chrome/browser/ui/webui/settings/chromeos/internet_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/internet_section.cc
@@ -727,6 +727,8 @@
        IDS_SETTINGS_INTERNET_SHOW_EID_POPUP_BUTTON_LABEL},
       {"eSimNoConnectionErrorToast",
        IDS_SETTINGS_INTERNET_ESIM_NO_CONNECTION_ERROR_TOAST},
+      {"eSimMobileDataNotEnabledErrorToast",
+       IDS_SETTINGS_INTERNET_ESIM_MOBILE_DATA_NOT_ENABLED_ERROR_TOAST},
       {"eSimInstallErrorDialogTitle",
        IDS_SETTINGS_INTERNET_NETWORK_INSTALL_ERROR_DIALOG_TITLE},
       {"eSimInstallErrorDialogConfirmationCodeMessage",
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 3c7fea5..5aa4f37 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-master-1619611159-fd3f76bf749c862ea82431a71981186448e4aafc.profdata
+chrome-mac-master-1619632776-bf9f30743a253033347f774818e9c95aee4c9cab.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 2b48cba..34fe5d2c 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-master-1619621900-b500fbfb23f00e485ee02981949b64bc088db137.profdata
+chrome-win64-master-1619632776-63b16d2daf336d315e60534a62a375f8b10fdde6.profdata
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/tests/webapk/MapsGoFirstRunTest.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/tests/webapk/MapsGoFirstRunTest.java
index 381981e75..d21af25 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/tests/webapk/MapsGoFirstRunTest.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/tests/webapk/MapsGoFirstRunTest.java
@@ -20,16 +20,14 @@
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.ActivityState;
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.Log;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
-import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.DisabledTest;
 import org.chromium.chrome.browser.firstrun.FirstRunActivity;
 import org.chromium.chrome.browser.firstrun.FirstRunStatus;
+import org.chromium.chrome.browser.firstrun.FirstRunUtils;
 import org.chromium.chrome.browser.firstrun.LightweightFirstRunActivity;
 import org.chromium.chrome.browser.webapps.WebappActivity;
 import org.chromium.chrome.browser.webapps.WebappRegistry;
@@ -70,17 +68,16 @@
     private Activity mFirstRunActivity;
     private Activity mWebappActivity;
     private ApplicationStatus.ActivityStateListener mActivityStateListener;
-    private final CallbackHelper mFreStoppedCallback = new CallbackHelper();
 
     @Before
     public void setUp() {
         WebApkValidator.setDisableValidationForTesting(true);
+        FirstRunUtils.setDisableDelayOnExitFreForTest(true);
         TestThreadUtils.runOnUiThreadBlocking(WebappRegistry::refreshSharedPrefsForTesting);
 
         mActivityStateListener = (activity, newState) -> {
             if (activity instanceof LightweightFirstRunActivity) {
                 if (mLightweightFreActivity == null) mLightweightFreActivity = activity;
-                if (newState == ActivityState.STOPPED) mFreStoppedCallback.notifyCalled();
             } else if (activity instanceof FirstRunActivity) {
                 if (mFirstRunActivity == null) mFirstRunActivity = activity;
             } else if (activity instanceof WebappActivity) {
@@ -92,6 +89,8 @@
 
     @After
     public void tearDown() {
+        WebApkValidator.setDisableValidationForTesting(false);
+        FirstRunUtils.setDisableDelayOnExitFreForTest(false);
         LightweightFirstRunActivity.setSupportSkippingTos(true);
         ApplicationStatus.unregisterActivityStateListener(mActivityStateListener);
     }
@@ -108,8 +107,7 @@
         controller.acceptAndContinue();
         // Note for offline devices this PWA will not be healthy, see https://crbug.com/1142821 for
         // details. Just verify the right activity has started.
-        CriteriaHelper.pollInstrumentationThread(
-                () -> mWebappActivity != null, "WebappActivity did not start.");
+        verifyWebappActivityStarted();
     }
 
     @Test
@@ -118,6 +116,7 @@
         // shown instead.
         WebApkValidator.setDisableValidationForTesting(false);
         launchWebapk("org.chromium.test.maps_go_webapk", "org.chromium.chrome");
+
         CriteriaHelper.pollInstrumentationThread(
                 () -> mFirstRunActivity != null, "FirstRunActivity did not start");
         Assert.assertNull("Lightweight FRE should not have started.", mLightweightFreActivity);
@@ -129,10 +128,8 @@
         FirstRunStatus.setLightweightFirstRunFlowComplete(true);
         launchWebapk("org.chromium.test.maps_go_webapk", "org.chromium.chrome");
 
-        LightWeightTOSController controller = LightWeightTOSController.getInstance();
-        Assert.assertFalse(
-                "Light weight TOS page should NOT be shown.", controller.isCurrentPageThis());
-        Assert.assertNull("Lightweight FRE should not launch.", mLightweightFreActivity);
+        verifyWebappActivityStarted();
+        Assert.assertNull("Lightweight FRE should not have started.", mLightweightFreActivity);
     }
 
     @Test
@@ -142,17 +139,16 @@
         FirstRunStatus.setLightweightFirstRunFlowComplete(false);
         launchWebapk("org.chromium.test.maps_go_webapk", "org.chromium.chrome");
 
-        Assert.assertNotNull("Lightweight FRE should launch.", mLightweightFreActivity);
-
-        LightWeightTOSController controller = LightWeightTOSController.getInstance();
-        Assert.assertFalse(
-                "Light weight TOS page should NOT be shown.", controller.isCurrentPageThis());
-        mFreStoppedCallback.waitForCallback("Lightweight Fre never completes.", 0);
+        // Verify LWFRE activity is created before skipped to WebappActivity. See
+        // https://crbug.com/1184149 for previous problems here.
+        CriteriaHelper.pollInstrumentationThread(()
+                                                         -> mLightweightFreActivity != null,
+                "Lightweight FRE should still launch before being skipped.");
+        verifyWebappActivityStarted();
     }
 
     @Test
     @CommandLineFlags.Add({FLAG_POLICY_TOS_DIALOG_BEHAVIOR_STANDARD})
-    @DisabledTest(message = "https://crbug.com/1184149")
     public void testTosNotSkippedByPolicy() {
         LightweightFirstRunActivity.setSupportSkippingTos(true);
         FirstRunStatus.setLightweightFirstRunFlowComplete(false);
@@ -186,4 +182,11 @@
         IUi2Locator packageLocator = Ui2Locators.withPackageName(chromePackageName);
         helper.verifyOnScreen(packageLocator);
     }
+
+    private void verifyWebappActivityStarted() {
+        CriteriaHelper.pollInstrumentationThread(()
+                                                         -> mWebappActivity != null,
+                "WebappActivity did not start.", MAPS_GO_FRE_TIMEOUT_MS,
+                CriteriaHelper.DEFAULT_POLLING_INTERVAL);
+    }
 }
diff --git a/chrome/test/data/webui/chromeos/scanning/scanning_app_browsertest.js b/chrome/test/data/webui/chromeos/scanning/scanning_app_browsertest.js
index 2046ef9..82c4442 100644
--- a/chrome/test/data/webui/chromeos/scanning/scanning_app_browsertest.js
+++ b/chrome/test/data/webui/chromeos/scanning/scanning_app_browsertest.js
@@ -34,7 +34,12 @@
   browsePreload: 'chrome://scanning/test_loader.html?module=chromeos/' +
       'scanning/scanning_app_unified_test.js',
 
-  featureList: {enabled: ['chromeos::features::kScanAppMediaLink']},
+  featureList: {
+    enabled: [
+      'chromeos::features::kScanAppMediaLink',
+      'chromeos::features::kScanAppStickySettings',
+    ]
+  },
 };
 
 // List of names of suites in unified test to register for individual debugging.
diff --git a/chrome/test/data/webui/chromeos/scanning/scanning_app_test.js b/chrome/test/data/webui/chromeos/scanning/scanning_app_test.js
index e791eb4..73d904fc 100644
--- a/chrome/test/data/webui/chromeos/scanning/scanning_app_test.js
+++ b/chrome/test/data/webui/chromeos/scanning/scanning_app_test.js
@@ -12,7 +12,7 @@
 import {ScanningBrowserProxyImpl} from 'chrome://scanning/scanning_browser_proxy.js';
 
 import {assertArrayEquals, assertEquals, assertFalse, assertTrue} from '../../chai_assert.js';
-import {flushTasks, isVisible} from '../../test_util.m.js';
+import {flushTasks, isVisible, waitAfterNextRender} from '../../test_util.m.js';
 
 import {changeSelect, createScanner, createScannerSource} from './scanning_app_test_utils.js';
 import {TestScanningBrowserProxy} from './test_scanning_browser_proxy.js';
@@ -327,7 +327,9 @@
 
   teardown(function() {
     fakeScanService_.resetForTest();
-    scanningApp.remove();
+    if (scanningApp) {
+      scanningApp.remove();
+    }
     scanningApp = null;
     scannerSelect = null;
     sourceSelect = null;
@@ -413,6 +415,16 @@
     return scanningApp.$$('#collapse').opened;
   }
 
+  /**
+   * Fetches capabilities then waits for app to change to READY state.
+   * @return {!Promise}
+   */
+  function getScannerCapabilities() {
+    return fakeScanService_.whenCalled('getScannerCapabilities').then(() => {
+      return waitAfterNextRender(/** @type {!HTMLElement} */ (scanningApp));
+    });
+  }
+
   // Verify a full scan job can be completed.
   test('Scan', () => {
     /** @type {!Array<!mojoBase.mojom.FilePath>} */
@@ -440,7 +452,7 @@
           progressText = scanningApp.$$('#scanPreview').$$('#progressText');
           progressBar = scanningApp.$$('#scanPreview').$$('paper-progress');
           scannedImages = scanningApp.$$('#scanPreview').$$('#scannedImages');
-          return fakeScanService_.whenCalled('getScannerCapabilities');
+          return getScannerCapabilities();
         })
         .then(() => {
           assertEquals(
@@ -581,7 +593,7 @@
         .then(() => {
           scanButton =
               /** @type {!CrButtonElement} */ (scanningApp.$$('#scanButton'));
-          return fakeScanService_.whenCalled('getScannerCapabilities');
+          return getScannerCapabilities();
         })
         .then(() => {
           // Click the Scan button and wait till the scan is started.
@@ -617,7 +629,7 @@
   test('ScanResults', () => {
     return initializeScanningApp(expectedScanners, capabilities)
         .then(() => {
-          return fakeScanService_.whenCalled('getScannerCapabilities');
+          return getScannerCapabilities();
         })
         .then(() => {
           scanButton =
@@ -715,7 +727,7 @@
               /** @type {!CrButtonElement} */ (scanningApp.$$('#scanButton'));
           cancelButton =
               /** @type {!CrButtonElement} */ (scanningApp.$$('#cancelButton'));
-          return fakeScanService_.whenCalled('getScannerCapabilities');
+          return getScannerCapabilities();
         })
         .then(() => {
           // Before the scan button is clicked, the scan button should be
@@ -777,7 +789,7 @@
               /** @type {!CrButtonElement} */ (scanningApp.$$('#scanButton'));
           cancelButton =
               /** @type {!CrButtonElement} */ (scanningApp.$$('#cancelButton'));
-          return fakeScanService_.whenCalled('getScannerCapabilities');
+          return getScannerCapabilities();
         })
         .then(() => {
           // Click the Scan button and wait till the scan is started.
@@ -830,7 +842,7 @@
         .then(() => {
           scanButton =
               /** @type {!CrButtonElement} */ (scanningApp.$$('#scanButton'));
-          return fakeScanService_.whenCalled('getScannerCapabilities');
+          return getScannerCapabilities();
         })
         .then(() => {
           assertFalse(scanningApp.$$('#toast').open);
@@ -871,7 +883,7 @@
   test('MoreSettingsToggle', () => {
     return initializeScanningApp(expectedScanners, capabilities)
         .then(() => {
-          return fakeScanService_.whenCalled('getScannerCapabilities');
+          return getScannerCapabilities();
         })
         .then(() => {
           // Verify that expandable section is closed by default.
@@ -928,7 +940,7 @@
     testBrowserProxy.setExpectedNumScanSettingChanges(0);
     return initializeScanningApp(expectedScanners, capabilities)
         .then(() => {
-          return fakeScanService_.whenCalled('getScannerCapabilities');
+          return getScannerCapabilities();
         })
         .then(() => {
           scanningApp.$$('#scanButton').click();
@@ -941,7 +953,7 @@
     testBrowserProxy.setExpectedNumScanSettingChanges(2);
     return initializeScanningApp(expectedScanners, capabilities)
         .then(() => {
-          return fakeScanService_.whenCalled('getScannerCapabilities');
+          return getScannerCapabilities();
         })
         .then(() => {
           return changeSelect(
@@ -964,7 +976,7 @@
     testBrowserProxy.setExpectedNumScanSettingChanges(3);
     return initializeScanningApp(expectedScanners, capabilities)
         .then(() => {
-          return fakeScanService_.whenCalled('getScannerCapabilities');
+          return getScannerCapabilities();
         })
         .then(() => {
           return changeSelect(
@@ -995,7 +1007,7 @@
   test('DefaultScanSettings', () => {
     return initializeScanningApp(expectedScanners, capabilities)
         .then(() => {
-          return fakeScanService_.whenCalled('getScannerCapabilities');
+          return getScannerCapabilities();
         })
         .then(() => {
           assertEquals(
@@ -1025,7 +1037,7 @@
   test('DefaultScanSettingsNotAvailable', () => {
     return initializeScanningApp(expectedScanners.slice(1), capabilities)
         .then(() => {
-          return fakeScanService_.whenCalled('getScannerCapabilities');
+          return getScannerCapabilities();
         })
         .then(() => {
           assertEquals(
@@ -1049,4 +1061,146 @@
               '600', scanningApp.$$('#resolutionSelect').$$('select').value);
         });
   });
+
+  // Verify the default scan settings are used when saved settings are not
+  // available for the selected scanner.
+  test('SavedSettingsNotAvailable', () => {
+    const savedScanSettings = {
+      lastUsedScannerName: 'Wrong Scanner',
+      scanToPath: 'scan/to/path',
+      scanners: [{
+        name: 'Wrong Scanner',
+        lastScanDate: new Date(),
+        sourceName: ADF_DUPLEX,
+        fileType: ash.scanning.mojom.FileType.kPng,
+        colorMode: ash.scanning.mojom.ColorMode.kGrayscale,
+        pageSize: ash.scanning.mojom.PageSize.kMax,
+        resolutionDpi: 100,
+      }],
+    };
+    testBrowserProxy.setSavedSettings(JSON.stringify(savedScanSettings));
+
+    return initializeScanningApp(expectedScanners, capabilities)
+        .then(() => {
+          return getScannerCapabilities();
+        })
+        .then(() => {
+          assertEquals(
+              tokenToString(firstScannerId),
+              scanningApp.$$('#scannerSelect').$$('select').value);
+          assertEquals(
+              PLATEN, scanningApp.$$('#sourceSelect').$$('select').value);
+          assertEquals(
+              loadTimeData.getString('myFilesSelectOption'),
+              scanningApp.$$('#scanToSelect').$$('select').value);
+          assertEquals(
+              ash.scanning.mojom.FileType.kPdf.toString(),
+              scanningApp.$$('#fileTypeSelect').$$('select').value);
+          assertEquals(
+              ash.scanning.mojom.ColorMode.kColor.toString(),
+              scanningApp.$$('#colorModeSelect').$$('select').value);
+          assertEquals(
+              ash.scanning.mojom.PageSize.kNaLetter.toString(),
+              scanningApp.$$('#pageSizeSelect').$$('select').value);
+          assertEquals(
+              '300', scanningApp.$$('#resolutionSelect').$$('select').value);
+        });
+  });
+
+  // Verify saved settings are applied when available for the selected scanner.
+  test('ApplySavedSettings', () => {
+    if (!loadTimeData.getBoolean('scanAppStickySettingsEnabled')) {
+      return;
+    }
+
+    const savedScanSettings = {
+      lastUsedScannerName: firstScannerName,
+      scanToPath: 'scan/to/path',
+      scanners: [{
+        name: firstScannerName,
+        lastScanDate: new Date(),
+        sourceName: ADF_DUPLEX,
+        fileType: ash.scanning.mojom.FileType.kPng,
+        colorMode: ash.scanning.mojom.ColorMode.kBlackAndWhite,
+        pageSize: ash.scanning.mojom.PageSize.kMax,
+        resolutionDpi: 75,
+      }],
+    };
+    testBrowserProxy.setSavedSettings(JSON.stringify(savedScanSettings));
+
+    return initializeScanningApp(expectedScanners, capabilities)
+        .then(() => {
+          return getScannerCapabilities();
+        })
+        .then(() => {
+          assertEquals(
+              tokenToString(firstScannerId),
+              scanningApp.$$('#scannerSelect').$$('select').value);
+          assertEquals(
+              ADF_DUPLEX, scanningApp.$$('#sourceSelect').$$('select').value);
+          assertEquals(
+              loadTimeData.getString('myFilesSelectOption'),
+              scanningApp.$$('#scanToSelect').$$('select').value);
+          assertEquals(
+              ash.scanning.mojom.FileType.kPng.toString(),
+              scanningApp.$$('#fileTypeSelect').$$('select').value);
+          assertEquals(
+              ash.scanning.mojom.ColorMode.kBlackAndWhite.toString(),
+              scanningApp.$$('#colorModeSelect').$$('select').value);
+          assertEquals(
+              ash.scanning.mojom.PageSize.kMax.toString(),
+              scanningApp.$$('#pageSizeSelect').$$('select').value);
+          assertEquals(
+              '75', scanningApp.$$('#resolutionSelect').$$('select').value);
+        });
+  });
+
+  // Verify if the setting value stored in saved settings is no longer
+  // available on the selected scanner, the default setting is chosen.
+  test('SettingNotFoundInCapabilities', () => {
+    if (!loadTimeData.getBoolean('scanAppStickySettingsEnabled')) {
+      return;
+    }
+
+    const savedScanSettings = {
+      lastUsedScannerName: firstScannerName,
+      scanToPath: 'scan/to/path',
+      scanners: [{
+        name: firstScannerName,
+        lastScanDate: new Date(),
+        sourceName: ADF_SIMPLEX,
+        fileType: -1,
+        colorMode: ash.scanning.mojom.ColorMode.kGrayscale,
+        pageSize: -1,
+        resolutionDpi: 600,
+      }],
+    };
+    testBrowserProxy.setSavedSettings(JSON.stringify(savedScanSettings));
+
+    return initializeScanningApp(expectedScanners, capabilities)
+        .then(() => {
+          return getScannerCapabilities();
+        })
+        .then(() => {
+          assertEquals(
+              tokenToString(firstScannerId),
+              scanningApp.$$('#scannerSelect').$$('select').value);
+          assertEquals(
+              PLATEN, scanningApp.$$('#sourceSelect').$$('select').value);
+          assertEquals(
+              loadTimeData.getString('myFilesSelectOption'),
+              scanningApp.$$('#scanToSelect').$$('select').value);
+          assertEquals(
+              ash.scanning.mojom.FileType.kPdf.toString(),
+              scanningApp.$$('#fileTypeSelect').$$('select').value);
+          assertEquals(
+              ash.scanning.mojom.ColorMode.kColor.toString(),
+              scanningApp.$$('#colorModeSelect').$$('select').value);
+          assertEquals(
+              ash.scanning.mojom.PageSize.kNaLetter.toString(),
+              scanningApp.$$('#pageSizeSelect').$$('select').value);
+          assertEquals(
+              '300', scanningApp.$$('#resolutionSelect').$$('select').value);
+        });
+  });
 }
diff --git a/chrome/test/data/webui/chromeos/scanning/test_scanning_browser_proxy.js b/chrome/test/data/webui/chromeos/scanning/test_scanning_browser_proxy.js
index 964f8857..1d5df8c 100644
--- a/chrome/test/data/webui/chromeos/scanning/test_scanning_browser_proxy.js
+++ b/chrome/test/data/webui/chromeos/scanning/test_scanning_browser_proxy.js
@@ -23,6 +23,8 @@
       'openFilesInMediaApp',
       'recordScanCompleteAction',
       'recordNumScanSettingChanges',
+      'saveScanSettings',
+      'getScanSettings',
     ]);
 
     /** @private {?SelectedPath} */
@@ -39,6 +41,9 @@
 
     /** @private {number} */
     this.expectedNumScanSettingChanges_ = 0;
+
+    /** @private {string} */
+    this.savedSettings_ = '';
   }
 
   /** @override */
@@ -108,6 +113,15 @@
     assertEquals(this.expectedNumScanSettingChanges_, numChanges);
   }
 
+  /** @override */
+  saveScanSettings(scanSettings) {}
+
+  /** @override */
+  getScanSettings() {
+    this.methodCalled('getScanSettings');
+    return Promise.resolve(this.savedSettings_);
+  }
+
   /** @param {!SelectedPath} selectedPath */
   setSelectedPath(selectedPath) {
     this.selectedPath_ = selectedPath;
@@ -128,6 +142,11 @@
     this.filePaths_ = filePaths;
   }
 
+  /** @param {string} savedSettings */
+  setSavedSettings(savedSettings) {
+    this.savedSettings_ = savedSettings;
+  }
+
   /** @param {number} numChanges */
   setExpectedNumScanSettingChanges(numChanges) {
     this.expectedNumScanSettingChanges_ = numChanges;
diff --git a/chrome/test/data/webui/cr_elements/cr_input_test.js b/chrome/test/data/webui/cr_elements/cr_input_test.js
index 0dadd440..066d1896 100644
--- a/chrome/test/data/webui/cr_elements/cr_input_test.js
+++ b/chrome/test/data/webui/cr_elements/cr_input_test.js
@@ -331,6 +331,20 @@
     assertFalse(input.checkValidity());
   });
 
+  test('ariaDescriptionsCorrect', function() {
+    assertEquals(crInput.inputElement.getAttribute('aria-description'), null);
+
+    const ariaDescription = 'description';
+    crInput.ariaDescription = ariaDescription;
+    flush();
+    assertEquals(
+        crInput.inputElement.getAttribute('aria-description'), ariaDescription);
+
+    crInput.ariaDescription = undefined;
+    flush();
+    assertEquals(crInput.inputElement.getAttribute('aria-description'), null);
+  });
+
   test('ariaLabelsCorrect', function() {
     assertFalse(!!crInput.inputElement.getAttribute('aria-label'));
 
diff --git a/chrome/test/data/webui/settings/chromeos/internet_page_tests.js b/chrome/test/data/webui/settings/chromeos/internet_page_tests.js
index 28d99dd..c5e0de2c 100644
--- a/chrome/test/data/webui/settings/chromeos/internet_page_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/internet_page_tests.js
@@ -64,6 +64,31 @@
     mojoApi_.addNetworksForTest(networks);
   }
 
+  /**
+   * @param {boolean} showPSimFlow
+   * @param {boolean} isCellularEnabled
+   * @return {!Promise<function()>}
+   */
+  function navigateToCellularSetupDialog(showPSimFlow, isCellularEnabled) {
+    const params = new URLSearchParams;
+    params.append('guid', 'cellular_guid');
+    params.append('type', 'Cellular');
+    params.append('name', 'cellular');
+    params.append('showCellularSetup', 'true');
+    if (showPSimFlow) {
+      params.append('showPsimFlow', 'true');
+    }
+    settings.Router.getInstance().navigateTo(
+        settings.routes.INTERNET_NETWORKS, params);
+
+    // Update the device state here to trigger an
+    // attemptShowCellularSetupDialog_() call.
+    mojoApi_.setNetworkTypeEnabledState(
+        chromeos.networkConfig.mojom.NetworkType.kCellular, isCellularEnabled);
+
+    return flushAsync();
+  }
+
   setup(function() {
     PolymerTest.clearBody();
     internetPage = document.createElement('settings-internet-page');
@@ -319,22 +344,14 @@
         loadTimeData.overrideValues({
           updatedCellularActivationUi: true,
         });
-        eSimManagerRemote.addEuiccForTest(1);
         await flushAsync();
 
         let cellularSetupDialog = internetPage.$$('#cellularSetupDialog');
         assertFalse(!!cellularSetupDialog);
 
-        const params = new URLSearchParams;
-        params.append('guid', 'cellular_guid');
-        params.append('type', 'Cellular');
-        params.append('name', 'cellular');
-        params.append('showCellularSetup', 'true');
-        params.append('showPsimFlow', 'true');
-        settings.Router.getInstance().navigateTo(
-            settings.routes.INTERNET_NETWORKS, params);
+        await navigateToCellularSetupDialog(
+            /*showPSimFlow=*/ true, /*isCellularEnabled=*/ true);
 
-        await flushAsync();
         cellularSetupDialog = internetPage.$$('#cellularSetupDialog');
         assertTrue(!!cellularSetupDialog);
         const psimFlow =
@@ -345,8 +362,8 @@
 
   test(
       'Show eSIM flow cellular setup dialog if route params' +
-          'contains showCellularSetup, does not contain showPsimFlow, and' +
-          'connected to a non-cellular network',
+          'contains showCellularSetup, does not contain showPsimFlow,' +
+          'connected to a non-cellular network, and cellular enabled',
       async function() {
         loadTimeData.overrideValues({
           updatedCellularActivationUi: true,
@@ -363,15 +380,9 @@
         let cellularSetupDialog = internetPage.$$('#cellularSetupDialog');
         assertFalse(!!cellularSetupDialog);
 
-        const params = new URLSearchParams;
-        params.append('guid', 'cellular_guid');
-        params.append('type', 'Cellular');
-        params.append('name', 'cellular');
-        params.append('showCellularSetup', 'true');
-        settings.Router.getInstance().navigateTo(
-            settings.routes.INTERNET_NETWORKS, params);
+        await navigateToCellularSetupDialog(
+            /*showPSimFlow=*/ false, /*isCellularEnabled=*/ true);
 
-        await flushAsync();
         cellularSetupDialog = internetPage.$$('#cellularSetupDialog');
         assertTrue(!!cellularSetupDialog);
         const esimFlow =
@@ -383,7 +394,7 @@
   test(
       'Show no connection toast if route params' +
           'contain showCellularSetup, does not contain showPsimFlow,' +
-          'but not connected to a non-cellular network',
+          'cellular is enabled, but not connected to a non-cellular network',
       async function() {
         loadTimeData.overrideValues({
           updatedCellularActivationUi: true,
@@ -392,16 +403,42 @@
 
         assertFalse(!!internetPage.$$('#cellularSetupDialog'));
 
-        const params = new URLSearchParams;
-        params.append('guid', 'cellular_guid');
-        params.append('type', 'Cellular');
-        params.append('name', 'cellular');
-        params.append('showCellularSetup', 'true');
-        settings.Router.getInstance().navigateTo(
-            settings.routes.INTERNET_NETWORKS, params);
+        await navigateToCellularSetupDialog(
+            /*showPSimFlow=*/ false, /*isCellularEnabled=*/ true);
 
-        await flushAsync();
         assertTrue(internetPage.$.errorToast.open);
+        assertEquals(
+            internetPage.$.errorToastMessage.innerHTML,
+            internetPage.i18n('eSimNoConnectionErrorToast'));
+        assertFalse(!!internetPage.$$('#cellularSetupDialog'));
+      });
+
+  test(
+      'Show mobile data not enabled toast if route params' +
+          'contains showCellularSetup, does not contain showPsimFlow,' +
+          'connected to a non-cellular network, but cellular not enabled',
+      async function() {
+        loadTimeData.overrideValues({
+          updatedCellularActivationUi: true,
+        });
+        eSimManagerRemote.addEuiccForTest(1);
+
+        const mojom = chromeos.networkConfig.mojom;
+        const wifiNetwork =
+            OncMojo.getDefaultNetworkState(mojom.NetworkType.kWiFi, 'wifi');
+        wifiNetwork.connectionState = mojom.ConnectionStateType.kOnline;
+        mojoApi_.addNetworksForTest([wifiNetwork]);
+        await flushAsync();
+
+        assertFalse(!!internetPage.$$('#cellularSetupDialog'));
+
+        await navigateToCellularSetupDialog(
+            /*showPSimFlow=*/ false, /*isCellularEnabled=*/ false);
+
+        assertTrue(internetPage.$.errorToast.open);
+        assertEquals(
+            internetPage.$.errorToastMessage.innerHTML,
+            internetPage.i18n('eSimMobileDataNotEnabledErrorToast'));
         assertFalse(!!internetPage.$$('#cellularSetupDialog'));
       });
 
@@ -434,6 +471,10 @@
       'Show no connection toast if receive show-cellular-setup' +
           'event and not connected to non-cellular network',
       async function() {
+        mojoApi_.setNetworkTypeEnabledState(
+            chromeos.networkConfig.mojom.NetworkType.kCellular, true);
+        await flushAsync();
+
         assertFalse(internetPage.$.errorToast.open);
 
         // Send event, toast should show, dialog hidden.
@@ -443,6 +484,9 @@
         internetPage.dispatchEvent(event);
         await flushAsync();
         assertTrue(internetPage.$.errorToast.open);
+        assertEquals(
+            internetPage.$.errorToastMessage.innerHTML,
+            internetPage.i18n('eSimNoConnectionErrorToast'));
         assertFalse(!!internetPage.$$('#cellularSetupDialog'));
 
         // Hide the toast
diff --git a/chrome/test/data/webui/settings/chromeos/network_summary_item_test.js b/chrome/test/data/webui/settings/chromeos/network_summary_item_test.js
index 3a7b7dd..4ad0b8f 100644
--- a/chrome/test/data/webui/settings/chromeos/network_summary_item_test.js
+++ b/chrome/test/data/webui/settings/chromeos/network_summary_item_test.js
@@ -157,6 +157,37 @@
     assertFalse(doesElementExist('.subpage-arrow'));
   });
 
+  test('Click event in SIMinfo should not trigger show details', function() {
+    const mojom = chromeos.networkConfig.mojom;
+
+    let showDetailFired = false;
+    netSummaryItem.addEventListener(
+        'show-detail', () => showDetailFired = true);
+
+    netSummaryItem.setProperties({
+      isUpdatedCellularUiEnabled_: false,
+      deviceState: {
+        deviceState: mojom.DeviceStateType.kEnabled,
+        type: mojom.NetworkType.kCellular,
+        simAbsent: false,
+        simLockStatus: {lockType: 'sim-pin'},
+      },
+      activeNetworkState: {
+        connectionState: mojom.ConnectionStateType.kNotConnected,
+        guid: 'test_guid',
+        type: mojom.NetworkType.kCellular,
+        typeState: {cellular: {networkTechnology: ''}}
+      },
+    });
+    Polymer.dom.flush();
+    assertTrue(doesElementExist('network-siminfo'));
+
+    const networkSimInfo = netSummaryItem.$$('network-siminfo');
+    networkSimInfo.click();
+    Polymer.dom.flush();
+    assertFalse(showDetailFired);
+  });
+
   test('Inhibited device on cellular network, flag on', function() {
     const mojom = chromeos.networkConfig.mojom;
 
diff --git a/chrome/utility/chrome_content_utility_client.cc b/chrome/utility/chrome_content_utility_client.cc
index 5b660b83..03dff4f 100644
--- a/chrome/utility/chrome_content_utility_client.cc
+++ b/chrome/utility/chrome_content_utility_client.cc
@@ -11,6 +11,8 @@
 #include "base/command_line.h"
 #include "base/files/file_path.h"
 #include "base/lazy_instance.h"
+#include "base/path_service.h"
+#include "chrome/common/chrome_paths.h"
 #include "chrome/common/profiler/thread_profiler.h"
 #include "chrome/common/profiler/thread_profiler_configuration.h"
 #include "chrome/utility/browser_exposed_utility_interfaces.h"
@@ -114,6 +116,11 @@
   return ::RegisterIOThreadServices(services);
 }
 
+bool ChromeContentUtilityClient::GetDefaultUserDataDirectory(
+    base::FilePath* path) {
+  return base::PathService::Get(chrome::DIR_USER_DATA, path);
+}
+
 // static
 void ChromeContentUtilityClient::SetNetworkBinderCreationCallback(
     NetworkBinderCreationCallback callback) {
diff --git a/chrome/utility/chrome_content_utility_client.h b/chrome/utility/chrome_content_utility_client.h
index bf1c94e..6e5ff03 100644
--- a/chrome/utility/chrome_content_utility_client.h
+++ b/chrome/utility/chrome_content_utility_client.h
@@ -35,6 +35,7 @@
   void UtilityThreadStarted() override;
   void RegisterMainThreadServices(mojo::ServiceFactory& services) override;
   void RegisterIOThreadServices(mojo::ServiceFactory& services) override;
+  bool GetDefaultUserDataDirectory(base::FilePath* path) override;
 
   // See NetworkBinderProvider above.
   static void SetNetworkBinderCreationCallback(
diff --git a/chromeos/assistant/tools/send-audio.sh b/chromeos/assistant/tools/send-audio.sh
index d82703c4..d583f2fd 100755
--- a/chromeos/assistant/tools/send-audio.sh
+++ b/chromeos/assistant/tools/send-audio.sh
@@ -50,7 +50,7 @@
     echo "Performing text-to-speech"
     http --download "https://www.google.com/speech-api/v1/synthesize?lang=en&text=$text" --output $MP3_FILE --body
     echo "Converting to $OUTPUT_FILE"
-    avconv -y -i $MP3_FILE -acodec pcm_s16le -f s16le -ar 16000 $OUTPUT_FILE \
+    ffmpeg -y -i $MP3_FILE -acodec pcm_s16le -f s16le -ar 16000 $OUTPUT_FILE \
          -loglevel error -hide_banner
 
     rm $MP3_FILE
diff --git a/chromeos/components/diagnostics_ui/DEPS b/chromeos/components/diagnostics_ui/DEPS
index 2975b46..415b9f99 100644
--- a/chromeos/components/diagnostics_ui/DEPS
+++ b/chromeos/components/diagnostics_ui/DEPS
@@ -3,6 +3,7 @@
   "-chrome",
   "+chromeos/grit/chromeos_diagnostics_app_resources.h",
   "+chromeos/strings/grit/chromeos_strings.h",
+  "+components",
   "+content/public/browser",
   "+content/public/test",
   "+services/data_decoder/public",
diff --git a/chromeos/components/diagnostics_ui/backend/BUILD.gn b/chromeos/components/diagnostics_ui/backend/BUILD.gn
index 80a1065..611603f 100644
--- a/chromeos/components/diagnostics_ui/backend/BUILD.gn
+++ b/chromeos/components/diagnostics_ui/backend/BUILD.gn
@@ -81,10 +81,14 @@
     "//chromeos/dbus/cros_healthd",
     "//chromeos/dbus/power",
     "//chromeos/dbus/power:power_manager_proto",
+    "//chromeos/login/login_state:login_state",
     "//chromeos/services/cros_healthd/public/cpp",
     "//chromeos/services/cros_healthd/public/mojom",
     "//chromeos/services/network_config/public/cpp:test_support",
     "//chromeos/services/network_config/public/mojom",
+    "//components/onc",
+    "//components/prefs:test_support",
+    "//components/sync_preferences:test_support",
     "//content/test:test_support",
     "//services/data_decoder/public/cpp:test_support",
     "//services/device/public/cpp:test_support",
diff --git a/chromeos/components/diagnostics_ui/backend/network_health_provider.cc b/chromeos/components/diagnostics_ui/backend/network_health_provider.cc
index aaf5b3d7..e0978e5 100644
--- a/chromeos/components/diagnostics_ui/backend/network_health_provider.cc
+++ b/chromeos/components/diagnostics_ui/backend/network_health_provider.cc
@@ -7,6 +7,8 @@
 #include <string>
 #include <utility>
 
+#include "base/bind.h"
+#include "base/containers/contains.h"
 #include "chromeos/services/network_config/in_process_instance.h"
 #include "chromeos/services/network_config/public/cpp/cros_network_config_util.h"
 
@@ -34,6 +36,12 @@
 
 }  // namespace
 
+NetworkProperties::NetworkProperties(
+    network_mojom::NetworkStatePropertiesPtr network_state)
+    : network_state(std::move(network_state)) {}
+
+NetworkProperties::~NetworkProperties() = default;
+
 NetworkHealthProvider::NetworkHealthProvider() {
   network_config::BindToInProcessInstance(
       remote_cros_network_config_.BindNewPipeAndPassReceiver());
@@ -63,10 +71,14 @@
 
 void NetworkHealthProvider::OnActiveNetworkStateListReceived(
     std::vector<network_mojom::NetworkStatePropertiesPtr> networks) {
-  guid_to_network_map_.clear();
+  network_properties_map_.clear();
   for (auto& network : networks) {
     if (IsSupportedNetworkType(network->type)) {
-      guid_to_network_map_[network->guid] = std::move(network);
+      const std::string guid = mojo::Clone(network->guid);
+      network_properties_map_.emplace(guid, std::move(network));
+      // This method depends on the |network_properties_map_| being populated
+      // before being called.
+      GetManagedPropertiesForNetwork(guid);
     }
   }
   // TODO(michaelcheco): Call Mojo API here.
@@ -84,7 +96,8 @@
 
 std::vector<std::string> NetworkHealthProvider::GetNetworkGuidListForTesting() {
   std::vector<std::string> network_guids;
-  for (const auto& entry : guid_to_network_map_) {
+  network_guids.reserve(network_properties_map_.size());
+  for (const auto& entry : network_properties_map_) {
     network_guids.push_back(entry.first);
   }
   return network_guids;
@@ -94,5 +107,30 @@
   return device_type_map_;
 }
 
+const NetworkPropertiesMap&
+NetworkHealthProvider::GetNetworkPropertiesMapForTesting() {
+  return network_properties_map_;
+}
+
+void NetworkHealthProvider::GetManagedPropertiesForNetwork(
+    const std::string& guid) {
+  remote_cros_network_config_->GetManagedProperties(
+      guid, base::BindOnce(&NetworkHealthProvider::OnManagedPropertiesReceived,
+                           base::Unretained(this), guid));
+}
+
+void NetworkHealthProvider::OnManagedPropertiesReceived(
+    const std::string& guid,
+    network_mojom::ManagedPropertiesPtr managed_properties) {
+  if (!managed_properties) {
+    DVLOG(1) << "No managed properties found for guid: " << guid;
+    return;
+  }
+  // Add managed properties to corresponding NetworkProperties struct.
+  DCHECK(base::Contains(network_properties_map_, guid));
+  auto network_props_iter = network_properties_map_.find(guid);
+  network_props_iter->second.managed_properties = std::move(managed_properties);
+}
+
 }  // namespace diagnostics
 }  // namespace chromeos
diff --git a/chromeos/components/diagnostics_ui/backend/network_health_provider.h b/chromeos/components/diagnostics_ui/backend/network_health_provider.h
index cd2ce8a..7c3e169 100644
--- a/chromeos/components/diagnostics_ui/backend/network_health_provider.h
+++ b/chromeos/components/diagnostics_ui/backend/network_health_provider.h
@@ -15,6 +15,19 @@
 
 namespace chromeos {
 namespace diagnostics {
+// Stores network state, managed properties, and an observer for a network.
+// TODO(michaelcheco): Use NetworkProperties to construct a mojo::Network
+// struct and send it to its corresponding observer.
+struct NetworkProperties {
+  explicit NetworkProperties(
+      chromeos::network_config::mojom::NetworkStatePropertiesPtr network_state);
+  ~NetworkProperties();
+  chromeos::network_config::mojom::NetworkStatePropertiesPtr network_state;
+  chromeos::network_config::mojom::ManagedPropertiesPtr managed_properties;
+  // TODO(michaelcheco): Add NetworkStateObserver as a member of this struct.
+};
+
+using NetworkPropertiesMap = std::map<std::string, NetworkProperties>;
 
 using DeviceMap = std::map<network_config::mojom::NetworkType,
                            network_config::mojom::DeviceStatePropertiesPtr>;
@@ -44,6 +57,8 @@
 
   const DeviceMap& GetDeviceTypeMapForTesting();
 
+  const NetworkPropertiesMap& GetNetworkPropertiesMapForTesting();
+
  private:
   // Handler for receiving a list of active networks.
   void OnActiveNetworkStateListReceived(
@@ -53,10 +68,17 @@
   void OnDeviceStateListReceived(
       std::vector<network_config::mojom::DeviceStatePropertiesPtr> devices);
 
+  // Handler for receiving managed properties for a network.
+  void OnManagedPropertiesReceived(
+      const std::string& guid,
+      network_config::mojom::ManagedPropertiesPtr managed_properties);
+
+  // Gets ManagedProperties for a network |guid| from CrosNetworkConfig.
+  void GetManagedPropertiesForNetwork(const std::string& guid);
+
   // Map of networks that are active and of a supported
   // type (Ethernet, WiFi, Cellular).
-  std::map<std::string, network_config::mojom::NetworkStatePropertiesPtr>
-      guid_to_network_map_;
+  NetworkPropertiesMap network_properties_map_;
 
   // Maps device type to device properties, used to find corresponding device
   // for a network.
diff --git a/chromeos/components/diagnostics_ui/backend/network_health_provider_unittest.cc b/chromeos/components/diagnostics_ui/backend/network_health_provider_unittest.cc
index 776df24..7fb4249 100644
--- a/chromeos/components/diagnostics_ui/backend/network_health_provider_unittest.cc
+++ b/chromeos/components/diagnostics_ui/backend/network_health_provider_unittest.cc
@@ -5,10 +5,28 @@
 #include "chromeos/components/diagnostics_ui/backend/network_health_provider.h"
 
 #include "base/containers/contains.h"
+#include "base/memory/ptr_util.h"
 #include "base/test/task_environment.h"
+#include "chromeos/login/login_state/login_state.h"
+#include "chromeos/network/managed_network_configuration_handler.h"
+#include "chromeos/network/network_cert_loader.h"
+#include "chromeos/network/network_certificate_handler.h"
+#include "chromeos/network/network_configuration_handler.h"
+#include "chromeos/network/network_device_handler.h"
+#include "chromeos/network/network_profile_handler.h"
+#include "chromeos/network/network_state_test_helper.h"
+#include "chromeos/network/onc/onc_utils.h"
+#include "chromeos/network/proxy/ui_proxy_config_service.h"
+#include "chromeos/network/system_token_cert_db_storage.h"
 #include "chromeos/services/network_config/public/cpp/cros_network_config_test_helper.h"
 #include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
 #include "chromeos/services/network_config/public/mojom/network_types.mojom-shared.h"
+#include "components/onc/onc_constants.h"
+#include "components/onc/onc_pref_names.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/proxy_config/pref_proxy_config_tracker_impl.h"
+#include "components/proxy_config/proxy_config_pref_names.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
 
@@ -16,113 +34,163 @@
 namespace diagnostics {
 namespace {
 
-// Values for fake devices and services.
-constexpr char kEthServicePath[] = "/service/eth/0";
-constexpr char kEthServiceName[] = "eth_service_name";
-constexpr char kEthGuid[] = "eth_guid";
-constexpr char kEthDevicePath[] = "/device/eth1";
-constexpr char kEthName[] = "eth_name";
-constexpr char kWifiDevicePath[] = "/device/wifi1";
-constexpr char kWifiGuid[] = "wifi_guid";
-constexpr char kWifiName[] = "wifi_name";
-constexpr char kVPNGuid[] = "vpn_guid";
-constexpr char kVPNName[] = "vpn_name";
+void ValidateManagedPropertiesSet(
+    const NetworkPropertiesMap& network_properties_map,
+    const std::string& guid) {
+  EXPECT_TRUE(base::Contains(network_properties_map, guid));
+  auto network_props_iter_wifi = network_properties_map.find(guid);
+  auto managed_properties_guid =
+      network_props_iter_wifi->second.managed_properties->guid;
+  EXPECT_EQ(managed_properties_guid, guid);
+}
 
 }  // namespace
 
 class NetworkHealthProviderTest : public testing::Test {
  public:
-  NetworkHealthProviderTest() {}
+  NetworkHealthProviderTest() {
+    // Initialize the ManagedNetworkConfigurationHandler and any associated
+    // properties.
+    LoginState::Initialize();
+    SystemTokenCertDbStorage::Initialize();
+    NetworkCertLoader::Initialize();
+    InitializeManagedNetworkConfigurationHandler();
 
-  ~NetworkHealthProviderTest() override = default;
+    cros_network_config_test_helper().Initialize(
+        managed_network_configuration_handler_.get());
+    // Wait until |cros_network_config_test_helper_| has initialized.
+    base::RunLoop().RunUntilIdle();
+    network_health_provider_ = std::make_unique<NetworkHealthProvider>();
+  }
 
-  void SetUp() override {
-    // Wait until CrosNetworkConfigTestHelper is fully setup.
-    task_environment_.RunUntilIdle();
+  ~NetworkHealthProviderTest() override {
+    managed_network_configuration_handler_.reset();
+    ui_proxy_config_service_.reset();
+    network_configuration_handler_.reset();
+    network_profile_handler_.reset();
+    network_health_provider_.reset();
+    LoginState::Shutdown();
+    NetworkCertLoader::Shutdown();
+    SystemTokenCertDbStorage::Shutdown();
+  }
+
+  void InitializeManagedNetworkConfigurationHandler() {
+    network_profile_handler_ = NetworkProfileHandler::InitializeForTesting();
+    network_configuration_handler_ =
+        base::WrapUnique<NetworkConfigurationHandler>(
+            NetworkConfigurationHandler::InitializeForTest(
+                network_state_helper().network_state_handler(),
+                cros_network_config_test_helper().network_device_handler()));
+
+    PrefProxyConfigTrackerImpl::RegisterProfilePrefs(user_prefs_.registry());
+    PrefProxyConfigTrackerImpl::RegisterPrefs(local_state_.registry());
+    ::onc::RegisterProfilePrefs(user_prefs_.registry());
+    ::onc::RegisterPrefs(local_state_.registry());
+
+    ui_proxy_config_service_ = std::make_unique<chromeos::UIProxyConfigService>(
+        &user_prefs_, &local_state_,
+        network_state_helper().network_state_handler(),
+        network_profile_handler_.get());
+
+    managed_network_configuration_handler_ =
+        ManagedNetworkConfigurationHandler::InitializeForTesting(
+            network_state_helper().network_state_handler(),
+            network_profile_handler_.get(),
+            cros_network_config_test_helper().network_device_handler(),
+            network_configuration_handler_.get(),
+            ui_proxy_config_service_.get());
+
+    managed_network_configuration_handler_->SetPolicy(
+        ::onc::ONC_SOURCE_DEVICE_POLICY,
+        /*userhash=*/std::string(),
+        /*network_configs_onc=*/base::ListValue(),
+        /*global_network_config=*/base::DictionaryValue());
+
+    // Wait until the |managed_network_configuration_handler_| is initialized
+    // and set up.
+    base::RunLoop().RunUntilIdle();
+  }
+
+  void SetupWiFiNetwork() {
+    network_state_helper().ConfigureService(
+        R"({"GUID": "wifi1_guid", "Type": "wifi", "State": "ready",
+            "Strength": 50, "AutoConnect": true, "WiFi.HiddenSSID": false})");
+
+    base::RunLoop().RunUntilIdle();
+  }
+
+  void SetupEthernetNetwork() {
+    network_state_helper().device_test()->AddDevice(
+        "/device/stub_eth_device", shill::kTypeEthernet, "stub_eth_device");
+    network_state_helper().ConfigureService(
+        R"({"GUID": "eth_guid", "Type": "ethernet", "State": "online"})");
+
+    base::RunLoop().RunUntilIdle();
+  }
+
+  void SetupVPNNetwork() {
+    network_state_helper().ConfigureService(
+        R"({"GUID": "vpn_guid", "Type": "vpn", "State": "association",
+            "Provider": {"Type": "l2tpipsec"}})");
+    base::RunLoop().RunUntilIdle();
   }
 
  protected:
-  // Adds a Service to the Manager and Service stubs.
-  void AddService(const std::string& service_path,
-                  const std::string& guid,
-                  const std::string& name,
-                  const std::string& type,
-                  const std::string& state,
-                  bool visible) {
-    cros_network_config_test_helper_.network_state_helper()
-        .service_test()
-        ->AddService(service_path, guid, name, type, state, visible);
-
-    task_environment_.RunUntilIdle();
+  network_config::CrosNetworkConfigTestHelper&
+  cros_network_config_test_helper() {
+    return cros_network_config_test_helper_;
   }
 
-  // Adds a device for testing.
-  void AddDevice(const std::string& device_path,
-                 const std::string& type,
-                 const std::string& name) {
-    cros_network_config_test_helper_.network_state_helper()
-        .device_test()
-        ->AddDevice(device_path, type, name);
-
-    task_environment_.RunUntilIdle();
+  chromeos::NetworkStateTestHelper& network_state_helper() {
+    return cros_network_config_test_helper_.network_state_helper();
   }
 
-  void ResetCrosNetworkConfigDevicesAndServices() {
+  void ResetDevicesAndServices() {
     // Clear test devices and services and setup the default wifi device.
-    cros_network_config_test_helper_.network_state_helper()
-        .ResetDevicesAndServices();
+    network_state_helper().ResetDevicesAndServices();
     task_environment_.RunUntilIdle();
   }
 
   base::test::TaskEnvironment task_environment_;
-  network_config::CrosNetworkConfigTestHelper cros_network_config_test_helper_;
-  NetworkHealthProvider network_health_provider_;
+  std::unique_ptr<NetworkProfileHandler> network_profile_handler_;
+  std::unique_ptr<NetworkConfigurationHandler> network_configuration_handler_;
+  std::unique_ptr<ManagedNetworkConfigurationHandler>
+      managed_network_configuration_handler_;
+  std::unique_ptr<UIProxyConfigService> ui_proxy_config_service_;
+  sync_preferences::TestingPrefServiceSyncable user_prefs_;
+  TestingPrefServiceSimple local_state_;
+  network_config::CrosNetworkConfigTestHelper cros_network_config_test_helper_{
+      false};
+  std::unique_ptr<NetworkHealthProvider> network_health_provider_;
 };
 
-TEST_F(NetworkHealthProviderTest, ConnectedNetworkStoredInActiveList) {
-  ResetCrosNetworkConfigDevicesAndServices();
-  AddService(kWifiDevicePath, kWifiGuid, kWifiName, shill::kTypeWifi,
-             shill::kStateOnline, true);
-
-  const std::vector<std::string>& network_guid_list =
-      network_health_provider_.GetNetworkGuidListForTesting();
-  ASSERT_EQ(1u, network_guid_list.size());
-  ASSERT_EQ(kWifiGuid, network_guid_list[0]);
-}
-
 TEST_F(NetworkHealthProviderTest, MultipleConnectedNetworksStoredInActiveList) {
-  ResetCrosNetworkConfigDevicesAndServices();
-  AddService(kWifiDevicePath, kWifiGuid, kWifiName, shill::kTypeWifi,
-             shill::kStateOnline, true);
-  AddDevice(kEthDevicePath, shill::kTypeEthernet, kEthName);
-  AddService(kEthServicePath, kEthGuid, kEthServiceName, shill::kTypeEthernet,
-             shill::kStateReady, true);
+  ResetDevicesAndServices();
+  SetupWiFiNetwork();
+  SetupEthernetNetwork();
 
   const std::vector<std::string>& network_guid_list =
-      network_health_provider_.GetNetworkGuidListForTesting();
+      network_health_provider_->GetNetworkGuidListForTesting();
   ASSERT_EQ(2u, network_guid_list.size());
-  ASSERT_TRUE(base::Contains(network_guid_list, kWifiGuid));
-  ASSERT_TRUE(base::Contains(network_guid_list, kEthGuid));
+  ASSERT_TRUE(base::Contains(network_guid_list, "wifi1_guid"));
+  ASSERT_TRUE(base::Contains(network_guid_list, "eth_guid"));
 }
 
 TEST_F(NetworkHealthProviderTest, UnsupportedNetworkTypeIgnored) {
-  ResetCrosNetworkConfigDevicesAndServices();
-  AddService(kWifiDevicePath, kVPNGuid, kVPNName, shill::kTypeVPN,
-             shill::kStateOffline, true);
-
-  task_environment_.RunUntilIdle();
+  ResetDevicesAndServices();
+  SetupVPNNetwork();
 
   const std::vector<std::string>& network_guid_list =
-      network_health_provider_.GetNetworkGuidListForTesting();
+      network_health_provider_->GetNetworkGuidListForTesting();
   ASSERT_TRUE(network_guid_list.empty());
 }
 
 TEST_F(NetworkHealthProviderTest, SingleSupportedDeviceStoredInDeviceTypeMap) {
-  ResetCrosNetworkConfigDevicesAndServices();
-  AddDevice(kWifiDevicePath, shill::kTypeWifi, kWifiName);
+  ResetDevicesAndServices();
+  SetupWiFiNetwork();
 
   const DeviceMap& device_type_map =
-      network_health_provider_.GetDeviceTypeMapForTesting();
+      network_health_provider_->GetDeviceTypeMapForTesting();
 
   EXPECT_EQ(1U, device_type_map.size());
   EXPECT_TRUE(base::Contains(device_type_map,
@@ -131,12 +199,12 @@
 
 TEST_F(NetworkHealthProviderTest,
        MultipleSupportedDevicesStoredInDeviceTypeMap) {
-  ResetCrosNetworkConfigDevicesAndServices();
-  AddDevice(kEthDevicePath, shill::kTypeEthernet, kEthName);
-  AddDevice(kWifiDevicePath, shill::kTypeWifi, kWifiName);
+  ResetDevicesAndServices();
+  SetupWiFiNetwork();
+  SetupEthernetNetwork();
 
   const DeviceMap& device_type_map =
-      network_health_provider_.GetDeviceTypeMapForTesting();
+      network_health_provider_->GetDeviceTypeMapForTesting();
 
   EXPECT_EQ(2U, device_type_map.size());
   EXPECT_TRUE(base::Contains(device_type_map,
@@ -146,7 +214,7 @@
 }
 
 TEST_F(NetworkHealthProviderTest, DeviceTypeMapEmptyWithNoDevices) {
-  ResetCrosNetworkConfigDevicesAndServices();
+  ResetDevicesAndServices();
   // Remove the default WiFi device created by network_state_helper.
   cros_network_config_test_helper_.network_state_helper()
       .manager_test()
@@ -154,9 +222,33 @@
   task_environment_.RunUntilIdle();
 
   const DeviceMap& device_type_map =
-      network_health_provider_.GetDeviceTypeMapForTesting();
+      network_health_provider_->GetDeviceTypeMapForTesting();
   EXPECT_EQ(0U, device_type_map.size());
 }
 
+TEST_F(NetworkHealthProviderTest, ManagedPropertiesSetForNetwork) {
+  ResetDevicesAndServices();
+  SetupWiFiNetwork();
+
+  const NetworkPropertiesMap& network_properties_map =
+      network_health_provider_->GetNetworkPropertiesMapForTesting();
+
+  EXPECT_EQ(1U, network_properties_map.size());
+  ValidateManagedPropertiesSet(network_properties_map, "wifi1_guid");
+}
+
+TEST_F(NetworkHealthProviderTest, ManagedPropertiesSetForMultipleNetwork) {
+  ResetDevicesAndServices();
+  SetupWiFiNetwork();
+  SetupEthernetNetwork();
+
+  const NetworkPropertiesMap& network_properties_map =
+      network_health_provider_->GetNetworkPropertiesMapForTesting();
+
+  EXPECT_EQ(2U, network_properties_map.size());
+  ValidateManagedPropertiesSet(network_properties_map, "wifi1_guid");
+  ValidateManagedPropertiesSet(network_properties_map, "eth_guid");
+}
+
 }  // namespace diagnostics
 }  // namespace chromeos
diff --git a/chromeos/components/proximity_auth/proximity_auth_system.cc b/chromeos/components/proximity_auth/proximity_auth_system.cc
index d20707f..ea69a74e 100644
--- a/chromeos/components/proximity_auth/proximity_auth_system.cc
+++ b/chromeos/components/proximity_auth/proximity_auth_system.cc
@@ -65,7 +65,11 @@
                   << (local_device.has_value() ? "present" : "absent") << "].";
 
   remote_devices_map_[account_id] = remote_devices;
-  local_device_map_.emplace(account_id, *local_device);
+  if (local_device) {
+    local_device_map_.emplace(account_id, *local_device);
+  } else {
+    local_device_map_.erase(account_id);
+  }
 
   if (started_) {
     const AccountId& focused_account_id =
diff --git a/chromeos/components/tether/connection_preserver_impl.cc b/chromeos/components/tether/connection_preserver_impl.cc
index ec4d215a..3b74040 100644
--- a/chromeos/components/tether/connection_preserver_impl.cc
+++ b/chromeos/components/tether/connection_preserver_impl.cc
@@ -173,9 +173,18 @@
     return;
   }
 
+  base::Optional<multidevice::RemoteDeviceRef> local_device =
+      device_sync_client_->GetLocalDeviceMetadata();
+  if (!local_device) {
+    PA_LOG(ERROR) << "ConnectionPreserverImpl::" << __func__
+                  << ": Local device unexpectedly null.";
+    RemovePreservedConnectionIfPresent();
+    return;
+  }
+
   connection_attempt_ = secure_channel_client_->ListenForConnectionFromDevice(
-      *remote_device, *device_sync_client_->GetLocalDeviceMetadata(),
-      kTetherFeature, secure_channel::ConnectionMedium::kBluetoothLowEnergy,
+      *remote_device, *local_device, kTetherFeature,
+      secure_channel::ConnectionMedium::kBluetoothLowEnergy,
       secure_channel::ConnectionPriority::kLow);
   connection_attempt_->SetDelegate(this);
 
diff --git a/chromeos/components/tether/message_transfer_operation.cc b/chromeos/components/tether/message_transfer_operation.cc
index dd6c859b..a144341 100644
--- a/chromeos/components/tether/message_transfer_operation.cc
+++ b/chromeos/components/tether/message_transfer_operation.cc
@@ -115,6 +115,15 @@
   if (initialized_) {
     return;
   }
+
+  base::Optional<multidevice::RemoteDeviceRef> local_device =
+      device_sync_client_->GetLocalDeviceMetadata();
+  if (!local_device) {
+    PA_LOG(ERROR) << "MessageTransferOperation::" << __func__
+                  << ": Local device unexpectedly null.";
+    return;
+  }
+
   initialized_ = true;
 
   // Store the message type for this connection as a private field. This is
@@ -132,8 +141,7 @@
         std::make_unique<ConnectionAttemptDelegate>(
             this, remote_device,
             secure_channel_client_->ListenForConnectionFromDevice(
-                remote_device, *device_sync_client_->GetLocalDeviceMetadata(),
-                kTetherFeature,
+                remote_device, *local_device, kTetherFeature,
                 secure_channel::ConnectionMedium::kBluetoothLowEnergy,
                 connection_priority_));
   }
diff --git a/chromeos/services/assistant/assistant_manager_service.h b/chromeos/services/assistant/assistant_manager_service.h
index d1e53ff8..6537206 100644
--- a/chromeos/services/assistant/assistant_manager_service.h
+++ b/chromeos/services/assistant/assistant_manager_service.h
@@ -12,7 +12,6 @@
 #include "chromeos/services/assistant/public/cpp/assistant_service.h"
 #include "chromeos/services/assistant/public/cpp/assistant_settings.h"
 #include "chromeos/services/libassistant/public/mojom/authentication_state_observer.mojom.h"
-#include "chromeos/services/libassistant/public/mojom/service_controller.mojom-shared.h"
 #include "services/media_session/public/mojom/media_session.mojom-shared.h"
 
 namespace chromeos {
@@ -34,7 +33,21 @@
     std::string access_token;
   };
 
-  using State = chromeos::libassistant::mojom::ServiceState;
+  enum State {
+    // Initial state, the service is created but not started yet.
+    STOPPED = 0,
+    // Start has been called but libassistant creation is still in progress.
+    // Calling |assistant_manager()| will still return a nullptr.
+    // TODO(b/171748795): I think we no longer need this state once
+    // Libassistant has migrated to a mojom service (in fact, we should be able
+    // to remove this enum and use chromeos::libassistant::mojom::ServiceState).
+    STARTING = 1,
+    // The service is started, libassistant has been created, but libassistant
+    // is not ready yet to take requests.
+    STARTED = 2,
+    // The service is fully running and ready to take requests.
+    RUNNING = 3
+  };
 
   ~AssistantManagerService() override = default;
 
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.cc b/chromeos/services/assistant/assistant_manager_service_impl.cc
index 0f002db..4f35d3e 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.cc
+++ b/chromeos/services/assistant/assistant_manager_service_impl.cc
@@ -221,7 +221,10 @@
 void AssistantManagerServiceImpl::Start(const base::Optional<UserInfo>& user,
                                         bool enable_hotword) {
   DCHECK(!IsServiceStarted());
-  DCHECK_EQ(GetState(), State::kStopped);
+  DCHECK_EQ(GetState(), State::STOPPED);
+
+  // Set the flag to avoid starting the service multiple times.
+  SetStateAndInformObservers(State::STARTING);
 
   started_time_ = base::TimeTicks::Now();
 
@@ -231,6 +234,11 @@
 }
 
 void AssistantManagerServiceImpl::Stop() {
+  // We cannot cleanly stop the service if it is in the process of starting up.
+  DCHECK_NE(GetState(), State::STARTING);
+
+  SetStateAndInformObservers(State::STOPPED);
+
   media_host_->Stop();
   scoped_app_list_event_subscriber_.Reset();
 
@@ -263,13 +271,13 @@
 }
 
 void AssistantManagerServiceImpl::SetArcPlayStoreEnabled(bool enable) {
-  DCHECK(GetState() == State::kRunning);
+  DCHECK(GetState() == State::RUNNING);
   if (assistant::features::IsAppSupportEnabled())
     display_controller().SetArcPlayStoreEnabled(enable);
 }
 
 void AssistantManagerServiceImpl::SetAssistantContextEnabled(bool enable) {
-  DCHECK(GetState() == State::kRunning);
+  DCHECK(GetState() == State::RUNNING);
 
   media_host_->SetRelatedInfoEnabled(enable);
   display_controller().SetRelatedInfoEnabled(enable);
@@ -424,7 +432,6 @@
       OnServiceRunning();
       break;
     case ServiceState::kStopped:
-      OnServiceStopped();
       break;
   }
 }
@@ -449,11 +456,13 @@
 }
 
 void AssistantManagerServiceImpl::OnServiceStarted() {
+  DCHECK_EQ(GetState(), State::STARTING);
+
   const base::TimeDelta time_since_started =
       base::TimeTicks::Now() - started_time_;
   UMA_HISTOGRAM_TIMES("Assistant.ServiceStartTime", time_since_started);
 
-  SetStateAndInformObservers(State::kStarted);
+  SetStateAndInformObservers(State::STARTED);
 
   if (base::FeatureList::IsEnabled(assistant::features::kAssistantAppSupport))
     scoped_app_list_event_subscriber_.Observe(device_actions());
@@ -461,10 +470,11 @@
 
 bool AssistantManagerServiceImpl::IsServiceStarted() const {
   switch (state_) {
-    case State::kStopped:
+    case State::STOPPED:
+    case State::STARTING:
       return false;
-    case State::kStarted:
-    case State::kRunning:
+    case State::STARTED:
+    case State::RUNNING:
       return true;
   }
 }
@@ -478,10 +488,10 @@
 
 void AssistantManagerServiceImpl::OnServiceRunning() {
   // Try to avoid double run by checking |GetState()|.
-  if (GetState() == State::kRunning)
+  if (GetState() == State::RUNNING)
     return;
 
-  SetStateAndInformObservers(State::kRunning);
+  SetStateAndInformObservers(State::RUNNING);
 
   if (is_first_init) {
     is_first_init = false;
@@ -502,10 +512,6 @@
     SetArcPlayStoreEnabled(assistant_state()->arc_play_store_enabled().value());
 }
 
-void AssistantManagerServiceImpl::OnServiceStopped() {
-  SetStateAndInformObservers(State::kStopped);
-}
-
 void AssistantManagerServiceImpl::OnAndroidAppListRefreshed(
     const std::vector<AndroidAppInfo>& apps_info) {
   std::vector<AndroidAppInfo> filtered_apps_info;
@@ -535,7 +541,7 @@
 void AssistantManagerServiceImpl::OnDeviceAppsEnabled(bool enabled) {
   // The device apps state sync should only be sent after service is running.
   // Check state here to prevent timing issue when the service is restarting.
-  if (GetState() != State::kRunning)
+  if (GetState() != State::RUNNING)
     return;
 
   display_controller().SetDeviceAppsEnabled(enabled);
@@ -660,9 +666,6 @@
 }
 
 void AssistantManagerServiceImpl::SetStateAndInformObservers(State new_state) {
-  if (state_ == new_state)
-    return;
-
   state_ = new_state;
 
   for (auto& observer : state_observers_)
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.h b/chromeos/services/assistant/assistant_manager_service_impl.h
index 8c948f6..b0e5b2c8 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.h
+++ b/chromeos/services/assistant/assistant_manager_service_impl.h
@@ -183,7 +183,6 @@
   void InitAssistant(const base::Optional<UserInfo>& user);
   void OnServiceStarted();
   void OnServiceRunning();
-  void OnServiceStopped();
   bool IsServiceStarted() const;
 
   mojo::PendingRemote<network::mojom::URLLoaderFactory> BindURLLoaderFactory();
@@ -228,7 +227,7 @@
 
   void SetStateAndInformObservers(State new_state);
 
-  State state_ = State::kStopped;
+  State state_ = State::STOPPED;
   std::unique_ptr<AssistantSettingsImpl> assistant_settings_;
 
   std::unique_ptr<AssistantProxy> assistant_proxy_;
diff --git a/chromeos/services/assistant/assistant_manager_service_impl_unittest.cc b/chromeos/services/assistant/assistant_manager_service_impl_unittest.cc
index 32561b5..8f9cee85 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl_unittest.cc
+++ b/chromeos/services/assistant/assistant_manager_service_impl_unittest.cc
@@ -195,9 +195,9 @@
   // Start Libassistant, and wait until it is running.
   void StartAndWaitForRunning() {
     Start();
-    WaitForState(AssistantManagerService::State::kStarted);
+    WaitForState(AssistantManagerService::STARTED);
     mojom_service_controller().SetState(ServiceState::kRunning);
-    WaitForState(AssistantManagerService::State::kRunning);
+    WaitForState(AssistantManagerService::RUNNING);
   }
 
   void RunUntilIdle() {
@@ -315,55 +315,62 @@
 }  // namespace
 
 TEST_F(AssistantManagerServiceImplTest, StateShouldStartAsStopped) {
-  EXPECT_STATE(AssistantManagerService::State::kStopped);
+  EXPECT_STATE(AssistantManagerService::STOPPED);
 }
 
 TEST_F(AssistantManagerServiceImplTest,
-       StateShouldRemainStoppedUntilLibassistantServiceIsStarted) {
+       StateShouldChangeToStartingAfterCallingStart) {
+  Start();
+
+  EXPECT_STATE(AssistantManagerService::STARTING);
+}
+
+TEST_F(AssistantManagerServiceImplTest,
+       StateShouldRemainStartingUntilLibassistantServiceIsStarted) {
   mojom_service_controller().BlockStartCalls();
 
   Start();
-  WaitForState(AssistantManagerService::State::kStopped);
+  WaitForState(AssistantManagerService::STARTING);
 
   mojom_service_controller().UnblockStartCalls();
-  WaitForState(AssistantManagerService::State::kStarted);
+  WaitForState(AssistantManagerService::STARTED);
 }
 
 TEST_F(AssistantManagerServiceImplTest,
        StateShouldBecomeRunningAfterLibassistantSignalsRunningState) {
   Start();
-  WaitForState(AssistantManagerService::State::kStarted);
+  WaitForState(AssistantManagerService::STARTED);
 
   mojom_service_controller().SetState(ServiceState::kRunning);
 
-  WaitForState(AssistantManagerService::State::kRunning);
+  WaitForState(AssistantManagerService::RUNNING);
 }
 
 TEST_F(AssistantManagerServiceImplTest, ShouldSetStateToStoppedAfterStopping) {
   Start();
-  WaitForState(AssistantManagerService::State::kStarted);
+  WaitForState(AssistantManagerService::STARTED);
 
   assistant_manager_service()->Stop();
-  WaitForState(AssistantManagerService::State::kStopped);
+  WaitForState(AssistantManagerService::STOPPED);
 }
 
 TEST_F(AssistantManagerServiceImplTest, ShouldAllowRestartingAfterStopping) {
   Start();
-  WaitForState(AssistantManagerService::State::kStarted);
+  WaitForState(AssistantManagerService::STARTED);
 
   assistant_manager_service()->Stop();
-  WaitForState(AssistantManagerService::State::kStopped);
+  WaitForState(AssistantManagerService::STOPPED);
 
   Start();
-  WaitForState(AssistantManagerService::State::kStarted);
+  WaitForState(AssistantManagerService::STARTED);
 }
 
 TEST_F(AssistantManagerServiceImplTest, ShouldNotResetDataWhenStopping) {
   Start();
-  WaitForState(AssistantManagerService::State::kStarted);
+  WaitForState(AssistantManagerService::STARTED);
 
   assistant_manager_service()->Stop();
-  WaitForState(AssistantManagerService::State::kStopped);
+  WaitForState(AssistantManagerService::STOPPED);
   RunUntilIdle();
 
   EXPECT_EQ(false, mojom_service_controller().has_data_been_reset());
@@ -372,11 +379,11 @@
 TEST_F(AssistantManagerServiceImplTest,
        ShouldResetDataWhenAssistantIsDisabled) {
   Start();
-  WaitForState(AssistantManagerService::State::kStarted);
+  WaitForState(AssistantManagerService::STARTED);
 
   assistant_state().SetAssistantEnabled(false);
   assistant_manager_service()->Stop();
-  WaitForState(AssistantManagerService::State::kStopped);
+  WaitForState(AssistantManagerService::STOPPED);
   RunUntilIdle();
 
   EXPECT_EQ(true, mojom_service_controller().has_data_been_reset());
@@ -387,7 +394,7 @@
   assistant_manager_service()->Start(UserInfo("<user-id>", "<access-token>"),
                                      /*enable_hotword=*/false);
 
-  WaitForState(AssistantManagerService::State::kStarted);
+  WaitForState(AssistantManagerService::STARTED);
 
   EXPECT_EQ("<user-id>", mojom_service_controller().gaia_id());
   EXPECT_EQ("<access-token>", mojom_service_controller().access_token());
@@ -395,7 +402,7 @@
 
 TEST_F(AssistantManagerServiceImplTest, ShouldPassUserInfoToAssistantManager) {
   Start();
-  WaitForState(AssistantManagerService::State::kStarted);
+  WaitForState(AssistantManagerService::STARTED);
 
   assistant_manager_service()->SetUser(
       UserInfo("<new-user-id>", "<new-access-token>"));
@@ -408,7 +415,7 @@
 TEST_F(AssistantManagerServiceImplTest,
        ShouldPassEmptyUserInfoToAssistantManager) {
   Start();
-  WaitForState(AssistantManagerService::State::kStarted);
+  WaitForState(AssistantManagerService::STARTED);
 
   assistant_manager_service()->SetUser(base::nullopt);
   RunUntilIdle();
@@ -419,11 +426,11 @@
 
 TEST_F(AssistantManagerServiceImplTest,
        ShouldNotCrashWhenSettingUserInfoBeforeStartIsFinished) {
-  EXPECT_STATE(AssistantManagerService::State::kStopped);
+  EXPECT_STATE(AssistantManagerService::STOPPED);
   assistant_manager_service()->SetUser(UserInfo("<user-id>", "<access-token>"));
 
   Start();
-  EXPECT_STATE(AssistantManagerService::State::kStopped);
+  EXPECT_STATE(AssistantManagerService::STARTING);
   assistant_manager_service()->SetUser(UserInfo("<user-id>", "<access-token>"));
 }
 
@@ -432,7 +439,7 @@
   CreateAssistantManagerServiceImpl("the-uri-override");
 
   Start();
-  WaitForState(AssistantManagerService::State::kStarted);
+  WaitForState(AssistantManagerService::STARTED);
 
   EXPECT_EQ(mojom_service_controller()
                 .libassistant_config()
@@ -446,7 +453,7 @@
       /*s3_server_uri_override=*/base::nullopt, "the-device-id-override");
 
   Start();
-  WaitForState(AssistantManagerService::State::kStarted);
+  WaitForState(AssistantManagerService::STARTED);
 
   EXPECT_EQ(mojom_service_controller()
                 .libassistant_config()
@@ -507,7 +514,7 @@
 TEST_F(AssistantManagerServiceImplTest,
        ShouldNotCrashWhenMediaManagerIsAbsent) {
   Start();
-  WaitForState(AssistantManagerService::State::kStarted);
+  WaitForState(AssistantManagerService::STARTED);
 
   assistant_manager_service()->UpdateInternalMediaPlayerStatus(
       media_session::mojom::MediaSessionAction::kPlay);
@@ -516,21 +523,37 @@
 TEST_F(AssistantManagerServiceImplTest, ShouldFireStateObserverWhenAddingIt) {
   StrictMock<StateObserverMock> observer;
   EXPECT_CALL(observer,
-              OnStateChanged(AssistantManagerService::State::kStopped));
+              OnStateChanged(AssistantManagerService::State::STOPPED));
 
   assistant_manager_service()->AddAndFireStateObserver(&observer);
 
   assistant_manager_service()->RemoveStateObserver(&observer);
 }
 
+TEST_F(AssistantManagerServiceImplTest, ShouldFireStateObserverWhenStarting) {
+  StrictMock<StateObserverMock> observer;
+  AddStateObserver(&observer);
+
+  mojom_service_controller().BlockStartCalls();
+
+  EXPECT_CALL(observer,
+              OnStateChanged(AssistantManagerService::State::STARTING));
+  Start();
+
+  assistant_manager_service()->RemoveStateObserver(&observer);
+  mojom_service_controller().UnblockStartCalls();
+}
+
 TEST_F(AssistantManagerServiceImplTest, ShouldFireStateObserverWhenStarted) {
   StrictMock<StateObserverMock> observer;
   AddStateObserver(&observer);
 
   EXPECT_CALL(observer,
-              OnStateChanged(AssistantManagerService::State::kStarted));
+              OnStateChanged(AssistantManagerService::State::STARTING));
+  EXPECT_CALL(observer,
+              OnStateChanged(AssistantManagerService::State::STARTED));
   Start();
-  WaitForState(AssistantManagerService::State::kStarted);
+  WaitForState(AssistantManagerService::STARTED);
 
   assistant_manager_service()->RemoveStateObserver(&observer);
 }
@@ -538,30 +561,29 @@
 TEST_F(AssistantManagerServiceImplTest,
        ShouldFireStateObserverWhenLibAssistantServiceIsRunning) {
   Start();
-  WaitForState(AssistantManagerService::State::kStarted);
+  WaitForState(AssistantManagerService::STARTED);
 
   StrictMock<StateObserverMock> observer;
   AddStateObserver(&observer);
   EXPECT_CALL(observer,
-              OnStateChanged(AssistantManagerService::State::kRunning));
+              OnStateChanged(AssistantManagerService::State::RUNNING));
 
   mojom_service_controller().SetState(ServiceState::kRunning);
-  WaitForState(AssistantManagerService::State::kRunning);
+  WaitForState(AssistantManagerService::RUNNING);
 
   assistant_manager_service()->RemoveStateObserver(&observer);
 }
 
 TEST_F(AssistantManagerServiceImplTest, ShouldFireStateObserverWhenStopping) {
   Start();
-  WaitForState(AssistantManagerService::State::kStarted);
+  WaitForState(AssistantManagerService::STARTED);
 
   StrictMock<StateObserverMock> observer;
   AddStateObserver(&observer);
   EXPECT_CALL(observer,
-              OnStateChanged(AssistantManagerService::State::kStopped));
+              OnStateChanged(AssistantManagerService::State::STOPPED));
 
   assistant_manager_service()->Stop();
-  WaitForState(AssistantManagerService::State::kStopped);
 
   assistant_manager_service()->RemoveStateObserver(&observer);
 }
@@ -581,7 +603,7 @@
        ShouldStartSpeakerIdEnrollmentWhenRequested) {
   NiceMock<SpeakerIdEnrollmentClientMock> client_mock;
   Start();
-  WaitForState(AssistantManagerService::State::kStarted);
+  WaitForState(AssistantManagerService::STARTED);
 
   StrictMock<SpeakerIdEnrollmentControllerMock> mojom_mock;
   mojom_mock.Bind(mojom_libassistant_service());
@@ -599,7 +621,7 @@
   NiceMock<SpeakerIdEnrollmentClientMock> client_mock;
   fake_service_context()->set_primary_account_gaia_id("gaia user id");
   Start();
-  WaitForState(AssistantManagerService::State::kStarted);
+  WaitForState(AssistantManagerService::STARTED);
 
   StrictMock<SpeakerIdEnrollmentControllerMock> mojom_mock;
   mojom_mock.Bind(mojom_libassistant_service());
@@ -615,7 +637,7 @@
 TEST_F(AssistantManagerServiceImplTest,
        ShouldSendSkipCloudEnrollmentDuringSpeakerIdEnrollment) {
   Start();
-  WaitForState(AssistantManagerService::State::kStarted);
+  WaitForState(AssistantManagerService::STARTED);
 
   StrictMock<SpeakerIdEnrollmentControllerMock> mojom_mock;
   mojom_mock.Bind(mojom_libassistant_service());
@@ -644,7 +666,7 @@
 TEST_F(AssistantManagerServiceImplTest, ShouldSendStopSpeakerIdEnrollment) {
   NiceMock<SpeakerIdEnrollmentClientMock> client_mock;
   Start();
-  WaitForState(AssistantManagerService::State::kStarted);
+  WaitForState(AssistantManagerService::STARTED);
 
   StrictMock<SpeakerIdEnrollmentControllerMock> mojom_mock;
   mojom_mock.Bind(mojom_libassistant_service());
@@ -658,7 +680,7 @@
 TEST_F(AssistantManagerServiceImplTest, ShouldSyncSpeakerIdEnrollmentStatus) {
   StrictMock<SpeakerIdEnrollmentClientMock> client_mock;
   Start();
-  WaitForState(AssistantManagerService::State::kStarted);
+  WaitForState(AssistantManagerService::STARTED);
 
   StrictMock<SpeakerIdEnrollmentControllerMock> mojom_mock;
   mojom_mock.Bind(mojom_libassistant_service());
diff --git a/chromeos/services/assistant/service.cc b/chromeos/services/assistant/service.cc
index c5f159a..4fc3bbc3 100644
--- a/chromeos/services/assistant/service.cc
+++ b/chromeos/services/assistant/service.cc
@@ -67,10 +67,11 @@
   using State = AssistantManagerService::State;
 
   switch (state) {
-    case State::kStopped:
-    case State::kStarted:
+    case State::STOPPED:
+    case State::STARTING:
+    case State::STARTED:
       return AssistantStatus::NOT_READY;
-    case State::kRunning:
+    case State::RUNNING:
       return AssistantStatus::READY;
   }
 }
@@ -281,7 +282,7 @@
   // Notify device apps status when user accepts activity control.
   if (assistant_manager_service_ &&
       assistant_manager_service_->GetState() ==
-          AssistantManagerService::State::kRunning) {
+          AssistantManagerService::State::RUNNING) {
     assistant_manager_service_->SyncDeviceAppsStatus();
   }
 }
@@ -326,9 +327,9 @@
 void Service::OnStateChanged(AssistantManagerService::State new_state) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  if (new_state == AssistantManagerService::State::kStarted)
+  if (new_state == AssistantManagerService::State::STARTED)
     FinalizeAssistantManagerService();
-  if (new_state == AssistantManagerService::State::kRunning)
+  if (new_state == AssistantManagerService::State::RUNNING)
     DVLOG(1) << "Assistant is running";
 
   AssistantClient::Get()->OnAssistantStatusChanged(
@@ -360,13 +361,14 @@
 
   auto state = assistant_manager_service_->GetState();
   switch (state) {
-    case AssistantManagerService::State::kStopped:
+    case AssistantManagerService::State::STOPPED:
       if (assistant_state->settings_enabled().value()) {
         assistant_manager_service_->Start(GetUserInfo(), ShouldEnableHotword());
         DVLOG(1) << "Request Assistant start";
       }
       break;
-    case AssistantManagerService::State::kStarted:
+    case AssistantManagerService::State::STARTING:
+    case AssistantManagerService::State::STARTED:
       // If the Assistant is disabled by domain policy, the libassistant will
       // never becomes ready. Stop waiting for the state change and stop the
       // service.
@@ -384,7 +386,7 @@
           FROM_HERE, update_assistant_manager_callback_.callback(),
           kUpdateAssistantManagerDelay);
       break;
-    case AssistantManagerService::State::kRunning:
+    case AssistantManagerService::State::RUNNING:
       if (assistant_state->settings_enabled().value()) {
         assistant_manager_service_->SetUser(GetUserInfo());
         assistant_manager_service_->EnableHotword(ShouldEnableHotword());
@@ -503,9 +505,9 @@
 void Service::FinalizeAssistantManagerService() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(assistant_manager_service_->GetState() ==
-             AssistantManagerService::State::kStarted ||
+             AssistantManagerService::STARTED ||
          assistant_manager_service_->GetState() ==
-             AssistantManagerService::State::kRunning);
+             AssistantManagerService::RUNNING);
 
   // Ensure one-time mojom initialization.
   if (is_assistant_manager_service_finalized_)
diff --git a/chromeos/services/assistant/service_unittest.cc b/chromeos/services/assistant/service_unittest.cc
index 97361fb0..6374f4f 100644
--- a/chromeos/services/assistant/service_unittest.cc
+++ b/chromeos/services/assistant/service_unittest.cc
@@ -213,36 +213,33 @@
 }
 
 TEST_F(AssistantServiceTest, StopImmediatelyIfAssistantIsRunning) {
-  // Test is set up as |State::kStarted|.
+  // Test is set up as |State::STARTED|.
   assistant_manager()->FinishStart();
-  EXPECT_STATE(AssistantManagerService::State::kRunning);
+  EXPECT_STATE(AssistantManagerService::State::RUNNING);
 
   StopAssistantAndWait();
 
-  EXPECT_STATE(AssistantManagerService::State::kStopped);
+  EXPECT_STATE(AssistantManagerService::State::STOPPED);
 }
 
 TEST_F(AssistantServiceTest, StopDelayedIfAssistantNotFinishedStarting) {
-  assistant_manager()->SetStateAndInformObservers(
-      AssistantManagerService::State::kStarted);
-
-  EXPECT_STATE(AssistantManagerService::State::kStarted);
+  EXPECT_STATE(AssistantManagerService::State::STARTING);
 
   // Turning settings off will trigger logic to try to stop it.
   StopAssistantAndWait();
 
-  EXPECT_STATE(AssistantManagerService::State::kStarted);
+  EXPECT_STATE(AssistantManagerService::State::STARTING);
 
   task_environment()->FastForwardBy(kUpdateAssistantManagerDelay);
 
-  // No change of state because it is still not running.
-  EXPECT_STATE(AssistantManagerService::State::kStarted);
+  // No change of state because it is still starting.
+  EXPECT_STATE(AssistantManagerService::State::STARTING);
 
   assistant_manager()->FinishStart();
 
   task_environment()->FastForwardBy(kUpdateAssistantManagerDelay);
 
-  EXPECT_STATE(AssistantManagerService::State::kStopped);
+  EXPECT_STATE(AssistantManagerService::State::STOPPED);
 }
 
 TEST_F(AssistantServiceTest, ShouldSendUserInfoWhenStarting) {
@@ -277,13 +274,11 @@
   EXPECT_EQ(kGaiaId, assistant_manager()->gaia_id());
 }
 
-TEST_F(AssistantServiceTest, ShouldSetClientStatusToNotReadyWhenStopped) {
+TEST_F(AssistantServiceTest, ShouldSetClientStatusToNotReadyWhenStarting) {
   assistant_manager()->SetStateAndInformObservers(
-      AssistantManagerService::State::kRunning);
+      AssistantManagerService::State::STARTING);
   base::RunLoop().RunUntilIdle();
 
-  StopAssistantAndWait();
-
   EXPECT_EQ(client()->status(), AssistantStatus::NOT_READY);
 }
 
@@ -291,22 +286,29 @@
   // Note: even though we've started, we are not ready to handle the queries
   // until LibAssistant tells us we are.
   assistant_manager()->SetStateAndInformObservers(
-      AssistantManagerService::State::kStarted);
+      AssistantManagerService::State::STARTED);
   base::RunLoop().RunUntilIdle();
 
   EXPECT_EQ(client()->status(), AssistantStatus::NOT_READY);
 }
 
-TEST_F(AssistantServiceTest, ShouldSetClientStatusToReadyWhenRunning) {
+TEST_F(AssistantServiceTest, ShouldSetClientStatusToNewReadyWhenRunning) {
   assistant_manager()->SetStateAndInformObservers(
-      AssistantManagerService::State::kStarted);
-
-  assistant_manager()->SetStateAndInformObservers(
-      AssistantManagerService::State::kRunning);
+      AssistantManagerService::State::RUNNING);
   base::RunLoop().RunUntilIdle();
 
   EXPECT_EQ(client()->status(), AssistantStatus::READY);
 }
 
+TEST_F(AssistantServiceTest, ShouldSetClientStatusToNotReadyWhenStopped) {
+  assistant_manager()->SetStateAndInformObservers(
+      AssistantManagerService::State::RUNNING);
+  base::RunLoop().RunUntilIdle();
+
+  StopAssistantAndWait();
+
+  EXPECT_EQ(client()->status(), AssistantStatus::NOT_READY);
+}
+
 }  // namespace assistant
 }  // namespace chromeos
diff --git a/chromeos/services/assistant/test_support/fake_assistant_manager_service_impl.cc b/chromeos/services/assistant/test_support/fake_assistant_manager_service_impl.cc
index 748fceff..dc894ee1 100644
--- a/chromeos/services/assistant/test_support/fake_assistant_manager_service_impl.cc
+++ b/chromeos/services/assistant/test_support/fake_assistant_manager_service_impl.cc
@@ -14,17 +14,18 @@
 FakeAssistantManagerServiceImpl::~FakeAssistantManagerServiceImpl() = default;
 
 void FakeAssistantManagerServiceImpl::FinishStart() {
-  SetStateAndInformObservers(State::kRunning);
+  SetStateAndInformObservers(State::RUNNING);
 }
 
 void FakeAssistantManagerServiceImpl::Start(
     const base::Optional<UserInfo>& user,
     bool enable_hotword) {
+  SetStateAndInformObservers(State::STARTING);
   SetUser(user);
 }
 
 void FakeAssistantManagerServiceImpl::Stop() {
-  SetStateAndInformObservers(State::kStopped);
+  SetStateAndInformObservers(State::STOPPED);
 }
 
 void FakeAssistantManagerServiceImpl::SetUser(
@@ -128,12 +129,13 @@
   State old_state = state_;
   state_ = new_state;
 
-  // In reality we will not skip states, i.e. we will always get |STARTED|
-  // before ever encountering |RUNNING|. As such our fake implementation will
+  // In reality we will not skip states, i.e. we will always get |STARTING|
+  // before ever encountering |STARTED|. As such our fake implementation will
   // send out all intermediate states between |old_state| and |new_state|.
-  MaybeSendStateChange(State::kStopped, old_state, new_state);
-  MaybeSendStateChange(State::kStarted, old_state, new_state);
-  MaybeSendStateChange(State::kRunning, old_state, new_state);
+  MaybeSendStateChange(State::STOPPED, old_state, new_state);
+  MaybeSendStateChange(State::STARTING, old_state, new_state);
+  MaybeSendStateChange(State::STARTED, old_state, new_state);
+  MaybeSendStateChange(State::RUNNING, old_state, new_state);
 }
 
 void FakeAssistantManagerServiceImpl::MaybeSendStateChange(State state,
diff --git a/chromeos/services/assistant/test_support/fake_assistant_manager_service_impl.h b/chromeos/services/assistant/test_support/fake_assistant_manager_service_impl.h
index 4203e012..bc66953 100644
--- a/chromeos/services/assistant/test_support/fake_assistant_manager_service_impl.h
+++ b/chromeos/services/assistant/test_support/fake_assistant_manager_service_impl.h
@@ -93,7 +93,7 @@
   // transitioning from a prior state to a later state.
   void MaybeSendStateChange(State state, State old_state, State target_state);
 
-  State state_ = State::kStopped;
+  State state_ = State::STOPPED;
   base::Optional<std::string> gaia_id_;
   base::Optional<std::string> access_token_;
   FakeAssistantSettingsImpl assistant_settings_;
diff --git a/chromeos/services/device_sync/public/cpp/device_sync_client_impl.cc b/chromeos/services/device_sync/public/cpp/device_sync_client_impl.cc
index e78408b3..64e28f90 100644
--- a/chromeos/services/device_sync/public/cpp/device_sync_client_impl.cc
+++ b/chromeos/services/device_sync/public/cpp/device_sync_client_impl.cc
@@ -11,6 +11,7 @@
 #include "ash/constants/ash_features.h"
 #include "base/base64url.h"
 #include "base/bind.h"
+#include "base/metrics/histogram_functions.h"
 #include "chromeos/components/multidevice/expiring_remote_device_cache.h"
 #include "chromeos/components/multidevice/logging/logging.h"
 #include "chromeos/components/multidevice/remote_device.h"
@@ -122,8 +123,26 @@
 base::Optional<multidevice::RemoteDeviceRef>
 DeviceSyncClientImpl::GetLocalDeviceMetadata() {
   DCHECK(is_ready());
-  return expiring_device_cache_->GetRemoteDevice(local_instance_id_,
-                                                 local_legacy_device_id_);
+  base::UmaHistogramBoolean("CryptAuth.GetLocalDeviceMetadata.IsReady",
+                            is_ready());
+
+  // Because we expect the the client to be ready when this function is called,
+  // we also expect the local device to be non-null.
+  base::Optional<multidevice::RemoteDeviceRef> local_device =
+      expiring_device_cache_->GetRemoteDevice(local_instance_id_,
+                                              local_legacy_device_id_);
+  base::UmaHistogramBoolean("CryptAuth.GetLocalDeviceMetadata.Result",
+                            local_device.has_value());
+  if (!local_device) {
+    PA_LOG(ERROR)
+        << "DeviceSyncClientImpl::" << __func__
+        << ": Could not retrieve local device metadata. local_instance_id="
+        << local_instance_id_.value_or("[null]") << ", local_legacy_device_id="
+        << local_legacy_device_id_.value_or("[null]")
+        << ", is_ready=" << (is_ready() ? "yes" : "no");
+  }
+
+  return local_device;
 }
 
 void DeviceSyncClientImpl::SetSoftwareFeatureState(
@@ -262,8 +281,18 @@
                                   : base::make_optional<std::string>(
                                         local_device_metadata->GetDeviceId());
   } else {
-    DCHECK(!local_device_metadata->instance_id.empty());
-    local_instance_id_ = local_device_metadata->instance_id;
+    local_instance_id_ = local_device_metadata->instance_id.empty()
+                             ? base::nullopt
+                             : base::make_optional<std::string>(
+                                   local_device_metadata->instance_id);
+  }
+
+  bool has_id = local_instance_id_ || local_legacy_device_id_;
+  base::UmaHistogramBoolean("CryptAuth.GetLocalDeviceMetadata.HasId", has_id);
+  if (!has_id) {
+    PA_LOG(ERROR) << "DeviceSyncClientImpl::" << __func__
+                  << ": Local device identifiers are unexpectedly empty.";
+    return;
   }
 
   expiring_device_cache_->UpdateRemoteDevice(*local_device_metadata);
diff --git a/chromeos/services/multidevice_setup/feature_state_manager_impl.cc b/chromeos/services/multidevice_setup/feature_state_manager_impl.cc
index 5abce746..b20051f 100644
--- a/chromeos/services/multidevice_setup/feature_state_manager_impl.cc
+++ b/chromeos/services/multidevice_setup/feature_state_manager_impl.cc
@@ -474,6 +474,14 @@
            multidevice::SoftwareFeature::kWifiSyncClient},
           {mojom::Feature::kEche, multidevice::SoftwareFeature::kEcheClient}};
 
+  base::Optional<multidevice::RemoteDeviceRef> local_device =
+      device_sync_client_->GetLocalDeviceMetadata();
+  if (!local_device) {
+    PA_LOG(ERROR) << "FeatureStateManagerImpl::" << __func__
+                  << ": Local device unexpectedly null.";
+    return false;
+  }
+
   for (const auto& pair : kFeatureAndClientSoftwareFeaturePairs) {
     if (pair.first != feature)
       continue;
@@ -483,8 +491,7 @@
       return false;
     }
 
-    return device_sync_client_->GetLocalDeviceMetadata()
-               ->GetSoftwareFeatureState(pair.second) !=
+    return local_device->GetSoftwareFeatureState(pair.second) !=
            multidevice::SoftwareFeatureState::kNotSupported;
   }
 
diff --git a/chromeos/services/multidevice_setup/wifi_sync_feature_manager_impl.cc b/chromeos/services/multidevice_setup/wifi_sync_feature_manager_impl.cc
index 42cd31e6..b6bb254 100644
--- a/chromeos/services/multidevice_setup/wifi_sync_feature_manager_impl.cc
+++ b/chromeos/services/multidevice_setup/wifi_sync_feature_manager_impl.cc
@@ -227,15 +227,29 @@
     return false;
   }
 
-  if (host_status_provider_->GetHostWithStatus()
-          .host_device()
-          ->GetSoftwareFeatureState(
-              multidevice::SoftwareFeature::kWifiSyncHost) ==
+  base::Optional<multidevice::RemoteDeviceRef> host_device =
+      host_status_provider_->GetHostWithStatus().host_device();
+  if (!host_device) {
+    PA_LOG(ERROR) << "WifiSyncFeatureManagerImpl::" << __func__
+                  << ": Host device unexpectedly null.";
+    return false;
+  }
+
+  if (host_device->GetSoftwareFeatureState(
+          multidevice::SoftwareFeature::kWifiSyncHost) ==
       multidevice::SoftwareFeatureState::kNotSupported) {
     return false;
   }
 
-  if (device_sync_client_->GetLocalDeviceMetadata()->GetSoftwareFeatureState(
+  base::Optional<multidevice::RemoteDeviceRef> local_device =
+      device_sync_client_->GetLocalDeviceMetadata();
+  if (!local_device) {
+    PA_LOG(ERROR) << "WifiSyncFeatureManagerImpl::" << __func__
+                  << ": Local device unexpectedly null.";
+    return false;
+  }
+
+  if (local_device->GetSoftwareFeatureState(
           multidevice::SoftwareFeature::kWifiSyncClient) ==
       multidevice::SoftwareFeatureState::kNotSupported) {
     return false;
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index ad04261..b6dbb74a 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -124,6 +124,8 @@
     "data_model/contact_info.h",
     "data_model/credit_card.cc",
     "data_model/credit_card.h",
+    "data_model/credit_card_art_image.cc",
+    "data_model/credit_card_art_image.h",
     "data_model/credit_card_cloud_token_data.cc",
     "data_model/credit_card_cloud_token_data.h",
     "data_model/data_model_utils.cc",
diff --git a/components/autofill/core/browser/data_model/credit_card.cc b/components/autofill/core/browser/data_model/credit_card.cc
index a1b3bd8..7b3a1ab 100644
--- a/components/autofill/core/browser/data_model/credit_card.cc
+++ b/components/autofill/core/browser/data_model/credit_card.cc
@@ -570,6 +570,8 @@
   nickname_ = credit_card.nickname_;
   card_issuer_ = credit_card.card_issuer_;
   instrument_id_ = credit_card.instrument_id_;
+  virtual_card_enrollment_state_ = credit_card.virtual_card_enrollment_state_;
+  card_art_url_ = GURL(credit_card.card_art_url_);
 
   set_guid(credit_card.guid());
   set_origin(credit_card.origin());
@@ -664,6 +666,19 @@
     return 1;
   }
 
+  if (static_cast<int>(virtual_card_enrollment_state_) <
+      static_cast<int>(credit_card.virtual_card_enrollment_state_)) {
+    return -1;
+  }
+  if (static_cast<int>(virtual_card_enrollment_state_) >
+      static_cast<int>(credit_card.virtual_card_enrollment_state_)) {
+    return 1;
+  }
+
+  comparison = card_art_url_.spec().compare(credit_card.card_art_url_.spec());
+  if (comparison != 0)
+    return comparison;
+
   // Do not distinguish masked server cards from full server cards as this is
   // not needed and not desired - we want to identify masked server card from
   // sync with the (potential) full server card stored locally.
@@ -1067,7 +1082,9 @@
             << credit_card.use_count() << " " << credit_card.use_date() << " "
             << credit_card.billing_address_id() << " " << credit_card.nickname()
             << " " << credit_card.card_issuer() << " "
-            << credit_card.instrument_id();
+            << credit_card.instrument_id() << " "
+            << credit_card.virtual_card_enrollment_state() << " "
+            << credit_card.card_art_url().spec();
 }
 
 void CreditCard::SetNameOnCardFromSeparateParts() {
diff --git a/components/autofill/core/browser/data_model/credit_card.h b/components/autofill/core/browser/data_model/credit_card.h
index ce2d224..387e493 100644
--- a/components/autofill/core/browser/data_model/credit_card.h
+++ b/components/autofill/core/browser/data_model/credit_card.h
@@ -16,6 +16,7 @@
 #include "build/build_config.h"
 #include "components/autofill/core/browser/data_model/autofill_data_model.h"
 #include "components/sync/protocol/sync.pb.h"
+#include "url/gurl.h"
 
 namespace autofill {
 
@@ -59,12 +60,25 @@
     OK,
   };
 
-  // The Issuer for the card.
+  // The Issuer for the card. This must stay in sync with the proto enum in
+  // autofill_specifics.proto.
   enum Issuer {
     ISSUER_UNKNOWN = 0,
     GOOGLE = 1,
   };
 
+  // Whether the card has been enrolled in the virtual card feature. This must
+  // stay in sync with the proto enum in autofill_specifics.proto.
+  enum VirtualCardEnrollmentState {
+    // State unspecified. This is the default value of this enum. Should not be
+    // ever used with cards.
+    UNSPECIFIED = 0,
+    // Card is not enrolled and does not have related virtual card.
+    UNENROLLED = 1,
+    // Card is enrolled and has related virtual cards.
+    ENROLLED = 2,
+  };
+
   CreditCard(const std::string& guid, const std::string& origin);
 
   // Creates a server card.  The type must be MASKED_SERVER_CARD or
@@ -309,6 +323,19 @@
   // Should be used ONLY by tests.
   std::u16string NicknameAndLastFourDigitsForTesting() const;
 
+  VirtualCardEnrollmentState virtual_card_enrollment_state() const {
+    return virtual_card_enrollment_state_;
+  }
+  void set_virtual_card_enrollment_state(
+      VirtualCardEnrollmentState virtual_card_enrollment_state) {
+    virtual_card_enrollment_state_ = virtual_card_enrollment_state;
+  }
+
+  const GURL& card_art_url() const { return card_art_url_; }
+  void set_card_art_url(const GURL& card_art_url) {
+    card_art_url_ = card_art_url;
+  }
+
  private:
   FRIEND_TEST_ALL_PREFIXES(CreditCardTest, SetExpirationDateFromString);
   FRIEND_TEST_ALL_PREFIXES(CreditCardTest, SetExpirationYearFromString);
@@ -389,6 +416,13 @@
   // identify this card. |server_id_| is the legacy version of this.
   // TODO(crbug.com/1121806): remove server_id_ after full deprecation
   int64_t instrument_id_;
+
+  // The virtual card enrollment state of this card. If it is ENROLLED, then
+  // this card has virtual cards linked to it.
+  VirtualCardEnrollmentState virtual_card_enrollment_state_ = UNSPECIFIED;
+
+  // The url to fetch the rich card art image.
+  GURL card_art_url_;
 };
 
 // So we can compare CreditCards with EXPECT_EQ().
diff --git a/components/autofill/core/browser/data_model/credit_card_art_image.cc b/components/autofill/core/browser/data_model/credit_card_art_image.cc
new file mode 100644
index 0000000..e63ac3a8
--- /dev/null
+++ b/components/autofill/core/browser/data_model/credit_card_art_image.cc
@@ -0,0 +1,21 @@
+// Copyright 2021 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/autofill/core/browser/data_model/credit_card_art_image.h"
+
+namespace autofill {
+
+CreditCardArtImage::CreditCardArtImage() = default;
+
+CreditCardArtImage::CreditCardArtImage(const std::string& id,
+                                       int64_t instrument_id,
+                                       std::vector<uint8_t> card_art_image) {
+  this->id = id;
+  this->instrument_id = instrument_id;
+  this->card_art_image = std::move(card_art_image);
+}
+
+CreditCardArtImage::~CreditCardArtImage() = default;
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/data_model/credit_card_art_image.h b/components/autofill/core/browser/data_model/credit_card_art_image.h
new file mode 100644
index 0000000..4ae25d2
--- /dev/null
+++ b/components/autofill/core/browser/data_model/credit_card_art_image.h
@@ -0,0 +1,36 @@
+// Copyright 2021 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_AUTOFILL_CORE_BROWSER_DATA_MODEL_CREDIT_CARD_ART_IMAGE_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_DATA_MODEL_CREDIT_CARD_ART_IMAGE_H_
+
+#include <string>
+#include <vector>
+
+namespace autofill {
+
+// Represents an rich card art image for the server credit card.
+struct CreditCardArtImage {
+ public:
+  CreditCardArtImage();
+  CreditCardArtImage(const std::string& id,
+                     int64_t instrument_id,
+                     std::vector<uint8_t> card_art_image);
+  ~CreditCardArtImage();
+  CreditCardArtImage(const CreditCardArtImage&) = delete;
+  CreditCardArtImage& operator=(const CreditCardArtImage&) = delete;
+
+  // The server id for the related credit card.
+  std::string id;
+
+  // The instrument id for the related credit card.
+  int64_t instrument_id;
+
+  // The customized card art image. Stored as an raw PNG-encoded data.
+  std::vector<uint8_t> card_art_image;
+};
+
+}  // namespace autofill
+
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_DATA_MODEL_CREDIT_CARD_ART_IMAGE_H_
diff --git a/components/autofill/core/browser/webdata/autofill_table.cc b/components/autofill/core/browser/webdata/autofill_table.cc
index d91fca3..376366b 100644
--- a/components/autofill/core/browser/webdata/autofill_table.cc
+++ b/components/autofill/core/browser/webdata/autofill_table.cc
@@ -28,6 +28,7 @@
 #include "components/autofill/core/browser/data_model/autofill_offer_data.h"
 #include "components/autofill/core/browser/data_model/autofill_profile.h"
 #include "components/autofill/core/browser/data_model/credit_card.h"
+#include "components/autofill/core/browser/data_model/credit_card_art_image.h"
 #include "components/autofill/core/browser/data_model/credit_card_cloud_token_data.h"
 #include "components/autofill/core/browser/geo/autofill_country.h"
 #include "components/autofill/core/browser/payments/payments_customer_data.h"
@@ -684,7 +685,8 @@
           InitAutofillSyncMetadataTable() && InitModelTypeStateTable() &&
           InitPaymentsCustomerDataTable() && InitPaymentsUPIVPATable() &&
           InitServerCreditCardCloudTokenDataTable() && InitOfferDataTable() &&
-          InitOfferEligibleInstrumentTable() && InitOfferMerchantDomainTable());
+          InitOfferEligibleInstrumentTable() &&
+          InitOfferMerchantDomainTable() && InitCreditCardArtImagesTable());
 }
 
 bool AutofillTable::IsSyncable() {
@@ -794,6 +796,9 @@
     case 94:
       *update_compatible_version = false;
       return MigrateToVersion94AddPromoCodeColumnsToOfferData();
+    case 95:
+      *update_compatible_version = false;
+      return MigrateToVersion95AddVirtualCardMetadata();
   }
   return true;
 }
@@ -1532,21 +1537,23 @@
 
   sql::Statement s(db_->GetUniqueStatement(
       "SELECT "
-      "card_number_encrypted, "       // 0
-      "last_four,"                    // 1
-      "masked.id,"                    // 2
-      "metadata.use_count,"           // 3
-      "metadata.use_date,"            // 4
-      "network,"                      // 5
-      "status,"                       // 6
-      "name_on_card,"                 // 7
-      "exp_month,"                    // 8
-      "exp_year,"                     // 9
-      "metadata.billing_address_id,"  // 10
-      "bank_name,"                    // 11
-      "nickname,"                     // 12
-      "card_issuer,"                  // 13
-      "instrument_id "                // 14
+      "card_number_encrypted, "          // 0
+      "last_four,"                       // 1
+      "masked.id,"                       // 2
+      "metadata.use_count,"              // 3
+      "metadata.use_date,"               // 4
+      "network,"                         // 5
+      "status,"                          // 6
+      "name_on_card,"                    // 7
+      "exp_month,"                       // 8
+      "exp_year,"                        // 9
+      "metadata.billing_address_id,"     // 10
+      "bank_name,"                       // 11
+      "nickname,"                        // 12
+      "card_issuer,"                     // 13
+      "instrument_id, "                  // 14
+      "virtual_card_enrollment_state, "  // 15
+      "card_art_url "                    // 16
       "FROM masked_credit_cards masked "
       "LEFT OUTER JOIN unmasked_credit_cards USING (id) "
       "LEFT OUTER JOIN server_card_metadata metadata USING (id)"));
@@ -1593,6 +1600,10 @@
     card->set_card_issuer(
         static_cast<CreditCard::Issuer>(s.ColumnInt(index++)));
     card->set_instrument_id(s.ColumnInt64(index++));
+    card->set_virtual_card_enrollment_state(
+        static_cast<CreditCard::VirtualCardEnrollmentState>(
+            s.ColumnInt(index++)));
+    card->set_card_art_url(GURL(s.ColumnString(index++)));
     credit_cards->push_back(std::move(card));
   }
   return s.Succeeded();
@@ -1843,18 +1854,20 @@
   // Add all the masked cards.
   sql::Statement masked_insert(
       db_->GetUniqueStatement("INSERT INTO masked_credit_cards("
-                              "id,"             // 0
-                              "network,"        // 1
-                              "status,"         // 2
-                              "name_on_card,"   // 3
-                              "last_four,"      // 4
-                              "exp_month,"      // 5
-                              "exp_year,"       // 6
-                              "bank_name,"      // 7
-                              "nickname,"       // 8
-                              "card_issuer,"    // 9
-                              "instrument_id)"  // 10
-                              "VALUES (?,?,?,?,?,?,?,?,?,?,?)"));
+                              "id,"                             // 0
+                              "network,"                        // 1
+                              "status,"                         // 2
+                              "name_on_card,"                   // 3
+                              "last_four,"                      // 4
+                              "exp_month,"                      // 5
+                              "exp_year,"                       // 6
+                              "bank_name,"                      // 7
+                              "nickname,"                       // 8
+                              "card_issuer,"                    // 9
+                              "instrument_id,"                  // 10
+                              "virtual_card_enrollment_state,"  // 11
+                              "card_art_url) "                  // 12
+                              "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)"));
   int index;
   for (const CreditCard& card : credit_cards) {
     DCHECK_EQ(CreditCard::MASKED_SERVER_CARD, card.record_type());
@@ -1872,6 +1885,9 @@
     masked_insert.BindString16(index++, card.nickname());
     masked_insert.BindInt(index++, static_cast<int>(card.card_issuer()));
     masked_insert.BindInt64(index++, card.instrument_id());
+    masked_insert.BindInt(
+        index++, static_cast<int>(card.virtual_card_enrollment_state()));
+    masked_insert.BindString(index++, card.card_art_url().spec());
     masked_insert.Run();
     masked_insert.Reset(true);
   }
@@ -2006,6 +2022,61 @@
   return s.Succeeded();
 }
 
+bool AutofillTable::AddCreditCardArtImage(
+    const CreditCardArtImage& credit_card_art_image) {
+  sql::Transaction transaction(db_);
+  if (!transaction.Begin())
+    return false;
+
+  sql::Statement s(db_->GetUniqueStatement(
+      "INSERT INTO credit_card_art_images(id, instrument_id, card_art_image)"
+      "VALUES (?,?,?)"));
+  s.BindString(0, credit_card_art_image.id);
+  s.BindInt64(1, credit_card_art_image.instrument_id);
+  s.BindBlob(2, credit_card_art_image.card_art_image.data(),
+             credit_card_art_image.card_art_image.size());
+  s.Run();
+
+  return transaction.Commit();
+}
+
+bool AutofillTable::GetCreditCardArtImages(
+    std::vector<std::unique_ptr<CreditCardArtImage>>* credit_card_art_images) {
+  credit_card_art_images->clear();
+
+  sql::Statement s(
+      db_->GetUniqueStatement("SELECT "
+                              "id, "             // 0
+                              "instrument_id, "  // 1
+                              "card_art_image "  // 2
+                              "FROM credit_card_art_images"));
+
+  while (s.Step()) {
+    std::vector<uint8_t> card_art_image;
+    if (s.ColumnBlobAsVector(2, &card_art_image)) {
+      std::unique_ptr<CreditCardArtImage> data =
+          std::make_unique<CreditCardArtImage>(
+              s.ColumnString(0), s.ColumnInt64(1), std::move(card_art_image));
+      credit_card_art_images->push_back(std::move(data));
+    }
+  }
+
+  return s.Succeeded();
+}
+
+bool AutofillTable::ClearCreditCardArtImage(const std::string& id) {
+  sql::Transaction transaction(db_);
+  if (!transaction.Begin())
+    return false;
+
+  sql::Statement s(db_->GetUniqueStatement(
+      "DELETE FROM credit_card_art_images WHERE id = ?"));
+  s.BindString(0, id);
+  s.Run();
+
+  return transaction.Commit();
+}
+
 void AutofillTable::SetPaymentsCustomerData(
     const PaymentsCustomerData* customer_data) {
   sql::Transaction transaction(db_);
@@ -2258,6 +2329,11 @@
   autofill_offer_merchant_domain.Run();
   changed |= db_->GetLastChangeCount() > 0;
 
+  sql::Statement credit_card_art_images(
+      db_->GetUniqueStatement("DELETE FROM credit_card_art_images"));
+  credit_card_art_images.Run();
+  changed |= db_->GetLastChangeCount() > 0;
+
   transaction.Commit();
   return changed;
 }
@@ -3459,6 +3535,35 @@
   return transaction.Commit();
 }
 
+bool AutofillTable::MigrateToVersion95AddVirtualCardMetadata() {
+  sql::Transaction transaction(db_);
+  if (!transaction.Begin())
+    return false;
+
+  if (!db_->DoesTableExist("masked_credit_cards"))
+    InitMaskedCreditCardsTable();
+
+  if (!db_->DoesTableExist("credit_card_art_images"))
+    InitCreditCardArtImagesTable();
+
+  // Add virtual_card_enrollment_state to masked_credit_cards.
+  if (!db_->DoesColumnExist("masked_credit_cards",
+                            "virtual_card_enrollment_state") &&
+      !db_->Execute("ALTER TABLE masked_credit_cards ADD COLUMN "
+                    "virtual_card_enrollment_state INTEGER DEFAULT 0")) {
+    return false;
+  }
+
+  // Add card_art_url to masked_credit_cards.
+  if (!db_->DoesColumnExist("masked_credit_cards", "card_art_url") &&
+      !db_->Execute("ALTER TABLE masked_credit_cards ADD COLUMN "
+                    "card_art_url VARCHAR")) {
+    return false;
+  }
+
+  return transaction.Commit();
+}
+
 bool AutofillTable::AddFormFieldValuesTime(
     const std::vector<FormFieldData>& elements,
     std::vector<AutofillChange>* changes,
@@ -3619,18 +3724,20 @@
   DCHECK_GT(db_->transaction_nesting(), 0);
   sql::Statement masked_insert(
       db_->GetUniqueStatement("INSERT INTO masked_credit_cards("
-                              "id,"             // 0
-                              "network,"        // 1
-                              "status,"         // 2
-                              "name_on_card,"   // 3
-                              "last_four,"      // 4
-                              "exp_month,"      // 5
-                              "exp_year,"       // 6
-                              "bank_name,"      // 7
-                              "nickname,"       // 8
-                              "card_issuer,"    // 9
-                              "instrument_id)"  // 10
-                              "VALUES (?,?,?,?,?,?,?,?,?,?,?)"));
+                              "id,"                              // 0
+                              "network,"                         // 1
+                              "status,"                          // 2
+                              "name_on_card,"                    // 3
+                              "last_four,"                       // 4
+                              "exp_month,"                       // 5
+                              "exp_year,"                        // 6
+                              "bank_name,"                       // 7
+                              "nickname,"                        // 8
+                              "card_issuer,"                     // 9
+                              "instrument_id,"                   // 10
+                              "virtual_card_enrollment_state, "  // 11
+                              "card_art_url) "                   // 12
+                              "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)"));
   int index;
   for (const CreditCard& card : credit_cards) {
     DCHECK_EQ(CreditCard::MASKED_SERVER_CARD, card.record_type());
@@ -3648,6 +3755,8 @@
     masked_insert.BindString16(index++, card.nickname());
     masked_insert.BindInt(index++, static_cast<int>(card.card_issuer()));
     masked_insert.BindInt64(index++, card.instrument_id());
+    masked_insert.BindInt(index++, card.virtual_card_enrollment_state());
+    masked_insert.BindString(index++, card.card_art_url().spec());
     masked_insert.Run();
     masked_insert.Reset(true);
 
@@ -3882,7 +3991,9 @@
                       "bank_name VARCHAR, "
                       "nickname VARCHAR, "
                       "card_issuer INTEGER DEFAULT 0, "
-                      "instrument_id INTEGER DEFAULT 0)")) {
+                      "instrument_id INTEGER DEFAULT 0, "
+                      "virtual_card_enrollment_state INTEGER DEFAULT 0, "
+                      "card_art_url VARCHAR)")) {
       NOTREACHED();
       return false;
     }
@@ -4062,4 +4173,17 @@
   return true;
 }
 
+bool AutofillTable::InitCreditCardArtImagesTable() {
+  if (!db_->DoesTableExist("credit_card_art_images")) {
+    if (!db_->Execute("CREATE TABLE credit_card_art_images ( "
+                      "id VARCHAR NOT NULL DEFAULT 0, "
+                      "instrument_id INTEGER DEFAULT 0, "
+                      "card_art_image BLOB)")) {
+      NOTREACHED();
+      return false;
+    }
+  }
+  return true;
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/webdata/autofill_table.h b/components/autofill/core/browser/webdata/autofill_table.h
index 273da00f..66e026a 100644
--- a/components/autofill/core/browser/webdata/autofill_table.h
+++ b/components/autofill/core/browser/webdata/autofill_table.h
@@ -35,6 +35,7 @@
 class AutofillTableEncryptor;
 class AutofillTableTest;
 class CreditCard;
+struct CreditCardArtImage;
 struct CreditCardCloudTokenData;
 struct FormFieldData;
 struct PaymentsCustomerData;
@@ -268,6 +269,13 @@
 //   instrument_id      Credit card id assigned by the server to identify this
 //                      card. This is opaque to the client, and |id| is the
 //                      legacy version of this.
+//   virtual_card_enrollment_state
+//                      An enum indicating the virtual card enrollment state of
+//                      this card. UNSPECIFIED is the default value. UNENROLLED
+//                      means this card has not been enrolled to have virtual
+//                      cards. ENROLLED means the card has been enrolled and
+//                      has related virtual credit cards.
+//   card_art_url       URL to generate the card art image for this card.
 //
 // unmasked_credit_cards
 //                      When a masked credit credit card is unmasked and the
@@ -418,6 +426,13 @@
 //                      offer_id in the offer_data table.
 //   merchant_domain    List of full origins for merchant websites on which
 //                      this offer would apply.
+// credit_card_art_images
+//                      Contains the card art image for the server credit card.
+//
+//   id                 The server id of the credit card.
+//   instrument_id      The non-legacy server instrument id of the card.
+//   card_art_image     The customized card art image. Stored in the form of
+//                      BLOB.
 
 class AutofillTable : public WebDatabaseTable,
                       public syncer::SyncMetadataStore {
@@ -578,6 +593,12 @@
       std::vector<std::unique_ptr<CreditCardCloudTokenData>>*
           credit_card_cloud_token_data);
 
+  // Setters and getters related to the credit card art images.
+  bool AddCreditCardArtImage(const CreditCardArtImage& credit_card_art_image);
+  bool GetCreditCardArtImages(
+      std::vector<std::unique_ptr<CreditCardArtImage>>* credit_card_art_images);
+  bool ClearCreditCardArtImage(const std::string& id);
+
   // Setters and getters related to the Google Payments customer data.
   // Passing null to the setter will clear the data.
   void SetPaymentsCustomerData(const PaymentsCustomerData* customer_data);
@@ -697,6 +718,7 @@
   bool MigrateToVersion92AddNewPrefixedNameColumn();
   bool MigrateToVersion93AddAutofillProfileLabelColumn();
   bool MigrateToVersion94AddPromoCodeColumnsToOfferData();
+  bool MigrateToVersion95AddVirtualCardMetadata();
 
   // Max data length saved in the table, AKA the maximum length allowed for
   // form data.
@@ -809,6 +831,7 @@
   bool InitOfferDataTable();
   bool InitOfferEligibleInstrumentTable();
   bool InitOfferMerchantDomainTable();
+  bool InitCreditCardArtImagesTable();
 
   std::unique_ptr<AutofillTableEncryptor> autofill_table_encryptor_;
 
diff --git a/components/autofill/core/browser/webdata/autofill_table_unittest.cc b/components/autofill/core/browser/webdata/autofill_table_unittest.cc
index 9e4b305..fbbba2e 100644
--- a/components/autofill/core/browser/webdata/autofill_table_unittest.cc
+++ b/components/autofill/core/browser/webdata/autofill_table_unittest.cc
@@ -8,7 +8,6 @@
 #include <memory>
 #include <set>
 #include <string>
-#include <tuple>
 #include <utility>
 
 #include "base/command_line.h"
@@ -28,6 +27,7 @@
 #include "components/autofill/core/browser/data_model/autofill_offer_data.h"
 #include "components/autofill/core/browser/data_model/autofill_profile.h"
 #include "components/autofill/core/browser/data_model/credit_card.h"
+#include "components/autofill/core/browser/data_model/credit_card_art_image.h"
 #include "components/autofill/core/browser/data_model/credit_card_cloud_token_data.h"
 #include "components/autofill/core/browser/payments/payments_customer_data.h"
 #include "components/autofill/core/browser/webdata/autofill_change.h"
@@ -2462,6 +2462,8 @@
   inputs[0].SetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR, u"2020");
   inputs[0].SetRawInfo(CREDIT_CARD_NUMBER, u"4111111111111111");
   inputs[0].set_instrument_id(321);
+  inputs[0].set_virtual_card_enrollment_state(
+      CreditCard::VirtualCardEnrollmentState::UNENROLLED);
 
   inputs.push_back(CreditCard(CreditCard::MASKED_SERVER_CARD, "b456"));
   inputs[1].SetRawInfo(CREDIT_CARD_NAME_FULL, u"Rick Roman");
@@ -2474,6 +2476,9 @@
   inputs[1].SetNickname(nickname);
   inputs[1].set_card_issuer(CreditCard::Issuer::GOOGLE);
   inputs[1].set_instrument_id(123);
+  inputs[1].set_virtual_card_enrollment_state(
+      CreditCard::VirtualCardEnrollmentState::ENROLLED);
+  inputs[1].set_card_art_url(GURL("https://www.example.com"));
 
   test::SetServerCreditCards(table_.get(), inputs);
 
@@ -2507,6 +2512,14 @@
 
   EXPECT_EQ(321, outputs[0]->instrument_id());
   EXPECT_EQ(123, outputs[1]->instrument_id());
+
+  EXPECT_EQ(CreditCard::VirtualCardEnrollmentState::UNENROLLED,
+            outputs[0]->virtual_card_enrollment_state());
+  EXPECT_EQ(CreditCard::VirtualCardEnrollmentState::ENROLLED,
+            outputs[1]->virtual_card_enrollment_state());
+
+  EXPECT_EQ(GURL(), outputs[0]->card_art_url());
+  EXPECT_EQ(GURL("https://www.example.com"), outputs[1]->card_art_url());
 }
 
 TEST_F(AutofillTableTest, SetGetRemoveServerCardMetadata) {
@@ -2717,6 +2730,9 @@
   inputs[0].SetServerStatus(CreditCard::EXPIRED);
   inputs[0].SetNickname(u"Grocery card");
   inputs[0].set_instrument_id(1);
+  inputs[0].set_virtual_card_enrollment_state(
+      CreditCard::VirtualCardEnrollmentState::ENROLLED);
+  inputs[0].set_card_art_url(GURL("https://www.example.com"));
   table_->SetServerCardsData(inputs);
 
   // Make sure the card was added correctly.
@@ -2733,6 +2749,11 @@
   EXPECT_EQ(inputs[0], *outputs[0]);
   EXPECT_EQ(CreditCard::EXPIRED, outputs[0]->GetServerStatus());
 
+  EXPECT_EQ(CreditCard::VirtualCardEnrollmentState::ENROLLED,
+            outputs[0]->virtual_card_enrollment_state());
+
+  EXPECT_EQ(GURL("https://www.example.com"), outputs[0]->card_art_url());
+
   // Make sure no metadata was added.
   std::map<std::string, AutofillMetadata> metadata_map;
   ASSERT_TRUE(table_->GetServerCardsMetadata(&metadata_map));
@@ -3751,4 +3772,34 @@
   }
 }
 
+TEST_F(AutofillTableTest, SetAndGetAndClearCreditCardArtImage) {
+  CreditCardArtImage image1("image1", 1, {UINT8_MAX});
+  table_->AddCreditCardArtImage(image1);
+  CreditCardArtImage image2("image2", 2, {UINT8_MAX});
+  table_->AddCreditCardArtImage(image2);
+
+  std::vector<std::unique_ptr<CreditCardArtImage>> output;
+  EXPECT_TRUE(table_->GetCreditCardArtImages(&output));
+  EXPECT_EQ(2U, output.size());
+  EXPECT_EQ("image1", output[0]->id);
+  EXPECT_EQ(1, output[0]->instrument_id);
+  EXPECT_EQ(UINT8_MAX, output[0]->card_art_image[0]);
+  EXPECT_EQ("image2", output[1]->id);
+  EXPECT_EQ(2, output[1]->instrument_id);
+  EXPECT_EQ(UINT8_MAX, output[1]->card_art_image[0]);
+
+  EXPECT_TRUE(table_->ClearCreditCardArtImage("image1"));
+  output.clear();
+  EXPECT_TRUE(table_->GetCreditCardArtImages(&output));
+  EXPECT_EQ(1U, output.size());
+  EXPECT_EQ("image2", output[0]->id);
+  EXPECT_EQ(2, output[0]->instrument_id);
+  EXPECT_EQ(UINT8_MAX, output[0]->card_art_image[0]);
+
+  EXPECT_TRUE(table_->ClearAllServerData());
+  output.clear();
+  EXPECT_TRUE(table_->GetCreditCardArtImages(&output));
+  EXPECT_EQ(0U, output.size());
+}
+
 }  // namespace autofill
diff --git a/components/enterprise/browser/enterprise_switches.cc b/components/enterprise/browser/enterprise_switches.cc
index d7787a1..b35a3b1 100644
--- a/components/enterprise/browser/enterprise_switches.cc
+++ b/components/enterprise/browser/enterprise_switches.cc
@@ -9,7 +9,7 @@
 
 namespace switches {
 
-#if !BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OS_ANDROID)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 // Enables the Chrome Browser Cloud Management integration on Chromium builds.
 // CBCM is always enabled in branded builds.
 const char kEnableChromeBrowserCloudManagement[] =
diff --git a/components/enterprise/browser/enterprise_switches.h b/components/enterprise/browser/enterprise_switches.h
index 902e1f2..0a9d813 100644
--- a/components/enterprise/browser/enterprise_switches.h
+++ b/components/enterprise/browser/enterprise_switches.h
@@ -13,7 +13,7 @@
 
 namespace switches {
 
-#if !BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OS_ANDROID)
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 extern const char kEnableChromeBrowserCloudManagement[];
 #endif
 
diff --git a/components/exo/data_exchange_delegate.h b/components/exo/data_exchange_delegate.h
index d7ef0de..4bbd623d 100644
--- a/components/exo/data_exchange_delegate.h
+++ b/components/exo/data_exchange_delegate.h
@@ -21,7 +21,7 @@
 }  // namespace base
 
 namespace ui {
-class Clipboard;
+class DataTransferEndpoint;
 struct FileInfo;
 enum class EndpointType;
 }  // namespace ui
@@ -75,11 +75,11 @@
       ui::EndpointType source,
       const std::vector<uint8_t>& data) const = 0;
 
-  // Reads clipboard custom data pickle for fs/sources with newline-separated
-  // filesystem URLs to send to |target| endpoint.
-  virtual std::vector<ui::FileInfo> ParseClipboardFilenamesPickle(
-      const ui::EndpointType target,
-      const ui::Clipboard& data) const = 0;
+  // Reads pickle for FilesApp fs/sources with newline-separated filesystem
+  // URLs. Validates that |source| is FilesApp.
+  virtual std::vector<ui::FileInfo> ParseFileSystemSources(
+      const ui::DataTransferEndpoint* source,
+      const base::Pickle& pickle) const = 0;
 };
 
 }  // namespace exo
diff --git a/components/exo/data_offer.cc b/components/exo/data_offer.cc
index efbf44e..315195c4 100644
--- a/components/exo/data_offer.cc
+++ b/components/exo/data_offer.cc
@@ -240,20 +240,28 @@
       data_exchange_delegate->GetDataTransferEndpointType(target);
   const std::string uri_list_mime_type =
       data_exchange_delegate->GetMimeTypeForUriList(endpoint_type);
-  if (data.HasFile()) {
-    std::vector<ui::FileInfo> files;
-    if (data.GetFilenames(&files)) {
-      data_callbacks_.emplace(
-          uri_list_mime_type,
-          base::BindOnce(&DataExchangeDelegate::SendFileInfo,
-                         base::Unretained(data_exchange_delegate),
-                         endpoint_type, std::move(files)));
-      delegate_->OnOffer(uri_list_mime_type);
-      return;
-    }
+  // We accept the filenames pickle from FilesApp, or
+  // OSExchangeData::GetFilenames().
+  std::vector<ui::FileInfo> filenames;
+  base::Pickle pickle;
+  if (data.GetPickledData(ui::ClipboardFormatType::GetWebCustomDataType(),
+                          &pickle)) {
+    filenames = data_exchange_delegate->ParseFileSystemSources(data.GetSource(),
+                                                               pickle);
+  }
+  if (filenames.empty() && data.HasFile()) {
+    data.GetFilenames(&filenames);
+  }
+  if (!filenames.empty()) {
+    data_callbacks_.emplace(
+        uri_list_mime_type,
+        base::BindOnce(&DataExchangeDelegate::SendFileInfo,
+                       base::Unretained(data_exchange_delegate), endpoint_type,
+                       std::move(filenames)));
+    delegate_->OnOffer(uri_list_mime_type);
+    return;
   }
 
-  base::Pickle pickle;
   if (data.GetPickledData(GetClipboardFormatType(), &pickle) &&
       data_exchange_delegate->HasUrlsInPickle(pickle)) {
     data_callbacks_.emplace(
@@ -365,9 +373,15 @@
   }
 
   // We accept the filenames pickle from FilesApp, or text/uri-list from apps.
-  std::vector<ui::FileInfo> filenames =
-      data_exchange_delegate->ParseClipboardFilenamesPickle(endpoint_type,
-                                                            data);
+  std::vector<ui::FileInfo> filenames;
+  std::string buf;
+  data.ReadData(ui::ClipboardFormatType::GetWebCustomDataType(), &data_dst,
+                &buf);
+  if (!buf.empty()) {
+    base::Pickle pickle(buf.data(), static_cast<int>(buf.size()));
+    filenames = data_exchange_delegate->ParseFileSystemSources(
+        data.GetSource(ui::ClipboardBuffer::kCopyPaste), pickle);
+  }
   if (filenames.empty() &&
       data.IsFormatAvailable(ui::ClipboardFormatType::GetFilenamesType(),
                              ui::ClipboardBuffer::kCopyPaste, &data_dst)) {
diff --git a/components/exo/test/exo_test_data_exchange_delegate.cc b/components/exo/test/exo_test_data_exchange_delegate.cc
index 00382ee3..550389e 100644
--- a/components/exo/test/exo_test_data_exchange_delegate.cc
+++ b/components/exo/test/exo_test_data_exchange_delegate.cc
@@ -91,15 +91,11 @@
   return result;
 }
 
-std::vector<ui::FileInfo>
-TestDataExchangeDelegate::ParseClipboardFilenamesPickle(
-    const ui::EndpointType target,
-    const ui::Clipboard& data) const {
+std::vector<ui::FileInfo> TestDataExchangeDelegate::ParseFileSystemSources(
+    const ui::DataTransferEndpoint* source,
+    const base::Pickle& pickle) const {
   std::vector<ui::FileInfo> file_info;
-  const ui::DataTransferEndpoint data_dst(target);
-  std::string lines;
-  data.ReadData(ui::ClipboardFormatType::GetWebCustomDataType(), &data_dst,
-                &lines);
+  std::string lines(static_cast<const char*>(pickle.data()), pickle.size());
   for (const base::StringPiece& line : base::SplitStringPiece(
            lines, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
     base::FilePath path;
diff --git a/components/exo/test/exo_test_data_exchange_delegate.h b/components/exo/test/exo_test_data_exchange_delegate.h
index e5767f90..9daeb28 100644
--- a/components/exo/test/exo_test_data_exchange_delegate.h
+++ b/components/exo/test/exo_test_data_exchange_delegate.h
@@ -37,9 +37,9 @@
   base::Pickle CreateClipboardFilenamesPickle(
       ui::EndpointType source,
       const std::vector<uint8_t>& data) const override;
-  std::vector<ui::FileInfo> ParseClipboardFilenamesPickle(
-      const ui::EndpointType target,
-      const ui::Clipboard& data) const override;
+  std::vector<ui::FileInfo> ParseFileSystemSources(
+      const ui::DataTransferEndpoint* source,
+      const base::Pickle& pickle) const override;
 
   void RunSendPickleCallback(std::vector<GURL> urls);
 
diff --git a/components/federated_learning/features/features.cc b/components/federated_learning/features/features.cc
index c618ad7..16d550c 100644
--- a/components/federated_learning/features/features.cc
+++ b/components/federated_learning/features/features.cc
@@ -20,7 +20,7 @@
 // may take a full computation cycle for the floc to meet the configured
 // criteria.
 const base::Feature kFlocPagesWithAdResourcesDefaultIncludedInFlocComputation{
-    "kFlocPagesWithAdResourcesDefaultIncludedInFlocComputation",
+    "FlocPagesWithAdResourcesDefaultIncludedInFlocComputation",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
 // The main floc feature for all the subsidiary control and setting params. It's
diff --git a/components/fullscreen_control/README.md b/components/fullscreen_control/README.md
index 1b68d23..471484f 100644
--- a/components/fullscreen_control/README.md
+++ b/components/fullscreen_control/README.md
@@ -1,6 +1,7 @@
 # Fullscreen Control #
 
 Fullscreen control provides UI components used during fullscreen:
+* 'Press and hold [ESC] to exit' message shown at the start.
 * Exit circle with 'X' shown when the mouse moves to the top of the screen.
 
 The components are used by chrome browser, and by Chrome OS exo.
diff --git a/components/remote_cocoa/app_shim/browser_native_widget_window_mac.mm b/components/remote_cocoa/app_shim/browser_native_widget_window_mac.mm
index 6958088..cf88f696 100644
--- a/components/remote_cocoa/app_shim/browser_native_widget_window_mac.mm
+++ b/components/remote_cocoa/app_shim/browser_native_widget_window_mac.mm
@@ -65,23 +65,6 @@
 
 @implementation BrowserNativeWidgetWindow
 
-// Prevent detached tabs from glitching when the window is partially offscreen.
-// See https://crbug.com/1095717 for details.
-// This is easy to get wrong so scope very tightly to only disallow large
-// vertical jumps.
-- (NSRect)constrainFrameRect:(NSRect)rect toScreen:(NSScreen*)screen {
-  NSRect proposed = [super constrainFrameRect:rect toScreen:screen];
-  // This boils down to: use the small threshold when we're not avoiding a
-  // Dock on the bottom, and the big threshold otherwise.
-  static constexpr CGFloat kBigThreshold = 200;
-  static constexpr CGFloat kSmallThreshold = 50;
-  const CGFloat yDelta = NSMaxY(proposed) - NSMaxY(rect);
-  if (yDelta > kBigThreshold ||
-      (yDelta > kSmallThreshold && NSMinY(proposed) == 0))
-    return rect;
-  return proposed;
-}
-
 // NSWindow (PrivateAPI) overrides.
 
 + (Class)frameViewClassForStyleMask:(NSUInteger)windowStyle {
diff --git a/components/remote_cocoa/app_shim/window_move_loop.h b/components/remote_cocoa/app_shim/window_move_loop.h
index 1a3f6c6..cb92808c 100644
--- a/components/remote_cocoa/app_shim/window_move_loop.h
+++ b/components/remote_cocoa/app_shim/window_move_loop.h
@@ -9,6 +9,7 @@
 
 #include "base/callback.h"
 #include "base/memory/weak_ptr.h"
+#include "ui/gfx/mac/scoped_cocoa_disable_screen_updates.h"
 
 namespace remote_cocoa {
 class NativeWidgetNSWindowBridge;
@@ -42,6 +43,8 @@
   LoopExitReason* exit_reason_ref_ = nullptr;
   base::OnceClosure quit_closure_;
 
+  std::unique_ptr<gfx::ScopedCocoaDisableScreenUpdates> screen_disabler_;
+
   // WeakPtrFactory for event monitor safety.
   base::WeakPtrFactory<CocoaWindowMoveLoop> weak_factory_;
 
diff --git a/components/remote_cocoa/app_shim/window_move_loop.mm b/components/remote_cocoa/app_shim/window_move_loop.mm
index dea9ab6..66dc54176 100644
--- a/components/remote_cocoa/app_shim/window_move_loop.mm
+++ b/components/remote_cocoa/app_shim/window_move_loop.mm
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "components/remote_cocoa/app_shim/window_move_loop.h"
+#include <memory>
 
 #include "base/debug/stack_trace.h"
 #include "base/run_loop.h"
@@ -80,6 +81,9 @@
       [[[WeakCocoaWindowMoveLoop alloc]
           initWithWeakPtr:weak_factory_.GetWeakPtr()] autorelease];
 
+  __block BOOL has_moved = NO;
+  screen_disabler_ = std::make_unique<gfx::ScopedCocoaDisableScreenUpdates>();
+
   // Esc keypress is handled by EscapeTracker, which is installed by
   // TabDragController.
   NSEventMask mask =
@@ -104,8 +108,13 @@
       NSRect ns_frame = NSOffsetRect(
           initial_frame, mouse_in_screen.x - initial_mouse_in_screen_.x,
           mouse_in_screen.y - initial_mouse_in_screen_.y);
-      ns_frame = [window constrainFrameRect:ns_frame toScreen:window.screen];
       [window setFrame:ns_frame display:NO animate:NO];
+      // `setFrame:...` may have destroyed `this`, so do the weak check again.
+      bool is_valid = [weak_cocoa_window_move_loop weak].get() == strong;
+      if (is_valid && !has_moved) {
+        has_moved = YES;
+        strong->screen_disabler_.reset();
+      }
 
       return event;
     }
@@ -136,6 +145,7 @@
 }
 
 void CocoaWindowMoveLoop::End() {
+  screen_disabler_.reset();
   if (exit_reason_ref_) {
     DCHECK_EQ(*exit_reason_ref_, ENDED_EXTERNALLY);
     // Ensure the destructor doesn't replace the reason.
diff --git a/components/safe_browsing/core/db/v4_store.cc b/components/safe_browsing/core/db/v4_store.cc
index 1ad7c27e..6e65e0dd 100644
--- a/components/safe_browsing/core/db/v4_store.cc
+++ b/components/safe_browsing/core/db/v4_store.cc
@@ -16,6 +16,7 @@
 #include "base/stl_util.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/stringprintf.h"
+#include "base/timer/elapsed_timer.h"
 #include "components/safe_browsing/core/db/prefix_iterator.h"
 #include "components/safe_browsing/core/db/v4_rice.h"
 #include "components/safe_browsing/core/db/v4_store.pb.h"
@@ -41,6 +42,8 @@
 const char kDecodeRemovals[] = ".DecodeRemovals";
 const char kAdditionsHashesCount[] = ".AdditionsHashesCount";
 const char kRemovalsHashesCount[] = ".RemovalsHashesCount";
+const char kApplyUpdateDuration[] = ".ApplyUpdateDuration";
+const char kVerifyChecksumDuration[] = ".VerifyChecksumDuration";
 // Part 3: Represent the unit of value being measured and logged.
 const char kResult[] = ".Result";
 // Part 4 (optional): Represent the name of the list for which the metric is
@@ -90,6 +93,14 @@
                                  /*buckets=*/50);
 }
 
+void RecordTimeWithAndWithoutSuffix(const std::string& metric,
+                                    base::TimeDelta duration,
+                                    const base::FilePath& file_path) {
+  base::UmaHistogramTimes(metric, duration);
+  std::string suffix = GetUmaSuffixForStore(file_path);
+  base::UmaHistogramTimes(metric + suffix, duration);
+}
+
 void RecordApplyUpdateResult(const std::string& base_metric,
                              ApplyUpdateResult result,
                              const base::FilePath& file_path) {
@@ -125,6 +136,20 @@
                                   REMOVALS_HASHES_COUNT_MAX, file_path);
 }
 
+void RecordApplyUpdateDuration(const std::string& base_metric,
+                               base::TimeDelta duration,
+                               const base::FilePath& file_path) {
+  RecordTimeWithAndWithoutSuffix(base_metric + kApplyUpdateDuration, duration,
+                                 file_path);
+}
+
+void RecordVerifyChecksumDuration(const std::string& base_metric,
+                                  base::TimeDelta duration,
+                                  const base::FilePath& file_path) {
+  RecordTimeWithAndWithoutSuffix(base_metric + kVerifyChecksumDuration,
+                                 duration, file_path);
+}
+
 void RecordStoreReadResult(StoreReadResult result) {
   UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.V4StoreRead.Result", result,
                             STORE_READ_RESULT_MAX);
@@ -329,6 +354,7 @@
     std::unique_ptr<ListUpdateResponse> response,
     const scoped_refptr<base::SingleThreadTaskRunner>& callback_task_runner,
     UpdatedStoreReadyCallback callback) {
+  base::ElapsedThreadTimer thread_timer;
   std::unique_ptr<V4Store> new_store(
       new V4Store(task_runner_, store_path_, file_size_));
   ApplyUpdateResult apply_update_result;
@@ -362,6 +388,7 @@
   last_apply_update_result_ = apply_update_result;
 
   RecordApplyUpdateResult(metric, apply_update_result, store_path_);
+  RecordApplyUpdateDuration(metric, thread_timer.Elapsed(), store_path_);
 
   // Posting the task should be the last thing to do in this function.
   // Otherwise, the posted task can end up running in parallel. If that
@@ -762,6 +789,7 @@
 }
 
 bool V4Store::VerifyChecksum() {
+  base::ElapsedThreadTimer thread_timer;
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
 
   if (expected_checksum_.empty()) {
@@ -809,9 +837,14 @@
                << "; expected: " << expected_checksum_b64
                << "; store: " << *this;
 #endif
+      RecordVerifyChecksumDuration(kReadFromDisk, thread_timer.Elapsed(),
+                                   store_path_);
       return false;
     }
   }
+
+  RecordVerifyChecksumDuration(kReadFromDisk, thread_timer.Elapsed(),
+                               store_path_);
   return true;
 }
 
diff --git a/components/test/data/web_database/version_94.sql b/components/test/data/web_database/version_94.sql
new file mode 100644
index 0000000..f24ffe2
--- /dev/null
+++ b/components/test/data/web_database/version_94.sql
@@ -0,0 +1,32 @@
+PRAGMA foreign_keys=OFF;
+BEGIN TRANSACTION;
+CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY, value LONGVARCHAR);
+INSERT INTO meta VALUES('mmap_status','-1');
+INSERT INTO meta VALUES('version','94');
+INSERT INTO meta VALUES('last_compatible_version','83');
+INSERT INTO meta VALUES('Builtin Keyword Version','117');
+CREATE TABLE token_service (service VARCHAR PRIMARY KEY NOT NULL,encrypted_token BLOB);
+CREATE TABLE keywords (id INTEGER PRIMARY KEY,short_name VARCHAR NOT NULL,keyword VARCHAR NOT NULL,favicon_url VARCHAR NOT NULL,url VARCHAR NOT NULL,safe_for_autoreplace INTEGER,originating_url VARCHAR,date_created INTEGER DEFAULT 0,usage_count INTEGER DEFAULT 0,input_encodings VARCHAR,suggest_url VARCHAR,prepopulate_id INTEGER DEFAULT 0,created_by_policy INTEGER DEFAULT 0,last_modified INTEGER DEFAULT 0,sync_guid VARCHAR,alternate_urls VARCHAR,image_url VARCHAR,search_url_post_params VARCHAR,suggest_url_post_params VARCHAR,image_url_post_params VARCHAR,new_tab_url VARCHAR,last_visited INTEGER DEFAULT 0, created_from_play_api INTEGER DEFAULT 0);
+CREATE TABLE autofill (name VARCHAR, value VARCHAR, value_lower VARCHAR, date_created INTEGER DEFAULT 0, date_last_used INTEGER DEFAULT 0, count INTEGER DEFAULT 1, PRIMARY KEY (name, value));
+CREATE TABLE credit_cards ( guid VARCHAR PRIMARY KEY, name_on_card VARCHAR, expiration_month INTEGER, expiration_year INTEGER, card_number_encrypted BLOB, date_modified INTEGER NOT NULL DEFAULT 0, origin VARCHAR DEFAULT '', use_count INTEGER NOT NULL DEFAULT 0, use_date INTEGER NOT NULL DEFAULT 0, billing_address_id VARCHAR, nickname VARCHAR);
+CREATE TABLE autofill_profiles ( guid VARCHAR PRIMARY KEY, company_name VARCHAR, street_address VARCHAR, dependent_locality VARCHAR, city VARCHAR, state VARCHAR, zipcode VARCHAR, sorting_code VARCHAR, country_code VARCHAR, date_modified INTEGER NOT NULL DEFAULT 0, origin VARCHAR DEFAULT '', language_code VARCHAR, use_count INTEGER NOT NULL DEFAULT 0, use_date INTEGER NOT NULL DEFAULT 0, validity_bitfield UNSIGNED NOT NULL DEFAULT 0, is_client_validity_states_updated BOOL NOT NULL DEFAULT FALSE, label VARCHAR);
+CREATE TABLE autofill_profile_names ( guid VARCHAR, first_name VARCHAR, middle_name VARCHAR, last_name VARCHAR, full_name VARCHAR, honorific_prefix VARCHAR, first_last_name VARCHAR, conjunction_last_name VARCHAR, second_last_name VARCHAR, honorific_prefix_status INTEGER DEFAULT 0, first_name_status INTEGER DEFAULT 0, middle_name_status INTEGER DEFAULT 0, last_name_status INTEGER DEFAULT 0, first_last_name_status INTEGER DEFAULT 0, conjunction_last_name_status INTEGER DEFAULT 0, second_last_name_status INTEGER DEFAULT 0, full_name_status INTEGER DEFAULT 0, full_name_with_honorific_prefix VARCHAR, full_name_with_honorific_prefix_status INTEGER DEFAULT 0);
+INSERT INTO "autofill_profile_names" VALUES('B41FE6E0-B13E-2A2A-BF0B-29FCE2C3ADBD','Jon','','Smith', 'Jon Smith', 'Sir', 'Smith', 'Notsmith', 'Moresmith', 0, 0, 0, 0, 0, 0, 0, 0, 'Sir Jon Smith', 0);
+CREATE TABLE autofill_profile_addresses ( guid VARCHAR, street_address VARCHAR, street_name VARCHAR, dependent_street_name VARCHAR, house_number VARCHAR, subpremise VARCHAR, premise_name VARCHAR, street_address_status INTEGER DEFAULT 0, street_name_status INTEGER DEFAULT 0, dependent_street_name_status INTEGER DEFAULT 0, house_number_status INTEGER DEFAULT 0, subpremise_status INTEGER DEFAULT 0, premise_name_status INTEGER DEFAULT 0, dependent_locality VARCHAR, city VARCHAR, state VARCHAR, zip_code VARCHAR, sorting_code VARCHAR, country_code VARCHAR, dependent_locality_status INTEGER DEFAULT 0, city_status INTEGER DEFAULT 0, state_status INTEGER DEFAULT 0, zip_code_status INTEGER DEFAULT 0, sorting_code_status INTEGER DEFAULT 0, country_code_status INTEGER DEFAULT 0, apartment_number VARCHAR, floor VARCHAR, apartment_number_status INTEGER DEFAULT 0, floor_status INTEGER DEFAULT 0);
+CREATE TABLE autofill_profile_emails ( guid VARCHAR, email VARCHAR);
+CREATE TABLE autofill_profile_phones ( guid VARCHAR, number VARCHAR);
+CREATE TABLE autofill_profiles_trash ( guid VARCHAR);
+CREATE TABLE masked_credit_cards (id VARCHAR,status VARCHAR,name_on_card VARCHAR,network VARCHAR,last_four VARCHAR,exp_month INTEGER DEFAULT 0,exp_year INTEGER DEFAULT 0, bank_name VARCHAR, nickname VARCHAR, card_issuer INTEGER DEFAULT 0, instrument_id INTEGER DEFAULT 0);
+CREATE TABLE unmasked_credit_cards (id VARCHAR,card_number_encrypted VARCHAR,unmask_date INTEGER NOT NULL DEFAULT 0);
+CREATE TABLE server_card_metadata (id VARCHAR NOT NULL,use_count INTEGER NOT NULL DEFAULT 0, use_date INTEGER NOT NULL DEFAULT 0, billing_address_id VARCHAR);
+CREATE TABLE server_addresses (id VARCHAR,company_name VARCHAR,street_address VARCHAR,address_1 VARCHAR,address_2 VARCHAR,address_3 VARCHAR,address_4 VARCHAR,postal_code VARCHAR,sorting_code VARCHAR,country_code VARCHAR,language_code VARCHAR, recipient_name VARCHAR, phone_number VARCHAR);
+CREATE TABLE server_address_metadata (id VARCHAR NOT NULL,use_count INTEGER NOT NULL DEFAULT 0, use_date INTEGER NOT NULL DEFAULT 0, has_converted BOOL NOT NULL DEFAULT FALSE);
+CREATE TABLE autofill_sync_metadata (model_type INTEGER NOT NULL, storage_key VARCHAR NOT NULL, value BLOB, PRIMARY KEY (model_type, storage_key));
+CREATE TABLE autofill_model_type_state (model_type INTEGER NOT NULL PRIMARY KEY, value BLOB);
+CREATE TABLE payments_customer_data (customer_id VARCHAR);
+CREATE TABLE payments_upi_vpa (vpa VARCHAR);
+CREATE TABLE server_card_cloud_token_data ( id VARCHAR, suffix VARCHAR, exp_month INTEGER DEFAULT 0, exp_year INTEGER DEFAULT 0, card_art_url VARCHAR, instrument_token VARCHAR);
+CREATE INDEX autofill_name ON autofill (name);
+CREATE INDEX autofill_name_value_lower ON autofill (name, value_lower);
+CREATE TABLE offer_data ( offer_id UNSIGNED LONG, offer_reward_amount VARCHAR, expiry UNSIGNED LONG, offer_details_url VARCHAR, merchant_domain VARCHAR, promo_code VARCHAR, value_prop_text VARCHAR, see_details_text VARCHAR, usage_instructions_text VARCHAR);
+COMMIT;
diff --git a/components/webdata/common/BUILD.gn b/components/webdata/common/BUILD.gn
index fd1e081..d3d431e 100644
--- a/components/webdata/common/BUILD.gn
+++ b/components/webdata/common/BUILD.gn
@@ -81,6 +81,7 @@
     "//components/test/data/web_database/version_91.sql",
     "//components/test/data/web_database/version_92.sql",
     "//components/test/data/web_database/version_93.sql",
+    "//components/test/data/web_database/version_94.sql",
   ]
   outputs = [ "{{bundle_resources_dir}}/" +
               "{{source_root_relative_dir}}/{{source_file_part}}" ]
diff --git a/components/webdata/common/web_database.cc b/components/webdata/common/web_database.cc
index b1fb9b02..58d2265 100644
--- a/components/webdata/common/web_database.cc
+++ b/components/webdata/common/web_database.cc
@@ -14,7 +14,7 @@
 // corresponding changes must happen in the unit tests, and new migration test
 // added.  See |WebDatabaseMigrationTest::kCurrentTestedVersionNumber|.
 // static
-const int WebDatabase::kCurrentVersionNumber = 94;
+const int WebDatabase::kCurrentVersionNumber = 95;
 
 const int WebDatabase::kDeprecatedVersionNumber = 51;
 
diff --git a/components/webdata/common/web_database_migration_unittest.cc b/components/webdata/common/web_database_migration_unittest.cc
index 9e92b0e7..40a2992a 100644
--- a/components/webdata/common/web_database_migration_unittest.cc
+++ b/components/webdata/common/web_database_migration_unittest.cc
@@ -125,7 +125,7 @@
   DISALLOW_COPY_AND_ASSIGN(WebDatabaseMigrationTest);
 };
 
-const int WebDatabaseMigrationTest::kCurrentTestedVersionNumber = 94;
+const int WebDatabaseMigrationTest::kCurrentTestedVersionNumber = 95;
 
 void WebDatabaseMigrationTest::LoadDatabase(
     const base::FilePath::StringType& file) {
@@ -2077,3 +2077,52 @@
         connection.DoesColumnExist("offer_data", "usage_instructions_text"));
   }
 }
+
+// Tests addition of virtual_card_enrollment_state and card_art_url columns in
+// masked_credit_cards table.
+TEST_F(WebDatabaseMigrationTest, MigrateVersion94ToCurrent) {
+  ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_94.sql")));
+
+  // Verify pre-conditions.
+  {
+    sql::Database connection;
+    ASSERT_TRUE(connection.Open(GetDatabasePath()));
+    ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection));
+
+    sql::MetaTable meta_table;
+    ASSERT_TRUE(meta_table.Init(&connection, 94, 83));
+
+    EXPECT_FALSE(connection.DoesTableExist("credit_card_art_images"));
+
+    EXPECT_FALSE(connection.DoesColumnExist("masked_credit_cards",
+                                            "virtual_card_enrollment_state"));
+    EXPECT_FALSE(
+        connection.DoesColumnExist("masked_credit_cards", "card_art_url"));
+  }
+
+  DoMigration();
+
+  // Verify post-conditions.
+  {
+    sql::Database connection;
+    ASSERT_TRUE(connection.Open(GetDatabasePath()));
+    ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection));
+
+    // Check version.
+    EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection));
+
+    // The virtual_card_enrollment_state column and the card_art_url column
+    // should exist.
+    EXPECT_TRUE(connection.DoesColumnExist("masked_credit_cards",
+                                           "virtual_card_enrollment_state"));
+    EXPECT_TRUE(
+        connection.DoesColumnExist("masked_credit_cards", "card_art_url"));
+
+    // New columns in credit_card_art_images should exist.
+    EXPECT_TRUE(connection.DoesColumnExist("credit_card_art_images", "id"));
+    EXPECT_TRUE(
+        connection.DoesColumnExist("credit_card_art_images", "instrument_id"));
+    EXPECT_TRUE(
+        connection.DoesColumnExist("credit_card_art_images", "card_art_image"));
+  }
+}
diff --git a/content/browser/accessibility/ax_platform_node_textrangeprovider_win_browsertest.cc b/content/browser/accessibility/ax_platform_node_textrangeprovider_win_browsertest.cc
index 1a2a907e..e5cd8d5a9 100644
--- a/content/browser/accessibility/ax_platform_node_textrangeprovider_win_browsertest.cc
+++ b/content/browser/accessibility/ax_platform_node_textrangeprovider_win_browsertest.cc
@@ -540,8 +540,11 @@
       </html>
   )HTML"));
 
-  // Case 1: Inside of a plain text field, NormalizeTextRange shouldn't modify
-  //         the text range endpoints.
+  // Case 1: Inside of an atomic text field, NormalizeTextRange shouldn't modify
+  // the text range endpoints. An atomic text field does not expose its internal
+  // implementation to assistive software, appearing as a single leaf node in
+  // the accessibility tree. It includes <input>, <textarea> and Views-based
+  // text fields.
   //
   // In order for the test harness to effectively simulate typing in a text
   // input, first change the value of the text input and then focus it. Only
@@ -598,8 +601,8 @@
   ASSERT_EQ(0, result);
 
   // Calling GetAttributeValue will call NormalizeTextRange, which shouldn't
-  // change the result of CompareEndpoints below since the range is inside a
-  // plain text field.
+  // change the result of CompareEndpoints below since the range is inside an
+  // atomic text field.
   base::win::ScopedVariant value;
   EXPECT_HRESULT_SUCCEEDED(text_range_provider->GetAttributeValue(
       UIA_IsReadOnlyAttributeId, value.Receive()));
diff --git a/content/browser/accessibility/browser_accessibility.cc b/content/browser/accessibility/browser_accessibility.cc
index b38fb473..cfc2b936 100644
--- a/content/browser/accessibility/browser_accessibility.cc
+++ b/content/browser/accessibility/browser_accessibility.cc
@@ -56,7 +56,7 @@
 // is not a native text field (input or textarea).
 BrowserAccessibility* GetTextFieldInnerEditorElement(
     const BrowserAccessibility& text_field) {
-  if (!text_field.IsNativeTextField() || !text_field.InternalChildCount())
+  if (!text_field.IsAtomicTextField() || !text_field.InternalChildCount())
     return nullptr;
 
   // Text fields wrap their static text and inline text boxes in generic
@@ -131,9 +131,12 @@
 }
 
 bool BrowserAccessibility::IsValid() const {
-  // Currently we only perform validity checks on non-empty, native text fields.
-  if (IsNativeTextField() && InternalChildCount()) {
-    // If the native text field is aria-hidden then all its descendants are
+  // Currently we only perform validity checks on non-empty, atomic text fields.
+  // An atomic text field does not expose its internal implementation to
+  // assistive software, appearing as a single leaf node in the accessibility
+  // tree. It includes <input>, <textarea> and Views-based text fields.
+  if (IsAtomicTextField() && InternalChildCount()) {
+    // If the atomic text field is aria-hidden then all its descendants are
     // ignored.
     //   See the dump tree test AccessibilityAriaHiddenFocusedInput.
     //
@@ -1034,12 +1037,12 @@
   return GetData().IsPasswordField();
 }
 
-bool BrowserAccessibility::IsNativeTextField() const {
-  return GetData().IsNativeTextField();
+bool BrowserAccessibility::IsAtomicTextField() const {
+  return GetData().IsAtomicTextField();
 }
 
-bool BrowserAccessibility::IsNonNativeTextField() const {
-  return GetData().IsNonNativeTextField();
+bool BrowserAccessibility::IsNonAtomicTextField() const {
+  return GetData().IsNonAtomicTextField();
 }
 
 bool BrowserAccessibility::HasExplicitlyEmptyName() const {
@@ -1563,8 +1566,8 @@
   return false;
 }
 
-bool BrowserAccessibility::IsDescendantOfNativeTextField() const {
-  return node()->IsDescendantOfNativeTextField();
+bool BrowserAccessibility::IsDescendantOfAtomicTextField() const {
+  return node()->IsDescendantOfAtomicTextField();
 }
 
 gfx::NativeViewAccessible BrowserAccessibility::GetLowestPlatformAncestor()
@@ -2250,7 +2253,7 @@
   // and exposed on the text field itself. Otherwise, assistive software (AT)
   // won't be able to see them because the native field's descendants are an
   // implementation detail that is hidden from AT.
-  if (IsNativeTextField()) {
+  if (IsAtomicTextField()) {
     int start_offset = 0;
     for (BrowserAccessibility* static_text =
              BrowserAccessibilityManager::NextTextOnlyObject(
diff --git a/content/browser/accessibility/browser_accessibility.h b/content/browser/accessibility/browser_accessibility.h
index 45d948b..2ff07b809 100644
--- a/content/browser/accessibility/browser_accessibility.h
+++ b/content/browser/accessibility/browser_accessibility.h
@@ -348,11 +348,11 @@
   // See AXNodeData::IsPasswordField().
   bool IsPasswordField() const;
 
-  // See AXNodeData::IsNativeTextField().
-  bool IsNativeTextField() const;
+  // See AXNodeData::IsAtomicTextField().
+  bool IsAtomicTextField() const;
 
-  // See AXNodeData::IsNonNativeTextField().
-  bool IsNonNativeTextField() const;
+  // See AXNodeData::IsNonAtomicTextField().
+  bool IsNonAtomicTextField() const;
 
   // Returns true if the accessible name was explicitly set to "" by the author
   bool HasExplicitlyEmptyName() const;
@@ -400,7 +400,7 @@
   gfx::NativeViewAccessible GetPreviousSibling() override;
 
   bool IsChildOfLeaf() const override;
-  bool IsDescendantOfNativeTextField() const override;
+  bool IsDescendantOfAtomicTextField() const override;
   bool IsLeaf() const override;
   bool IsFocused() const override;
   bool IsInvisibleOrIgnored() const override;
diff --git a/content/browser/accessibility/browser_accessibility_android.cc b/content/browser/accessibility/browser_accessibility_android.cc
index 5cfdcb50..cd7df47 100644
--- a/content/browser/accessibility/browser_accessibility_android.cc
+++ b/content/browser/accessibility/browser_accessibility_android.cc
@@ -1906,7 +1906,7 @@
 
 int BrowserAccessibilityAndroid::GetSelectionStart() const {
   int sel_start = 0;
-  if (IsNativeTextField() &&
+  if (IsAtomicTextField() &&
       GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart, &sel_start)) {
     return sel_start;
   }
@@ -1928,7 +1928,7 @@
 
 int BrowserAccessibilityAndroid::GetSelectionEnd() const {
   int sel_end = 0;
-  if (IsNativeTextField() &&
+  if (IsAtomicTextField() &&
       GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, &sel_end)) {
     return sel_end;
   }
diff --git a/content/browser/accessibility/browser_accessibility_cocoa.mm b/content/browser/accessibility/browser_accessibility_cocoa.mm
index 7dee4394..efa116ac 100644
--- a/content/browser/accessibility/browser_accessibility_cocoa.mm
+++ b/content/browser/accessibility/browser_accessibility_cocoa.mm
@@ -389,8 +389,11 @@
     return {};
 
   // |anchor_offset| and / or |focus_offset| refer to a character offset if
-  // |anchor_object| / |focus_object| are text-only objects or native text
-  // fields. Otherwise, they should be treated as child indices.
+  // |anchor_object| / |focus_object| are text-only objects or atomic text
+  // fields. Otherwise, they should be treated as child indices. An atomic text
+  // field does not expose its internal implementation to assistive software,
+  // appearing as a single leaf node in the accessibility tree. It includes
+  // <input>, <textarea> and Views-based text fields.
   int anchor_offset = unignored_selection.anchor_offset;
   int focus_offset = unignored_selection.focus_offset;
   DCHECK_GE(anchor_offset, 0);
@@ -2380,7 +2383,7 @@
   if (![self instanceActive])
     return nil;
 
-  if (_owner->IsNativeTextField() &&
+  if (_owner->IsAtomicTextField() &&
       GetState(_owner, ax::mojom::State::kProtected)) {
     return NSAccessibilitySecureTextFieldSubrole;
   }
@@ -2714,7 +2717,7 @@
 }
 
 - (NSRect)frameForRange:(NSRange)range {
-  if (!_owner->IsText() && !_owner->IsNativeTextField())
+  if (!_owner->IsText() && !_owner->IsAtomicTextField())
     return CGRectNull;
   gfx::Rect rect = _owner->GetUnclippedRootFrameInnerTextRangeBoundsRect(
       range.location, NSMaxRange(range));
diff --git a/content/browser/accessibility/browser_accessibility_manager.cc b/content/browser/accessibility/browser_accessibility_manager.cc
index 563128716..c10ae16 100644
--- a/content/browser/accessibility/browser_accessibility_manager.cc
+++ b/content/browser/accessibility/browser_accessibility_manager.cc
@@ -1191,7 +1191,7 @@
 
   const BrowserAccessibility* start_text_object = nullptr;
   const BrowserAccessibility* end_text_object = nullptr;
-  if (&start_object == &end_object && start_object.IsNativeTextField()) {
+  if (&start_object == &end_object && start_object.IsAtomicTextField()) {
     // We need to get to the shadow DOM that is inside the text control in order
     // to find the text-only objects.
     if (!start_object.InternalChildCount())
@@ -1247,7 +1247,7 @@
   DCHECK_GE(start_offset, 0);
   DCHECK_GE(end_offset, 0);
 
-  if (&start_object == &end_object && start_object.IsNativeTextField()) {
+  if (&start_object == &end_object && start_object.IsAtomicTextField()) {
     if (start_offset > end_offset)
       std::swap(start_offset, end_offset);
 
@@ -1320,7 +1320,7 @@
   DCHECK_GE(start_offset, 0);
   DCHECK_GE(end_offset, 0);
 
-  if (&start_object == &end_object && start_object.IsNativeTextField()) {
+  if (&start_object == &end_object && start_object.IsAtomicTextField()) {
     if (start_offset > end_offset)
       std::swap(start_offset, end_offset);
 
diff --git a/content/browser/accessibility/browser_accessibility_manager_android.cc b/content/browser/accessibility/browser_accessibility_manager_android.cc
index f36a2de..afb61ad7 100644
--- a/content/browser/accessibility/browser_accessibility_manager_android.cc
+++ b/content/browser/accessibility/browser_accessibility_manager_android.cc
@@ -80,7 +80,7 @@
 
 BrowserAccessibility* BrowserAccessibilityManagerAndroid::GetFocus() const {
   BrowserAccessibility* focus = BrowserAccessibilityManager::GetFocus();
-  if (focus && !focus->IsNativeTextField())
+  if (focus && !focus->IsAtomicTextField())
     return GetActiveDescendant(focus);
   return focus;
 }
diff --git a/content/browser/accessibility/browser_accessibility_manager_win.cc b/content/browser/accessibility/browser_accessibility_manager_win.cc
index 0dbc7fe8..932d38d7 100644
--- a/content/browser/accessibility/browser_accessibility_manager_win.cc
+++ b/content/browser/accessibility/browser_accessibility_manager_win.cc
@@ -248,12 +248,12 @@
                 focus_object->GetTextFieldAncestor()) {
           EnqueueSelectionChangedEvent(*text_field);
 
-          // Plain text fields (including input and textarea elements) have
+          // Atomic text fields (including input and textarea elements) have
           // descendant objects that are part of their internal implementation
           // in Blink, which are not exposed to platform APIs in the
           // accessibility tree. Firing an event on such descendants will not
           // reach the assistive software.
-          if (text_field->IsNativeTextField()) {
+          if (text_field->IsAtomicTextField()) {
             FireWinAccessibilityEvent(IA2_EVENT_TEXT_CARET_MOVED, text_field);
           } else {
             FireWinAccessibilityEvent(IA2_EVENT_TEXT_CARET_MOVED, focus_object);
diff --git a/content/browser/accessibility/browser_accessibility_win_unittest.cc b/content/browser/accessibility/browser_accessibility_win_unittest.cc
index 10dbf1f..77816fc 100644
--- a/content/browser/accessibility/browser_accessibility_win_unittest.cc
+++ b/content/browser/accessibility/browser_accessibility_win_unittest.cc
@@ -2385,7 +2385,8 @@
   div_editable.role = ax::mojom::Role::kGenericContainer;
   div_editable.AddState(ax::mojom::State::kEditable);
   div_editable.AddState(ax::mojom::State::kRichlyEditable);
-  div_editable.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot, true);
+  div_editable.AddBoolAttribute(ax::mojom::BoolAttribute::kContentEditableRoot,
+                                true);
   div_editable.AddState(ax::mojom::State::kFocusable);
   div_editable.AddStringAttribute(ax::mojom::StringAttribute::kFontFamily,
                                   "Helvetica");
diff --git a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
index 54146dd..644741b4 100644
--- a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
+++ b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
@@ -25,6 +25,7 @@
 #include "components/download/public/common/download_task_runner.h"
 #include "content/browser/devtools/protocol/devtools_download_manager_delegate.h"
 #include "content/browser/devtools/protocol/devtools_protocol_test_support.h"
+#include "content/browser/devtools/render_frame_devtools_agent_host.h"
 #include "content/browser/download/download_manager_impl.h"
 #include "content/browser/renderer_host/navigator.h"
 #include "content/browser/renderer_host/render_widget_host_view_base.h"
@@ -1998,6 +1999,58 @@
   EXPECT_EQ(2u, found);
 }
 
+IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest,
+                       AutoAttachToOOPIFAfterNavigationStarted) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
+                           {"b.com"});
+  GURL a_url = embedded_test_server()->GetURL("a.com", "/title1.html");
+  EXPECT_TRUE(NavigateToURL(shell(), a_url));
+  RenderFrameHostImpl* main_frame = static_cast<RenderFrameHostImpl*>(
+      shell()->web_contents()->GetMainFrame());
+
+  // Create iframe and start navigation.
+  GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
+  TestNavigationManager navigation_manager(shell()->web_contents(), b_url);
+  EXPECT_TRUE(ExecJs(
+      main_frame, JsReplace("const iframe = document.createElement('iframe');\n"
+                            "iframe.src = $1;\n"
+                            "document.body.appendChild(iframe);\n",
+                            b_url)));
+  // Pause subframe navigation.
+  EXPECT_TRUE(navigation_manager.WaitForRequestStart());
+
+  // Attach to DevTools after subframe starts navigating (but before it
+  // finishes).
+  Attach();
+
+  DevToolsAgentHostImpl* main_frame_agent =
+      RenderFrameDevToolsAgentHost::GetFor(main_frame);
+  EXPECT_NE(main_frame_agent, nullptr);
+
+  // Start auto-attach.
+  std::unique_ptr<base::DictionaryValue> command_params;
+  command_params = std::make_unique<base::DictionaryValue>();
+  command_params->SetBoolean("autoAttach", true);
+  command_params->SetBoolean("waitForDebuggerOnStart", false);
+  command_params->SetBoolean("flatten", true);
+  SendCommand("Target.setAutoAttach", std::move(command_params));
+
+  // Child frame should be created at this point, but isn't an OOPIF yet, so
+  // shouldn't have its own DevToolsAgentHost yet.
+  FrameTreeNode* child = main_frame->child_at(0);
+  EXPECT_NE(child, nullptr);
+  EXPECT_EQ(RenderFrameDevToolsAgentHost::GetFor(child), main_frame_agent);
+
+  // Resume navigation.
+  navigation_manager.WaitForNavigationFinished();
+
+  // Target for OOPIF should get attached.
+  auto notification = WaitForNotification("Target.attachedToTarget", true);
+  EXPECT_NE(RenderFrameDevToolsAgentHost::GetFor(child), main_frame_agent);
+  EXPECT_FALSE(notification->FindBoolPath("waitingForDebugger").value());
+}
+
 class DevToolsProtocolDeviceEmulationTest : public DevToolsProtocolTest {
  public:
   ~DevToolsProtocolDeviceEmulationTest() override {}
diff --git a/content/browser/devtools/protocol/target_auto_attacher.cc b/content/browser/devtools/protocol/target_auto_attacher.cc
index d49b2af6..ef9a9ba 100644
--- a/content/browser/devtools/protocol/target_auto_attacher.cc
+++ b/content/browser/devtools/protocol/target_auto_attacher.cc
@@ -106,8 +106,8 @@
   if (render_frame_host) {
     for (FrameTreeNode* node : render_frame_host->frame_tree()->Nodes()) {
       frame_urls.insert(node->current_url());
-      // We use both old and new frame urls to support [3], where we attach while
-      // navigation is still ongoing.
+      // We use both old and new frame urls to support [3], where we attach
+      // while navigation is still ongoing.
       if (node->navigation_request()) {
         frame_urls.insert(node->navigation_request()->common_params().url);
       }
@@ -209,14 +209,25 @@
 }
 
 void TargetAutoAttacher::AttachToAgentHost(DevToolsAgentHost* host) {
+  AttachToAgentHost(host, wait_for_debugger_on_start_);
+}
+
+void TargetAutoAttacher::AttachToAgentHost(DevToolsAgentHost* host,
+                                           bool wait_for_debugger_on_start) {
   scoped_refptr<DevToolsAgentHost> agent_host(host);
   DCHECK(auto_attached_hosts_.find(agent_host) == auto_attached_hosts_.end());
-  delegate_->AutoAttach(agent_host.get(), wait_for_debugger_on_start_);
+  delegate_->AutoAttach(agent_host.get(), wait_for_debugger_on_start);
   auto_attached_hosts_.insert(agent_host);
 }
 
 DevToolsAgentHost* TargetAutoAttacher::AutoAttachToFrame(
     NavigationRequest* navigation_request) {
+  return AutoAttachToFrame(navigation_request, wait_for_debugger_on_start_);
+}
+
+DevToolsAgentHost* TargetAutoAttacher::AutoAttachToFrame(
+    NavigationRequest* navigation_request,
+    bool wait_for_debugger_on_start) {
   if (!ShouldThrottleFramesNavigation())
     return nullptr;
 
@@ -246,8 +257,8 @@
               navigation_request);
     }
     if (auto_attached_hosts_.find(agent_host) == auto_attached_hosts_.end()) {
-      AttachToAgentHost(agent_host.get());
-      return wait_for_debugger_on_start_ ? agent_host.get() : nullptr;
+      AttachToAgentHost(agent_host.get(), wait_for_debugger_on_start);
+      return wait_for_debugger_on_start ? agent_host.get() : nullptr;
     }
     return nullptr;
   }
@@ -382,5 +393,33 @@
   auto_attached_hosts_.insert(scoped_refptr<DevToolsAgentHost>(agent_host));
 }
 
+void TargetAutoAttacher::DidFinishNavigation(
+    NavigationRequest* navigation_request) {
+  if (!render_frame_host_)
+    return;
+
+  if (navigation_request->frame_tree_node() ==
+      render_frame_host_->frame_tree_node()) {
+    UpdateServiceWorkers();
+    return;
+  }
+
+  // We only care about subframes that have |render_frame_host_| as their local
+  // root.
+  if (!navigation_request->HasCommitted())
+    return;
+  RenderFrameHostImpl* parent = navigation_request->GetParentFrame();
+  while (parent && !parent->is_local_root())
+    parent = parent->GetParent();
+  if (parent != render_frame_host_)
+    return;
+
+  // Some subframes may not be attached through TargetHandler::ResponseThrottle
+  // because DevTools wasn't attached when the navigation started, so no
+  // throttle was installed. We auto-attach them here instead (note that
+  // we cannot honor |wait_for_debugger_on_start_| in this case).
+  AutoAttachToFrame(navigation_request, false);
+}
+
 }  // namespace protocol
 }  // namespace content
diff --git a/content/browser/devtools/protocol/target_auto_attacher.h b/content/browser/devtools/protocol/target_auto_attacher.h
index 96e72cc13..e0572cd6 100644
--- a/content/browser/devtools/protocol/target_auto_attacher.h
+++ b/content/browser/devtools/protocol/target_auto_attacher.h
@@ -48,6 +48,7 @@
   DevToolsAgentHost* AutoAttachToFrame(NavigationRequest* navigation_request);
   void ChildWorkerCreated(DevToolsAgentHostImpl* agent_host,
                           bool waiting_for_debugger);
+  void DidFinishNavigation(NavigationRequest* navigation_handle);
 
  private:
   using Hosts = base::flat_set<scoped_refptr<DevToolsAgentHost>>;
@@ -62,6 +63,11 @@
                      bool* should_pause_on_start) override;
   void WorkerDestroyed(ServiceWorkerDevToolsAgentHost* host) override;
 
+  void AttachToAgentHost(DevToolsAgentHost* host,
+                         bool wait_for_debugger_on_start);
+  DevToolsAgentHost* AutoAttachToFrame(NavigationRequest* navigation_request,
+                                       bool wait_for_debugger_on_start);
+
   void UpdateFrames();
   bool is_browser_mode() const { return !renderer_channel_; }
 
diff --git a/content/browser/devtools/protocol/target_handler.cc b/content/browser/devtools/protocol/target_handler.cc
index 58bb6fe..d6961592 100644
--- a/content/browser/devtools/protocol/target_handler.cc
+++ b/content/browser/devtools/protocol/target_handler.cc
@@ -617,8 +617,9 @@
   return Response::Success();
 }
 
-void TargetHandler::DidFinishNavigation() {
-  auto_attacher_.UpdateServiceWorkers();
+void TargetHandler::DidFinishNavigation(NavigationHandle* navigation_handle) {
+  auto_attacher_.DidFinishNavigation(
+      NavigationRequest::From(navigation_handle));
 }
 
 std::unique_ptr<NavigationThrottle> TargetHandler::CreateThrottleForNavigation(
diff --git a/content/browser/devtools/protocol/target_handler.h b/content/browser/devtools/protocol/target_handler.h
index 83437c80..7cc3b67 100644
--- a/content/browser/devtools/protocol/target_handler.h
+++ b/content/browser/devtools/protocol/target_handler.h
@@ -57,7 +57,7 @@
                    RenderFrameHostImpl* frame_host) override;
   Response Disable() override;
 
-  void DidFinishNavigation();
+  void DidFinishNavigation(NavigationHandle* navigation_handle);
   std::unique_ptr<NavigationThrottle> CreateThrottleForNavigation(
       NavigationHandle* navigation_handle);
   void UpdatePortals();
diff --git a/content/browser/devtools/render_frame_devtools_agent_host.cc b/content/browser/devtools/render_frame_devtools_agent_host.cc
index 03f9227e..160a34f 100644
--- a/content/browser/devtools/render_frame_devtools_agent_host.cc
+++ b/content/browser/devtools/render_frame_devtools_agent_host.cc
@@ -425,25 +425,25 @@
 void RenderFrameDevToolsAgentHost::DidFinishNavigation(
     NavigationHandle* navigation_handle) {
   NavigationRequest* request = NavigationRequest::From(navigation_handle);
-  if (request->frame_tree_node() != frame_tree_node_)
-    return;
-  navigation_requests_.erase(request);
-  if (request->HasCommitted())
-    NotifyNavigated();
+  if (request->frame_tree_node() == frame_tree_node_) {
+    navigation_requests_.erase(request);
+    if (request->HasCommitted())
+      NotifyNavigated();
 
-  if (IsAttached()) {
-    UpdateRawHeadersAccess(frame_tree_node_->current_frame_host());
-  }
-  // UpdateFrameHost may destruct |this|.
-  scoped_refptr<RenderFrameDevToolsAgentHost> protect(this);
-  UpdateFrameHost(frame_tree_node_->current_frame_host());
+    if (IsAttached()) {
+      UpdateRawHeadersAccess(frame_tree_node_->current_frame_host());
+    }
+    // UpdateFrameHost may destruct |this|.
+    scoped_refptr<RenderFrameDevToolsAgentHost> protect(this);
+    UpdateFrameHost(frame_tree_node_->current_frame_host());
 
-  if (navigation_requests_.empty()) {
-    for (DevToolsSession* session : sessions())
-      session->ResumeSendingMessagesToAgent();
+    if (navigation_requests_.empty()) {
+      for (DevToolsSession* session : sessions())
+        session->ResumeSendingMessagesToAgent();
+    }
   }
   for (auto* target : protocol::TargetHandler::ForAgentHost(this))
-    target->DidFinishNavigation();
+    target->DidFinishNavigation(navigation_handle);
 }
 
 void RenderFrameDevToolsAgentHost::UpdateFrameHost(
diff --git a/content/browser/file_system_access/file_system_access_file_writer_impl.cc b/content/browser/file_system_access/file_system_access_file_writer_impl.cc
index ee60c5e..def6d65f 100644
--- a/content/browser/file_system_access/file_system_access_file_writer_impl.cc
+++ b/content/browser/file_system_access/file_system_access_file_writer_impl.cc
@@ -151,21 +151,22 @@
 }
 
 FileSystemAccessFileWriterImpl::~FileSystemAccessFileWriterImpl() {
-  // Purge the swap file. The swap file should be deleted after Close(), but
-  // we'll try to delete it anyways in case the writer wasn't closed cleanly.
-  DoFileSystemOperation(
-      FROM_HERE, &FileSystemOperationRunner::RemoveFile,
-      base::BindOnce(
-          [](const storage::FileSystemURL& swap_url, base::File::Error result) {
-            if (result != base::File::FILE_OK &&
-                result != base::File::FILE_ERROR_NOT_FOUND) {
-              DLOG(ERROR) << "Error Deleting Swap File, status: "
-                          << base::File::ErrorToString(result)
-                          << " path: " << swap_url.path();
-            }
-          },
-          swap_url()),
-      swap_url());
+  if (should_purge_swap_file_on_destruction_) {
+    DoFileSystemOperation(
+        FROM_HERE, &FileSystemOperationRunner::RemoveFile,
+        base::BindOnce(
+            [](const storage::FileSystemURL& swap_url,
+               base::File::Error result) {
+              if (result != base::File::FILE_OK &&
+                  result != base::File::FILE_ERROR_NOT_FOUND) {
+                DLOG(ERROR) << "Error Deleting Swap File, status: "
+                            << base::File::ErrorToString(result)
+                            << " path: " << swap_url.path();
+              }
+            },
+            swap_url()),
+        swap_url());
+  }
 }
 
 void FileSystemAccessFileWriterImpl::Write(
@@ -228,6 +229,8 @@
 // Do not call this method if |close_callback_| is not set.
 void FileSystemAccessFileWriterImpl::CallCloseCallbackAndDeleteThis(
     blink::mojom::FileSystemAccessErrorPtr result) {
+  should_purge_swap_file_on_destruction_ =
+      result->status != blink::mojom::FileSystemAccessStatus::kOk;
   std::move(close_callback_).Run(std::move(result));
 
   // |this| is deleted after this call.
diff --git a/content/browser/file_system_access/file_system_access_file_writer_impl.h b/content/browser/file_system_access/file_system_access_file_writer_impl.h
index 58e8c33..4d1881e5 100644
--- a/content/browser/file_system_access/file_system_access_file_writer_impl.h
+++ b/content/browser/file_system_access/file_system_access_file_writer_impl.h
@@ -142,6 +142,11 @@
   // explicitly closed.
   bool auto_close_ = false;
 
+  // The writer should not attempt to purge the swap file if the move operation
+  // to the target file is successful, since this may incidentally remove the
+  // active swap file of a different writer.
+  bool should_purge_swap_file_on_destruction_ = true;
+
   base::WeakPtr<FileSystemAccessHandleBase> AsWeakPtr() override;
 
   base::WeakPtrFactory<FileSystemAccessFileWriterImpl> weak_factory_{this};
diff --git a/content/browser/interest_group/ad_auction_service_impl.cc b/content/browser/interest_group/ad_auction_service_impl.cc
index 6bbdba7..09bfe18 100644
--- a/content/browser/interest_group/ad_auction_service_impl.cc
+++ b/content/browser/interest_group/ad_auction_service_impl.cc
@@ -14,6 +14,7 @@
 #include "base/optional.h"
 #include "base/strings/stringprintf.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
+#include "content/browser/service_sandbox_type.h"
 #include "content/browser/storage_partition_impl.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/content_browser_client.h"
@@ -206,7 +207,7 @@
   content::ServiceProcessHost::Launch(
       auction_worklet_service_.BindNewPipeAndPassReceiver(),
       ServiceProcessHost::Options()
-          .WithDisplayName("Auction Worklet Sevice")
+          .WithDisplayName("Auction Worklet Service")
           .Pass());
 }
 
diff --git a/content/browser/media/key_system_support_impl.cc b/content/browser/media/key_system_support_impl.cc
index 8bb568b7..1e09146 100644
--- a/content/browser/media/key_system_support_impl.cc
+++ b/content/browser/media/key_system_support_impl.cc
@@ -80,11 +80,12 @@
     return base::nullopt;
   }
 
-  // Overridden codecs assume CENC/CBCS and temporary session support.
-  return media::CdmCapability(
-      std::move(video_codecs),
-      {media::EncryptionScheme::kCenc, media::EncryptionScheme::kCbcs},
-      {media::CdmSessionType::kTemporary});
+  // Overridden codecs assume CENC and temporary session support.
+  // The EncryptedMediaSupportedTypesWidevineHwSecureTest tests depend
+  // on 'cbcs' not being supported.
+  return media::CdmCapability(std::move(video_codecs),
+                              {media::EncryptionScheme::kCenc},
+                              {media::CdmSessionType::kTemporary});
 }
 
 // Software secure capability can be obtained synchronously in all supported
diff --git a/content/browser/media/media_interface_proxy.cc b/content/browser/media/media_interface_proxy.cc
index a8e6d726..df82683 100644
--- a/content/browser/media/media_interface_proxy.cc
+++ b/content/browser/media/media_interface_proxy.cc
@@ -26,6 +26,7 @@
 #include "media/mojo/buildflags.h"
 #include "media/mojo/mojom/frame_interface_factory.mojom.h"
 #include "media/mojo/mojom/media_service.mojom.h"
+#include "mojo/public/cpp/bindings/callback_helpers.h"
 
 #if BUILDFLAG(ENABLE_MOJO_CDM)
 #include "content/public/browser/browser_context.h"
@@ -372,10 +373,16 @@
 
 void MediaInterfaceProxy::CreateCdm(const std::string& key_system,
                                     const media::CdmConfig& cdm_config,
-                                    CreateCdmCallback callback) {
+                                    CreateCdmCallback create_cdm_cb) {
   DCHECK(thread_checker_.CalledOnValidThread());
   DVLOG(1) << __func__ << ": key_system=" << key_system;
 
+  // The remote process may drop the callback (e.g. in case of crash). Doing it
+  // here instead of in the renderer process because the browser is trusted.
+  auto callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
+      std::move(create_cdm_cb), mojo::NullRemote(), nullptr,
+      "CDM creation failed");
+
   // Handle `use_hw_secure_codecs` cases first.
 #if BUILDFLAG(IS_CHROMEOS_ASH) && BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA)
   if (base::FeatureList::IsEnabled(chromeos::features::kCdmFactoryDaemon) &&
diff --git a/content/browser/media/media_interface_proxy.h b/content/browser/media/media_interface_proxy.h
index 4def9d7c9..bf05c14b 100644
--- a/content/browser/media/media_interface_proxy.h
+++ b/content/browser/media/media_interface_proxy.h
@@ -89,7 +89,7 @@
 #endif  // defined(OS_WIN)
   void CreateCdm(const std::string& key_system,
                  const media::CdmConfig& cdm_config,
-                 CreateCdmCallback callback) final;
+                 CreateCdmCallback create_cdm_cb) final;
 
  private:
   // Gets services provided by the browser (at RenderFrameHost level) to the
diff --git a/content/browser/service_sandbox_type.h b/content/browser/service_sandbox_type.h
index cde29aed..350b237 100644
--- a/content/browser/service_sandbox_type.h
+++ b/content/browser/service_sandbox_type.h
@@ -16,6 +16,18 @@
 // require a non-utility sandbox can be added here.  See
 // ServiceProcessHost::Launch() for how these templates are consumed.
 
+// auction_worklet::mojom::AdAuctionService
+namespace auction_worklet {
+namespace mojom {
+class AdAuctionService;
+}
+}  // namespace auction_worklet
+template <>
+inline sandbox::policy::SandboxType
+content::GetServiceSandboxType<auction_worklet::mojom::AdAuctionService>() {
+  return sandbox::policy::SandboxType::kService;
+}
+
 // audio::mojom::AudioService
 namespace audio {
 namespace mojom {
diff --git a/content/browser/webid/federated_auth_request_impl.cc b/content/browser/webid/federated_auth_request_impl.cc
index 30564607..c5cdf39 100644
--- a/content/browser/webid/federated_auth_request_impl.cc
+++ b/content/browser/webid/federated_auth_request_impl.cc
@@ -15,6 +15,7 @@
 #include "content/public/common/content_client.h"
 #include "url/url_constants.h"
 
+using blink::mojom::LogoutStatus;
 using blink::mojom::RequestIdTokenStatus;
 using blink::mojom::RequestMode;
 using UserApproval = content::IdentityRequestDialogController::UserApproval;
@@ -59,12 +60,12 @@
                                               const std::string& id_request,
                                               RequestMode mode,
                                               RequestIdTokenCallback callback) {
-  if (callback_) {
+  if (logout_callback_ || auth_request_callback_) {
     std::move(callback).Run(RequestIdTokenStatus::kErrorTooManyRequests, "");
     return;
   }
 
-  callback_ = std::move(callback);
+  auth_request_callback_ = std::move(callback);
   provider_ = provider;
   id_request_ = id_request;
   mode_ = mode;
@@ -106,6 +107,39 @@
   }
 }
 
+// TODO(kenrb): Depending on how this code evolves, it might make sense to
+// spin session management code into its own service. The prohibition on
+// making authentication requests and logout requests at the same time, while
+// not problematic for any plausible use case, need not be strictly necessary
+// if there is a good way to not have to resource contention between requests.
+// https://crbug.com/1200581
+void FederatedAuthRequestImpl::Logout(
+    const std::vector<std::string>& logout_endpoints,
+    LogoutCallback callback) {
+  if (logout_callback_ || auth_request_callback_) {
+    std::move(callback).Run(LogoutStatus::kErrorTooManyRequests);
+    return;
+  }
+
+  if (logout_endpoints.empty()) {
+    std::move(callback).Run(LogoutStatus::kError);
+    return;
+  }
+
+  logout_callback_ = std::move(callback);
+  logout_endpoints_ = std::move(logout_endpoints);
+
+  network_manager_ = CreateNetworkManager(origin().GetURL());
+  if (!network_manager_) {
+    CompleteLogoutRequest(LogoutStatus::kError);
+    return;
+  }
+
+  // TODO(kenrb): These should be parallelized rather than being dispatched
+  // serially. https://crbug.com/1200581.
+  DispatchOneLogout();
+}
+
 void FederatedAuthRequestImpl::OnWellKnownFetched(
     IdpNetworkRequestManager::FetchStatus status,
     IdpNetworkRequestManager::Endpoints endpoints) {
@@ -383,6 +417,44 @@
   }
 }
 
+void FederatedAuthRequestImpl::DispatchOneLogout() {
+  GURL endpoint = GURL(logout_endpoints_.back());
+  logout_endpoints_.pop_back();
+
+  // TODO(kenrb): Validate that |endpoint| is a legal target under whatever
+  // policy we decide is appropriate.
+
+  if (endpoint.is_valid()) {
+    network_manager_->SendLogout(
+        endpoint, base::BindOnce(&FederatedAuthRequestImpl::OnLogoutCompleted,
+                                 weak_ptr_factory_.GetWeakPtr()));
+  } else {
+    logout_status_ = blink::mojom::LogoutStatus::kError;
+    if (logout_endpoints_.empty()) {
+      CompleteLogoutRequest(logout_status_);
+      return;
+    }
+
+    DispatchOneLogout();
+  }
+}
+
+void FederatedAuthRequestImpl::OnLogoutCompleted(
+    IdpNetworkRequestManager::LogoutResponse status) {
+  // Return an error overall for the call if any one request returns an error.
+  if (logout_status_ == blink::mojom::LogoutStatus::kSuccess &&
+      status != IdpNetworkRequestManager::LogoutResponse::kSuccess) {
+    logout_status_ = blink::mojom::LogoutStatus::kError;
+  }
+
+  if (logout_endpoints_.empty()) {
+    CompleteLogoutRequest(logout_status_);
+    return;
+  }
+
+  DispatchOneLogout();
+}
+
 std::unique_ptr<WebContents> FederatedAuthRequestImpl::CreateIdpWebContents() {
   auto idp_web_contents = content::WebContents::Create(
       WebContents::CreateParams(render_frame_host()->GetBrowserContext()));
@@ -405,8 +477,15 @@
   // Given that |request_dialog_controller_| has reference to this web content
   // instance we destroy that first.
   idp_web_contents_.reset();
-  if (callback_)
-    std::move(callback_).Run(status, id_token);
+  if (auth_request_callback_)
+    std::move(auth_request_callback_).Run(status, id_token);
+}
+
+void FederatedAuthRequestImpl::CompleteLogoutRequest(
+    blink::mojom::LogoutStatus status) {
+  network_manager_.reset();
+  if (logout_callback_)
+    std::move(logout_callback_).Run(status);
 }
 
 std::unique_ptr<IdpNetworkRequestManager>
diff --git a/content/browser/webid/federated_auth_request_impl.h b/content/browser/webid/federated_auth_request_impl.h
index f6f10366..e602cc6 100644
--- a/content/browser/webid/federated_auth_request_impl.h
+++ b/content/browser/webid/federated_auth_request_impl.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "base/callback_forward.h"
 #include "base/macros.h"
@@ -52,6 +53,8 @@
                       const std::string& id_request,
                       blink::mojom::RequestMode mode,
                       RequestIdTokenCallback) override;
+  void Logout(const std::vector<std::string>& logout_endpoints,
+              LogoutCallback) override;
 
   void SetNetworkManagerForTests(
       std::unique_ptr<IdpNetworkRequestManager> manager);
@@ -75,9 +78,12 @@
   void OnAccountSelected(const std::string& account_id);
   void OnTokenResponseReceived(IdpNetworkRequestManager::TokenResponse status,
                                const std::string& id_token);
+  void DispatchOneLogout();
+  void OnLogoutCompleted(IdpNetworkRequestManager::LogoutResponse status);
   std::unique_ptr<WebContents> CreateIdpWebContents();
   void CompleteRequest(blink::mojom::RequestIdTokenStatus,
                        const std::string& id_token);
+  void CompleteLogoutRequest(blink::mojom::LogoutStatus);
 
   std::unique_ptr<IdpNetworkRequestManager> CreateNetworkManager(
       const GURL& provider);
@@ -119,7 +125,12 @@
       sharing_permission_delegate_ = nullptr;
 
   std::string id_token_;
-  RequestIdTokenCallback callback_;
+  RequestIdTokenCallback auth_request_callback_;
+
+  std::vector<std::string> logout_endpoints_;
+  blink::mojom::LogoutStatus logout_status_ =
+      blink::mojom::LogoutStatus::kSuccess;
+  LogoutCallback logout_callback_;
 
   base::WeakPtrFactory<FederatedAuthRequestImpl> weak_ptr_factory_{this};
 };
diff --git a/content/browser/webid/federated_auth_request_impl_unittest.cc b/content/browser/webid/federated_auth_request_impl_unittest.cc
index 10ec6a7..8b77db3 100644
--- a/content/browser/webid/federated_auth_request_impl_unittest.cc
+++ b/content/browser/webid/federated_auth_request_impl_unittest.cc
@@ -23,12 +23,14 @@
 #include "third_party/blink/public/mojom/webid/federated_auth_request.mojom.h"
 #include "url/gurl.h"
 
+using blink::mojom::LogoutStatus;
 using blink::mojom::RequestIdTokenStatus;
 using blink::mojom::RequestMode;
 using AccountsResponse = content::IdpNetworkRequestManager::AccountsResponse;
-using TokenResponse = content::IdpNetworkRequestManager::TokenResponse;
 using FetchStatus = content::IdpNetworkRequestManager::FetchStatus;
+using LogoutResponse = content::IdpNetworkRequestManager::LogoutResponse;
 using SigninResponse = content::IdpNetworkRequestManager::SigninResponse;
+using TokenResponse = content::IdpNetworkRequestManager::TokenResponse;
 using UserApproval = content::IdentityRequestDialogController::UserApproval;
 using ::testing::_;
 using ::testing::Invoke;
@@ -306,6 +308,44 @@
   base::Optional<std::string> token_;
 };
 
+// Helper class for receiving the Logout method callback.
+class LogoutRequestCallbackHelper {
+ public:
+  LogoutRequestCallbackHelper() = default;
+  ~LogoutRequestCallbackHelper() = default;
+
+  LogoutRequestCallbackHelper(const LogoutRequestCallbackHelper&) = delete;
+  LogoutRequestCallbackHelper& operator=(const LogoutRequestCallbackHelper&) =
+      delete;
+
+  LogoutStatus status() const { return status_; }
+
+  // This can only be called once per lifetime of this object.
+  base::OnceCallback<void(LogoutStatus)> callback() {
+    return base::BindOnce(&LogoutRequestCallbackHelper::ReceiverMethod,
+                          base::Unretained(this));
+  }
+
+  // Returns when callback() is called, which can be immediately if it has
+  // already been called.
+  void WaitForCallback() {
+    if (was_called_)
+      return;
+    wait_for_callback_loop_.Run();
+  }
+
+ private:
+  void ReceiverMethod(LogoutStatus status) {
+    status_ = status;
+    was_called_ = true;
+    wait_for_callback_loop_.Quit();
+  }
+
+  bool was_called_ = false;
+  base::RunLoop wait_for_callback_loop_;
+  LogoutStatus status_;
+};
+
 }  // namespace
 
 class FederatedAuthRequestImplTest : public RenderViewHostTestHarness {
@@ -341,6 +381,17 @@
     return std::make_pair(auth_helper.status(), auth_helper.token());
   }
 
+  LogoutStatus PerformLogoutRequest(
+      const std::vector<std::string>& logout_endpoints) {
+    auth_request_impl_->SetNetworkManagerForTests(
+        std::move(mock_request_manager_));
+
+    LogoutRequestCallbackHelper logout_helper;
+    request_remote_->Logout(logout_endpoints, logout_helper.callback());
+    logout_helper.WaitForCallback();
+    return logout_helper.status();
+  }
+
   void SetPermissionMockExpectations(const MockPermissionConfiguration& conf,
                                      std::string token) {
     if (conf.signin_response) {
@@ -453,6 +504,24 @@
                                 test_case.config.token);
   }
 
+  // Expectations have to be set explicitly in advance using
+  // logout_return_status() and logout_endpoints().
+  void SetLogoutMockExpectations() {
+    static int count = 0;
+    EXPECT_CALL(*mock_request_manager_, SendLogout(_, _))
+        .Times(logout_endpoints_.size())
+        .WillRepeatedly(
+            Invoke([&](const GURL& logout_endpoint,
+                       IdpNetworkRequestManager::LogoutCallback callback) {
+              std::move(callback).Run(logout_return_status_[count++]);
+            }));
+  }
+
+  std::vector<LogoutResponse>& logout_return_status() {
+    return logout_return_status_;
+  }
+  std::vector<std::string>& logout_endpoints() { return logout_endpoints_; }
+
  private:
   mojo::Remote<blink::mojom::FederatedAuthRequest> request_remote_;
   std::unique_ptr<FederatedAuthRequestImpl> auth_request_impl_;
@@ -463,6 +532,10 @@
 
   base::OnceClosure close_idp_window_callback_;
 
+  // Test case storage for Logout tests.
+  std::vector<LogoutResponse> logout_return_status_;
+  std::vector<std::string> logout_endpoints_;
+
   GURL provider_;
 };
 
@@ -491,4 +564,56 @@
   EXPECT_EQ(auth_response.second, test_case.expected.token);
 }
 
+// Test Logout method success with multiple relying parties.
+TEST_F(BasicFederatedAuthRequestImplTest, LogoutSuccessMultiple) {
+  CreateAuthRequest(GURL(kIdpTestOrigin));
+
+  logout_endpoints().push_back("https://rp1.example");
+  logout_return_status().push_back(LogoutResponse::kSuccess);
+  logout_endpoints().push_back("https://rp2.example");
+  logout_return_status().push_back(LogoutResponse::kSuccess);
+  logout_endpoints().push_back("https://rp3.example");
+  logout_return_status().push_back(LogoutResponse::kSuccess);
+  SetLogoutMockExpectations();
+
+  auto logout_response = PerformLogoutRequest(logout_endpoints());
+  EXPECT_EQ(logout_response, LogoutStatus::kSuccess);
+}
+
+// Test Logout method with an invalid endpoint URL.
+TEST_F(BasicFederatedAuthRequestImplTest, LogoutInvalidEndpoint) {
+  CreateAuthRequest(GURL(kIdpTestOrigin));
+
+  logout_endpoints().push_back("Invalid string");
+
+  // No need to set mock expectations here because it should not attempt to
+  // send anything.
+  auto logout_response = PerformLogoutRequest(logout_endpoints());
+  EXPECT_EQ(logout_response, LogoutStatus::kError);
+}
+
+// Test an error is returned if one logout request fails.
+TEST_F(BasicFederatedAuthRequestImplTest, LogoutSingleFailure) {
+  CreateAuthRequest(GURL(kIdpTestOrigin));
+
+  logout_endpoints().push_back("https://rp1.example");
+  logout_return_status().push_back(LogoutResponse::kSuccess);
+  logout_endpoints().push_back("https://rp2.example");
+  logout_return_status().push_back(LogoutResponse::kError);
+
+  SetLogoutMockExpectations();
+  auto logout_response = PerformLogoutRequest(logout_endpoints());
+  EXPECT_EQ(logout_response, LogoutStatus::kError);
+}
+
+// Test Logout method with an empty endpoint vector.
+TEST_F(BasicFederatedAuthRequestImplTest, LogoutNoEndpoints) {
+  CreateAuthRequest(GURL(kIdpTestOrigin));
+
+  // No need to set mock expectations here because it should not attempt to
+  // send anything.
+  auto logout_response = PerformLogoutRequest(logout_endpoints());
+  EXPECT_EQ(logout_response, LogoutStatus::kError);
+}
+
 }  // namespace content
diff --git a/content/browser/webid/idp_network_request_manager.cc b/content/browser/webid/idp_network_request_manager.cc
index 1cbf1d3..00634e5d 100644
--- a/content/browser/webid/idp_network_request_manager.cc
+++ b/content/browser/webid/idp_network_request_manager.cc
@@ -95,10 +95,6 @@
   resource_request->site_for_cookies = site_for_cookies;
   resource_request->headers.SetHeader(net::HttpRequestHeaders::kAccept,
                                       kJSONMimeType);
-  // This header is present exclusively for CSRF resistance.
-  // TODO(kenrb): To avoid the value being depended on  set it to a random
-  // value. We can later change it if something more useful e.g., a version is
-  // needed. https://crbug.com/1196371
   resource_request->headers.SetHeader(kSecWebIdCsrfHeader, "");
   resource_request->credentials_mode =
       network::mojom::CredentialsMode::kInclude;
@@ -320,6 +316,33 @@
       maxResponseSizeInKiB * 1024);
 }
 
+void IdpNetworkRequestManager::SendLogout(const GURL& logout_url,
+                                          LogoutCallback callback) {
+  // TODO(kenrb): Add browser test verifying that the response to this can
+  // clear cookies. https://crbug.com/1155312.
+  DCHECK(!url_loader_);
+  DCHECK(!logout_callback_);
+
+  logout_callback_ = std::move(callback);
+
+  auto resource_request = CreateCredentialedResourceRequest(
+      logout_url, render_frame_host_->GetLastCommittedOrigin());
+  resource_request->headers.SetHeader(net::HttpRequestHeaders::kAccept, "*/*");
+
+  auto traffic_annotation = CreateTrafficAnnotation();
+
+  url_loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
+                                                 traffic_annotation);
+
+  auto loader_factory = GetUrlLoaderFactory(render_frame_host_);
+
+  url_loader_->DownloadToString(
+      loader_factory.get(),
+      base::BindOnce(&IdpNetworkRequestManager::OnLogoutCompleted,
+                     weak_ptr_factory_.GetWeakPtr()),
+      maxResponseSizeInKiB * 1024);
+}
+
 // static
 bool IdpNetworkRequestManager::ParseAccounts(
     const base::Value* accounts,
@@ -559,4 +582,20 @@
       .Run(TokenResponse::kSuccess, id_token->GetString());
 }
 
+void IdpNetworkRequestManager::OnLogoutCompleted(
+    std::unique_ptr<std::string> response_body) {
+  int response_code = -1;
+  if (url_loader_->ResponseInfo() && url_loader_->ResponseInfo()->headers)
+    response_code = url_loader_->ResponseInfo()->headers->response_code();
+
+  url_loader_.reset();
+
+  if (!response_body) {
+    std::move(logout_callback_).Run(LogoutResponse::kError);
+    return;
+  }
+
+  std::move(logout_callback_).Run(LogoutResponse::kSuccess);
+}
+
 }  // namespace content
diff --git a/content/browser/webid/idp_network_request_manager.h b/content/browser/webid/idp_network_request_manager.h
index 866b9f24..a90c741a 100644
--- a/content/browser/webid/idp_network_request_manager.h
+++ b/content/browser/webid/idp_network_request_manager.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "base/callback.h"
 #include "content/common/content_export.h"
@@ -79,6 +80,11 @@
     kInvalidResponseError,
   };
 
+  enum class LogoutResponse {
+    kSuccess,
+    kError,
+  };
+
   struct Endpoints {
     std::string idp;
     std::string token;
@@ -96,6 +102,7 @@
       base::OnceCallback<void(AccountsResponse, const AccountList&)>;
   using TokenRequestCallback =
       base::OnceCallback<void(TokenResponse, const std::string&)>;
+  using LogoutCallback = base::OnceCallback<void(LogoutResponse)>;
 
   static std::unique_ptr<IdpNetworkRequestManager> Create(
       const GURL& provider,
@@ -126,6 +133,9 @@
                                 const std::string& request,
                                 TokenRequestCallback callback);
 
+  // Send logout request to a single target.
+  virtual void SendLogout(const GURL& logout_url, LogoutCallback);
+
   // Parses accounts from given Value. Returns true if parse is successful and
   // adds parsed accounts to the |account_list|.
   // TODO(majidvp): Make this function private and update tests to test the
@@ -144,6 +154,7 @@
   void OnAccountsRequestParsed(data_decoder::DataDecoder::ValueOrError result);
   void OnTokenRequestResponse(std::unique_ptr<std::string> response_body);
   void OnTokenRequestParsed(data_decoder::DataDecoder::ValueOrError result);
+  void OnLogoutCompleted(std::unique_ptr<std::string> response_body);
 
   // URL of the Identity Provider.
   GURL provider_;
@@ -154,6 +165,7 @@
   SigninRequestCallback signin_request_callback_;
   AccountsRequestCallback accounts_request_callback_;
   TokenRequestCallback token_request_callback_;
+  LogoutCallback logout_callback_;
 
   std::unique_ptr<network::SimpleURLLoader> url_loader_;
 
diff --git a/content/browser/webid/test/mock_idp_network_request_manager.h b/content/browser/webid/test/mock_idp_network_request_manager.h
index c1c0c54..6bedb488 100644
--- a/content/browser/webid/test/mock_idp_network_request_manager.h
+++ b/content/browser/webid/test/mock_idp_network_request_manager.h
@@ -29,6 +29,7 @@
                     const std::string&,
                     const std::string&,
                     TokenRequestCallback));
+  MOCK_METHOD2(SendLogout, void(const GURL& logout_url, LogoutCallback));
 };
 
 }  // namespace content
diff --git a/content/public/utility/content_utility_client.cc b/content/public/utility/content_utility_client.cc
index 7d566c86..f00b904 100644
--- a/content/public/utility/content_utility_client.cc
+++ b/content/public/utility/content_utility_client.cc
@@ -16,4 +16,8 @@
   return false;
 }
 
+bool ContentUtilityClient::GetDefaultUserDataDirectory(base::FilePath* path) {
+  return false;
+}
+
 }  // namespace content
diff --git a/content/public/utility/content_utility_client.h b/content/public/utility/content_utility_client.h
index 361a2f3..dc0aa6a 100644
--- a/content/public/utility/content_utility_client.h
+++ b/content/public/utility/content_utility_client.h
@@ -73,6 +73,8 @@
 
   virtual void RegisterNetworkBinders(
       service_manager::BinderRegistry* registry) {}
+
+  virtual bool GetDefaultUserDataDirectory(base::FilePath* path);
 };
 
 }  // namespace content
diff --git a/content/renderer/accessibility/render_accessibility_impl.cc b/content/renderer/accessibility/render_accessibility_impl.cc
index 4741233..d240a8d 100644
--- a/content/renderer/accessibility/render_accessibility_impl.cc
+++ b/content/renderer/accessibility/render_accessibility_impl.cc
@@ -596,12 +596,12 @@
     return false;
 
   if (event.event_type == ax::mojom::Event::kTextSelectionChanged &&
-      !obj.IsNativeTextField()) {
-    // Selection changes on non-native text controls cause no change to the
+      !obj.IsAtomicTextField()) {
+    // Selection changes on non-atomic text fields cause no change to the
     // control node's data.
     //
     // Selection offsets exposed via kTextSelStart and kTextSelEnd are only used
-    // for plain text controls, (input of a text field type, and textarea). Rich
+    // for atomic text fields, (input of a text field type, and textarea). Rich
     // editable areas, such as contenteditables, use AXTreeData.
     //
     // TODO(nektar): Remove kTextSelStart and kTextSelEnd from the renderer.
diff --git a/content/renderer/renderer_blink_platform_impl.cc b/content/renderer/renderer_blink_platform_impl.cc
index 3b7991a..1db5cf1 100644
--- a/content/renderer/renderer_blink_platform_impl.cc
+++ b/content/renderer/renderer_blink_platform_impl.cc
@@ -324,6 +324,9 @@
     base::Time response_time,
     const uint8_t* data,
     size_t size) {
+  // The browser-side GeneratedCodeCache ignores writes over 2GB.
+  if (size > std::numeric_limits<int32_t>::max())
+    return;
   // Let the browser know we generated cacheable metadata for this resource.
   // The browser may cache it and return it on subsequent responses to speed
   // the processing of this resource.
diff --git a/content/test/data/accessibility/html/contenteditable-descendants-expected-blink.txt b/content/test/data/accessibility/html/contenteditable-descendants-expected-blink.txt
index 8175721..0175602 100644
--- a/content/test/data/accessibility/html/contenteditable-descendants-expected-blink.txt
+++ b/content/test/data/accessibility/html/contenteditable-descendants-expected-blink.txt
@@ -1,7 +1,7 @@
 rootWebArea
 ++genericContainer ignored
 ++++genericContainer ignored
-++++++genericContainer editable multiline richlyEditable value='A contenteditable with a link and an  and a .<newline><newline>Always expose editable tables as tables.<newline>Editable list item.' editableRoot=true
+++++++genericContainer editable multiline richlyEditable value='A contenteditable with a link and an  and a .<newline><newline>Always expose editable tables as tables.<newline>Editable list item.' contentEditableRoot=true
 ++++++++paragraph editable richlyEditable
 ++++++++++staticText editable richlyEditable name='A contenteditable with a '
 ++++++++++++inlineTextBox editable richlyEditable name='A contenteditable with a '
@@ -34,6 +34,6 @@
 ++++++paragraph
 ++++++++staticText name='Non-editable paragraph.'
 ++++++++++inlineTextBox name='Non-editable paragraph.'
-++++++paragraph editable multiline richlyEditable value='Should keep the role but change the state.' editableRoot=true
+++++++paragraph editable multiline richlyEditable value='Should keep the role but change the state.' contentEditableRoot=true
 ++++++++staticText editable richlyEditable name='Should keep the role but change the state.'
 ++++++++++inlineTextBox editable richlyEditable name='Should keep the role but change the state.'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/contenteditable-descendants-with-selection-expected-blink.txt b/content/test/data/accessibility/html/contenteditable-descendants-with-selection-expected-blink.txt
index e4d20c5..4bd1aba9 100644
--- a/content/test/data/accessibility/html/contenteditable-descendants-with-selection-expected-blink.txt
+++ b/content/test/data/accessibility/html/contenteditable-descendants-with-selection-expected-blink.txt
@@ -1,7 +1,7 @@
 rootWebArea
 ++genericContainer ignored
 ++++genericContainer ignored
-++++++genericContainer editable multiline richlyEditable value='A contenteditable with a link and an  and a .<newline><newline>Always expose editable tables as tables.<newline>Editable list item.' editableRoot=true TreeData.textSelStartOffset=0 TreeData.textSelEndOffset=3
+++++++genericContainer editable multiline richlyEditable value='A contenteditable with a link and an  and a .<newline><newline>Always expose editable tables as tables.<newline>Editable list item.' contentEditableRoot=true TreeData.textSelStartOffset=0 TreeData.textSelEndOffset=3
 ++++++++paragraph editable richlyEditable
 ++++++++++staticText editable richlyEditable name='A contenteditable with a '
 ++++++++++++inlineTextBox editable richlyEditable name='A contenteditable with a '
diff --git a/content/test/data/accessibility/html/contenteditable-descendants-with-selection.html b/content/test/data/accessibility/html/contenteditable-descendants-with-selection.html
index c92572e..0a537ae 100644
--- a/content/test/data/accessibility/html/contenteditable-descendants-with-selection.html
+++ b/content/test/data/accessibility/html/contenteditable-descendants-with-selection.html
@@ -8,6 +8,7 @@
 @WIN-ALLOW:n_selections*
 @WIN-ALLOW:selection_start*
 @WIN-ALLOW:selection_end*
+@BLINK-ALLOW:contentEditableRoot*
 @BLINK-ALLOW:editable*
 @BLINK-ALLOW:richlyEditable*
 @BLINK-ALLOW:*textSel*
diff --git a/content/test/data/accessibility/html/contenteditable-descendants.html b/content/test/data/accessibility/html/contenteditable-descendants.html
index 97da7cc..b32b6f2f 100644
--- a/content/test/data/accessibility/html/contenteditable-descendants.html
+++ b/content/test/data/accessibility/html/contenteditable-descendants.html
@@ -11,6 +11,7 @@
 @WIN-ALLOW:selection_start*
 @WIN-ALLOW:selection_end*
 @BLINK-ALLOW:linked
+@BLINK-ALLOW:contentEditableRoot*
 @BLINK-ALLOW:editable*
 @BLINK-ALLOW:richlyEditable*
 @BLINK-ALLOW:textSel*
diff --git a/content/test/data/accessibility/html/contenteditable-plaintext-with-role-expected-blink.txt b/content/test/data/accessibility/html/contenteditable-plaintext-with-role-expected-blink.txt
index f42e714..6f39748 100644
--- a/content/test/data/accessibility/html/contenteditable-plaintext-with-role-expected-blink.txt
+++ b/content/test/data/accessibility/html/contenteditable-plaintext-with-role-expected-blink.txt
@@ -1,6 +1,6 @@
 rootWebArea
 ++genericContainer ignored
 ++++genericContainer
-++++++menuItemRadio editable multiline name='x' value='x' checkedState=false editableRoot=true
+++++++menuItemRadio editable multiline name='x' value='x' checkedState=false contentEditableRoot=true
 ++++++++staticText editable name='x'
 ++++++++++inlineTextBox editable name='x'
diff --git a/content/test/data/accessibility/html/contenteditable-plaintext-with-role.html b/content/test/data/accessibility/html/contenteditable-plaintext-with-role.html
index 6faf650..57aaeb50 100644
--- a/content/test/data/accessibility/html/contenteditable-plaintext-with-role.html
+++ b/content/test/data/accessibility/html/contenteditable-plaintext-with-role.html
@@ -1,4 +1,5 @@
 <!--
+@BLINK-ALLOW:contentEditableRoot*
 @BLINK-ALLOW:editable*
 @BLINK-ALLOW:richlyEditable*
 -->
diff --git a/content/test/data/accessibility/html/contenteditable-with-embedded-contenteditables-expected-blink.txt b/content/test/data/accessibility/html/contenteditable-with-embedded-contenteditables-expected-blink.txt
index e26ba29..de62979 100644
--- a/content/test/data/accessibility/html/contenteditable-with-embedded-contenteditables-expected-blink.txt
+++ b/content/test/data/accessibility/html/contenteditable-with-embedded-contenteditables-expected-blink.txt
@@ -1,7 +1,7 @@
 rootWebArea
 ++genericContainer ignored
 ++++genericContainer ignored
-++++++genericContainer editable multiline richlyEditable value='This is editable.<newline><newline>This is not editable.<newline><newline><newline>But this one is.<newline><newline>So is this one.' editableRoot=true
+++++++genericContainer editable multiline richlyEditable value='This is editable.<newline><newline>This is not editable.<newline><newline><newline>But this one is.<newline><newline>So is this one.' contentEditableRoot=true
 ++++++++paragraph editable richlyEditable
 ++++++++++staticText editable richlyEditable name='This is editable.'
 ++++++++++++inlineTextBox editable richlyEditable name='This is editable.'
@@ -10,9 +10,9 @@
 ++++++++++++inlineTextBox name='This is not editable.'
 ++++++++++lineBreak name='<newline>'
 ++++++++++++inlineTextBox name='<newline>'
-++++++++++paragraph editable multiline richlyEditable value='But this one is.' editableRoot=true
+++++++++++paragraph editable multiline richlyEditable value='But this one is.' contentEditableRoot=true
 ++++++++++++staticText editable richlyEditable name='But this one is.'
 ++++++++++++++inlineTextBox editable richlyEditable name='But this one is.'
-++++++++paragraph editable multiline richlyEditable value='So is this one.' editableRoot=true
+++++++++paragraph editable multiline richlyEditable value='So is this one.' contentEditableRoot=true
 ++++++++++staticText editable richlyEditable name='So is this one.'
 ++++++++++++inlineTextBox editable richlyEditable name='So is this one.'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/contenteditable-with-embedded-contenteditables.html b/content/test/data/accessibility/html/contenteditable-with-embedded-contenteditables.html
index aad7347f..9ec49dd6 100644
--- a/content/test/data/accessibility/html/contenteditable-with-embedded-contenteditables.html
+++ b/content/test/data/accessibility/html/contenteditable-with-embedded-contenteditables.html
@@ -5,6 +5,7 @@
 @WIN-ALLOW:n_selections*
 @WIN-ALLOW:selection_start*
 @WIN-ALLOW:selection_end*
+@BLINK-ALLOW:contentEditableRoot*
 @BLINK-ALLOW:editable*
 @BLINK-ALLOW:richlyEditable*
 @BLINK-ALLOW:textSel*
diff --git a/content/test/data/accessibility/html/design-mode-expected-blink.txt b/content/test/data/accessibility/html/design-mode-expected-blink.txt
index b4e70bd..76508c3 100644
--- a/content/test/data/accessibility/html/design-mode-expected-blink.txt
+++ b/content/test/data/accessibility/html/design-mode-expected-blink.txt
@@ -1,6 +1,6 @@
 rootWebArea editable richlyEditable htmlTag='#document'
-++genericContainer editable ignored richlyEditable htmlTag='html' editableRoot=true
-++++genericContainer editable richlyEditable htmlTag='body' editableRoot=true
+++genericContainer editable ignored richlyEditable htmlTag='html' contentEditableRoot=true
+++++genericContainer editable richlyEditable htmlTag='body' contentEditableRoot=true
 ++++++paragraph editable richlyEditable htmlTag='p'
 ++++++++staticText editable richlyEditable name='Hello'
 ++++++++++inlineTextBox editable richlyEditable name='Hello'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/design-mode.html b/content/test/data/accessibility/html/design-mode.html
index bf9cd8f6..cfb997f 100644
--- a/content/test/data/accessibility/html/design-mode.html
+++ b/content/test/data/accessibility/html/design-mode.html
@@ -1,4 +1,5 @@
 <!--
+@BLINK-ALLOW:contentEditableRoot*
 @BLINK-ALLOW:editable*
 @BLINK-ALLOW:richlyEditable*
 @BLINK-ALLOW:htmlTag*
diff --git a/content/test/data/accessibility/html/input-types-with-value-expected-blink.txt b/content/test/data/accessibility/html/input-types-with-value-expected-blink.txt
index ecb6c281..49c5edb8 100644
--- a/content/test/data/accessibility/html/input-types-with-value-expected-blink.txt
+++ b/content/test/data/accessibility/html/input-types-with-value-expected-blink.txt
@@ -63,15 +63,15 @@
 ++++++++++genericContainer editable
 ++++++++++++staticText editable name='Sometext'
 ++++++++++++++inlineTextBox editable name='Sometext'
-++++++genericContainer editable multiline value='Plain contenteditable' editableRoot=true
+++++++genericContainer editable multiline value='Plain contenteditable' contentEditableRoot=true
 ++++++++staticText editable name='Plain contenteditable'
 ++++++++++inlineTextBox editable name='Plain contenteditable'
-++++++genericContainer editable multiline richlyEditable value='Rich contenteditable' editableRoot=true
+++++++genericContainer editable multiline richlyEditable value='Rich contenteditable' contentEditableRoot=true
 ++++++++staticText editable richlyEditable name='Rich contenteditable'
 ++++++++++inlineTextBox editable richlyEditable name='Rich contenteditable'
-++++++textField editable multiline value='Plain contenteditable' editableRoot=true
+++++++textField editable multiline value='Plain contenteditable' contentEditableRoot=true
 ++++++++staticText editable name='Plain contenteditable'
 ++++++++++inlineTextBox editable name='Plain contenteditable'
-++++++textField editable multiline richlyEditable value='Rich contenteditable' editableRoot=true
+++++++textField editable multiline richlyEditable value='Rich contenteditable' contentEditableRoot=true
 ++++++++staticText editable richlyEditable name='Rich contenteditable'
 ++++++++++inlineTextBox editable richlyEditable name='Rich contenteditable'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/input-types-with-value.html b/content/test/data/accessibility/html/input-types-with-value.html
index 4bcd308ed..00d2e127 100644
--- a/content/test/data/accessibility/html/input-types-with-value.html
+++ b/content/test/data/accessibility/html/input-types-with-value.html
@@ -1,5 +1,6 @@
 <!--
-@BLINK-ALLOW:*editable*
+@BLINK-ALLOW:contentEditableRoot*
+@BLINK-ALLOW:editable*
 @BLINK-ALLOW:richlyEditable*
 -->
 <!DOCTYPE html>
diff --git a/content/test/gpu/fuchsia_util.py b/content/test/gpu/fuchsia_util.py
index 177a830..11ff018 100644
--- a/content/test/gpu/fuchsia_util.py
+++ b/content/test/gpu/fuchsia_util.py
@@ -47,6 +47,8 @@
 
   # Pass all other arguments to the gpu integration tests.
   script_cmd.extend(test_args)
+  listener_process = None
+  symbolizer_process = None
   try:
     with GetDeploymentTargetForArgs(runner_script_args) as target:
       target.Start()
@@ -64,15 +66,15 @@
         script_cmd.append('-v')
 
       # Set up logging of WebEngine
-      listener = target.RunCommandPiped(['log_listener'],
-                                        stdout=subprocess.PIPE,
-                                        stderr=subprocess.STDOUT)
+      listener_process = target.RunCommandPiped(['log_listener'],
+                                                stdout=subprocess.PIPE,
+                                                stderr=subprocess.STDOUT)
       build_ids_paths = map(
           lambda package_name: os.path.join(web_engine_dir, package_name,
                                             'ids.txt'), package_names)
-      RunSymbolizer(listener.stdout,
-                    open(runner_script_args.system_log_file, 'w'),
-                    build_ids_paths)
+      symbolizer_process = RunSymbolizer(
+          listener_process.stdout, open(runner_script_args.system_log_file,
+                                        'w'), build_ids_paths)
 
       # Keep the Amber repository live while the test runs.
       with target.GetAmberRepo():
@@ -86,3 +88,7 @@
   finally:
     if temp_log_file:
       shutil.rmtree(os.path.dirname(runner_script_args.system_log_file))
+    if listener_process:
+      listener_process.kill()
+    if symbolizer_process:
+      symbolizer_process.kill()
diff --git a/content/utility/services.cc b/content/utility/services.cc
index 9cac007..ca332d8 100644
--- a/content/utility/services.cc
+++ b/content/utility/services.cc
@@ -229,10 +229,16 @@
 }
 
 #if defined(OS_WIN)
-auto RunMediaFoundationService(
+std::unique_ptr<media::MediaFoundationService> RunMediaFoundationService(
     mojo::PendingReceiver<media::mojom::MediaFoundationService> receiver) {
+  base::FilePath user_data;
+  if (!GetContentClient()->utility()->GetDefaultUserDataDirectory(&user_data)) {
+    receiver.ResetWithReason(0, "Cannot get user data directory!");
+    return nullptr;
+  }
+
   return std::make_unique<media::MediaFoundationService>(
-      std::move(receiver), base::BindOnce(&EnsureSandboxedWin));
+      std::move(receiver), user_data, base::BindOnce(&EnsureSandboxedWin));
 }
 #endif  // defined(OS_WIN)
 
diff --git a/content/web_test/renderer/web_ax_object_proxy.cc b/content/web_test/renderer/web_ax_object_proxy.cc
index 35f97e733..50f76a9 100644
--- a/content/web_test/renderer/web_ax_object_proxy.cc
+++ b/content/web_test/renderer/web_ax_object_proxy.cc
@@ -768,7 +768,7 @@
 bool WebAXObjectProxy::IsEditableRoot() {
   UpdateLayout();
   return GetAXNodeData().GetBoolAttribute(
-      ax::mojom::BoolAttribute::kEditableRoot);
+      ax::mojom::BoolAttribute::kContentEditableRoot);
 }
 
 bool WebAXObjectProxy::IsEditable() {
diff --git a/extensions/common/api/automation.idl b/extensions/common/api/automation.idl
index 5f982f7..4c6c27cb 100644
--- a/extensions/common/api/automation.idl
+++ b/extensions/common/api/automation.idl
@@ -1285,8 +1285,9 @@
     // Indicates the font family.
     DOMString fontFamily;
 
-    // Indicates whether this is a root of an editable subtree.
-    boolean editableRoot;
+    // Indicates whether the object is at the root of a content editable region,
+    // or at a <body> element that has "design-mode" set to "on".
+    boolean contentEditableRoot;
 
     // Indicates aria-current state.
     AriaCurrentState? ariaCurrentState;
diff --git a/extensions/renderer/resources/automation/automation_node.js b/extensions/renderer/resources/automation/automation_node.js
index 6bde358..24d8a7cb 100644
--- a/extensions/renderer/resources/automation/automation_node.js
+++ b/extensions/renderer/resources/automation/automation_node.js
@@ -1376,8 +1376,8 @@
 
 var boolAttributes = [
   'busy', 'clickable', 'containerLiveAtomic', 'containerLiveBusy',
-  'editableRoot', 'liveAtomic', 'modal', 'notUserSelectableStyle', 'scrollable',
-  'selected', 'supportsTextLocation'
+  'contentEditableRoot', 'liveAtomic', 'modal', 'notUserSelectableStyle',
+  'scrollable', 'selected', 'supportsTextLocation'
 ];
 
 var intAttributes = [
diff --git a/gpu/command_buffer/service/BUILD.gn b/gpu/command_buffer/service/BUILD.gn
index 11e5f269fd..18225e6 100644
--- a/gpu/command_buffer/service/BUILD.gn
+++ b/gpu/command_buffer/service/BUILD.gn
@@ -182,6 +182,8 @@
     "memory_program_cache.h",
     "multi_draw_manager.cc",
     "multi_draw_manager.h",
+    "native_image_buffer.cc",
+    "native_image_buffer.h",
     "passthrough_abstract_texture_impl.cc",
     "passthrough_abstract_texture_impl.h",
     "passthrough_discardable_manager.cc",
@@ -243,8 +245,6 @@
     "shared_memory_region_wrapper.h",
     "skia_utils.cc",
     "skia_utils.h",
-    "texture_definition.cc",
-    "texture_definition.h",
     "texture_manager.cc",
     "texture_manager.h",
     "transform_feedback_manager.cc",
diff --git a/gpu/command_buffer/service/feature_info.cc b/gpu/command_buffer/service/feature_info.cc
index 90235bed..5449150 100644
--- a/gpu/command_buffer/service/feature_info.cc
+++ b/gpu/command_buffer/service/feature_info.cc
@@ -20,7 +20,7 @@
 #include "build/chromecast_buildflags.h"
 #include "build/chromeos_buildflags.h"
 #include "gpu/command_buffer/service/gpu_switches.h"
-#include "gpu/command_buffer/service/texture_definition.h"
+#include "gpu/command_buffer/service/native_image_buffer.h"
 #include "gpu/config/gpu_switches.h"
 #include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_fence.h"
@@ -1577,10 +1577,6 @@
     validators_.g_l_state.AddValue(GL_MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT);
   }
 
-  if (workarounds_.avoid_egl_image_target_texture_reuse) {
-    TextureDefinition::AvoidEGLTargetTextureReuse();
-  }
-
   if (gl_version_info_->IsLowerThanGL(4, 3)) {
     // crbug.com/481184.
     // GL_PRIMITIVE_RESTART_FIXED_INDEX is only available on Desktop GL 4.3+,
diff --git a/gpu/command_buffer/service/native_image_buffer.cc b/gpu/command_buffer/service/native_image_buffer.cc
new file mode 100644
index 0000000..3d77346
--- /dev/null
+++ b/gpu/command_buffer/service/native_image_buffer.cc
@@ -0,0 +1,177 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gpu/command_buffer/service/native_image_buffer.h"
+
+// #include <stdint.h>
+
+#include <list>
+
+#include "base/synchronization/lock.h"
+#include "build/build_config.h"
+#include "ui/gl/gl_image.h"
+#include "ui/gl/gl_implementation.h"
+
+#if !defined(OS_MAC)
+#include "ui/gl/gl_surface_egl.h"
+#endif
+
+namespace gpu {
+namespace gles2 {
+
+namespace {
+
+#if !defined(OS_MAC)
+class NativeImageBufferEGL : public NativeImageBuffer {
+ public:
+  static scoped_refptr<NativeImageBufferEGL> Create(GLuint texture_id);
+
+ private:
+  NativeImageBufferEGL(EGLDisplay display, EGLImageKHR image);
+  ~NativeImageBufferEGL() override;
+  void AddClient(gl::GLImage* client) override;
+  void RemoveClient(gl::GLImage* client) override;
+  bool IsClient(gl::GLImage* client) override;
+  void BindToTexture(GLenum target) const override;
+
+  const EGLDisplay egl_display_;
+  const EGLImageKHR egl_image_;
+
+  base::Lock lock_;
+
+  struct ClientInfo {
+    explicit ClientInfo(gl::GLImage* client);
+    ~ClientInfo();
+
+    gl::GLImage* client;
+    bool needs_wait_before_read;
+  };
+  std::list<ClientInfo> client_infos_;
+  gl::GLImage* write_client_;
+
+  DISALLOW_COPY_AND_ASSIGN(NativeImageBufferEGL);
+};
+
+scoped_refptr<NativeImageBufferEGL> NativeImageBufferEGL::Create(
+    GLuint texture_id) {
+  EGLDisplay egl_display = gl::GLSurfaceEGL::GetHardwareDisplay();
+  EGLContext egl_context = eglGetCurrentContext();
+
+  DCHECK_NE(EGL_NO_CONTEXT, egl_context);
+  DCHECK_NE(EGL_NO_DISPLAY, egl_display);
+  DCHECK(glIsTexture(texture_id));
+
+  DCHECK(gl::g_driver_egl.ext.b_EGL_KHR_image_base &&
+         gl::g_driver_egl.ext.b_EGL_KHR_gl_texture_2D_image &&
+         gl::g_current_gl_driver->ext.b_GL_OES_EGL_image);
+
+  const EGLint egl_attrib_list[] = {
+      EGL_GL_TEXTURE_LEVEL_KHR, 0, EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
+  EGLClientBuffer egl_buffer = reinterpret_cast<EGLClientBuffer>(texture_id);
+  EGLenum egl_target = EGL_GL_TEXTURE_2D_KHR;
+
+  EGLImageKHR egl_image = eglCreateImageKHR(
+      egl_display, egl_context, egl_target, egl_buffer, egl_attrib_list);
+
+  if (egl_image == EGL_NO_IMAGE_KHR) {
+    LOG(ERROR) << "eglCreateImageKHR for cross-thread sharing failed: 0x"
+               << std::hex << eglGetError();
+    return nullptr;
+  }
+
+  return new NativeImageBufferEGL(egl_display, egl_image);
+}
+
+NativeImageBufferEGL::ClientInfo::ClientInfo(gl::GLImage* client)
+    : client(client), needs_wait_before_read(true) {}
+
+NativeImageBufferEGL::ClientInfo::~ClientInfo() = default;
+
+NativeImageBufferEGL::NativeImageBufferEGL(EGLDisplay display,
+                                           EGLImageKHR image)
+    : egl_display_(display),
+      egl_image_(image),
+      write_client_(nullptr) {
+  DCHECK(egl_display_ != EGL_NO_DISPLAY);
+  DCHECK(egl_image_ != EGL_NO_IMAGE_KHR);
+}
+
+NativeImageBufferEGL::~NativeImageBufferEGL() {
+  DCHECK(client_infos_.empty());
+  if (egl_image_ != EGL_NO_IMAGE_KHR)
+    eglDestroyImageKHR(egl_display_, egl_image_);
+}
+
+void NativeImageBufferEGL::AddClient(gl::GLImage* client) {
+  base::AutoLock lock(lock_);
+  client_infos_.emplace_back(client);
+}
+
+void NativeImageBufferEGL::RemoveClient(gl::GLImage* client) {
+  base::AutoLock lock(lock_);
+  if (write_client_ == client)
+    write_client_ = nullptr;
+  for (std::list<ClientInfo>::iterator it = client_infos_.begin();
+       it != client_infos_.end(); it++) {
+    if (it->client == client) {
+      client_infos_.erase(it);
+      return;
+    }
+  }
+  NOTREACHED();
+}
+
+bool NativeImageBufferEGL::IsClient(gl::GLImage* client) {
+  base::AutoLock lock(lock_);
+  for (auto & client_info : client_infos_) {
+    if (client_info.client == client)
+      return true;
+  }
+  return false;
+}
+
+void NativeImageBufferEGL::BindToTexture(GLenum target) const {
+  DCHECK(egl_image_ != EGL_NO_IMAGE_KHR);
+  glEGLImageTargetTexture2DOES(target, egl_image_);
+  DCHECK_EQ(static_cast<EGLint>(EGL_SUCCESS), eglGetError());
+  DCHECK_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
+}
+
+#endif
+
+class NativeImageBufferStub : public NativeImageBuffer {
+ public:
+  NativeImageBufferStub() = default;
+
+ private:
+  ~NativeImageBufferStub() override = default;
+  void AddClient(gl::GLImage* client) override {}
+  void RemoveClient(gl::GLImage* client) override {}
+  bool IsClient(gl::GLImage* client) override { return true; }
+  void BindToTexture(GLenum target) const override {}
+
+  DISALLOW_COPY_AND_ASSIGN(NativeImageBufferStub);
+};
+
+}  // anonymous namespace
+
+// static
+scoped_refptr<NativeImageBuffer> NativeImageBuffer::Create(GLuint texture_id) {
+  switch (gl::GetGLImplementation()) {
+#if !defined(OS_MAC)
+    case gl::kGLImplementationEGLGLES2:
+    case gl::kGLImplementationEGLANGLE:
+      return NativeImageBufferEGL::Create(texture_id);
+#endif
+    case gl::kGLImplementationMockGL:
+    case gl::kGLImplementationStubGL:
+      return new NativeImageBufferStub;
+    default:
+      NOTREACHED();
+      return nullptr;
+  }
+}
+
+}  // namespace gles2
+}  // namespace gpu
diff --git a/gpu/command_buffer/service/native_image_buffer.h b/gpu/command_buffer/service/native_image_buffer.h
new file mode 100644
index 0000000..d981044
--- /dev/null
+++ b/gpu/command_buffer/service/native_image_buffer.h
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef GPU_COMMAND_BUFFER_SERVICE_NATIVE_IMAGE_BUFFER_H_
+#define GPU_COMMAND_BUFFER_SERVICE_NATIVE_IMAGE_BUFFER_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "gpu/command_buffer/service/gl_utils.h"
+
+namespace gl {
+class GLImage;
+}
+
+namespace gpu {
+namespace gles2 {
+
+class NativeImageBuffer : public base::RefCountedThreadSafe<NativeImageBuffer> {
+ public:
+  static scoped_refptr<NativeImageBuffer> Create(GLuint texture_id);
+
+  virtual void AddClient(gl::GLImage* client) = 0;
+  virtual void RemoveClient(gl::GLImage* client) = 0;
+  virtual bool IsClient(gl::GLImage* client) = 0;
+  virtual void BindToTexture(GLenum target) const = 0;
+
+ protected:
+  friend class base::RefCountedThreadSafe<NativeImageBuffer>;
+  NativeImageBuffer() = default;
+  virtual ~NativeImageBuffer() = default;
+
+  DISALLOW_COPY_AND_ASSIGN(NativeImageBuffer);
+};
+
+}  // namespace gles2
+}  // namespace gpu
+
+#endif  // GPU_COMMAND_BUFFER_SERVICE_NATIVE_IMAGE_BUFFER_H_
diff --git a/gpu/command_buffer/service/shared_image_backing_egl_image.cc b/gpu/command_buffer/service/shared_image_backing_egl_image.cc
index 8425b294..9efed56 100644
--- a/gpu/command_buffer/service/shared_image_backing_egl_image.cc
+++ b/gpu/command_buffer/service/shared_image_backing_egl_image.cc
@@ -4,11 +4,11 @@
 
 #include "gpu/command_buffer/service/shared_image_backing_egl_image.h"
 
+#include "gpu/command_buffer/service/native_image_buffer.h"
 #include "gpu/command_buffer/service/shared_context_state.h"
 #include "gpu/command_buffer/service/shared_image_batch_access_manager.h"
 #include "gpu/command_buffer/service/shared_image_representation.h"
 #include "gpu/command_buffer/service/shared_image_representation_skia_gl.h"
-#include "gpu/command_buffer/service/texture_definition.h"
 #include "gpu/command_buffer/service/texture_manager.h"
 #include "ui/gl/gl_fence_egl.h"
 #include "ui/gl/gl_utils.h"
diff --git a/gpu/command_buffer/service/texture_definition.cc b/gpu/command_buffer/service/texture_definition.cc
deleted file mode 100644
index 193c3a8..0000000
--- a/gpu/command_buffer/service/texture_definition.cc
+++ /dev/null
@@ -1,491 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "gpu/command_buffer/service/texture_definition.h"
-
-#include <stdint.h>
-
-#include <list>
-
-#include "base/lazy_instance.h"
-#include "base/synchronization/lock.h"
-#include "base/threading/thread_local.h"
-#include "gpu/command_buffer/service/texture_manager.h"
-#include "ui/gl/gl_image.h"
-#include "ui/gl/gl_implementation.h"
-#include "ui/gl/scoped_binders.h"
-
-#if !defined(OS_MAC)
-#include "base/macros.h"
-#include "ui/gl/gl_surface_egl.h"
-#endif
-
-namespace gpu {
-namespace gles2 {
-
-namespace {
-
-class GLImageSync : public gl::GLImage {
- public:
-  explicit GLImageSync(const scoped_refptr<NativeImageBuffer>& buffer,
-                       const gfx::Size& size);
-
-  // Implement GLImage.
-  gfx::Size GetSize() override;
-  unsigned GetInternalFormat() override;
-  unsigned GetDataType() override;
-  BindOrCopy ShouldBindOrCopy() override;
-  bool BindTexImage(unsigned target) override;
-  void ReleaseTexImage(unsigned target) override;
-  bool CopyTexImage(unsigned target) override;
-  bool CopyTexSubImage(unsigned target,
-                       const gfx::Point& offset,
-                       const gfx::Rect& rect) override;
-  bool ScheduleOverlayPlane(gfx::AcceleratedWidget widget,
-                            int z_order,
-                            gfx::OverlayTransform transform,
-                            const gfx::Rect& bounds_rect,
-                            const gfx::RectF& crop_rect,
-                            bool enable_blend,
-                            std::unique_ptr<gfx::GpuFence> gpu_fence) override;
-  void SetColorSpace(const gfx::ColorSpace& color_space) override {}
-  void Flush() override {}
-  void OnMemoryDump(base::trace_event::ProcessMemoryDump* pmd,
-                    uint64_t process_tracing_id,
-                    const std::string& dump_name) override;
-
- protected:
-  ~GLImageSync() override;
-
- private:
-  scoped_refptr<NativeImageBuffer> buffer_;
-  gfx::Size size_;
-
-  DISALLOW_COPY_AND_ASSIGN(GLImageSync);
-};
-
-GLImageSync::GLImageSync(const scoped_refptr<NativeImageBuffer>& buffer,
-                         const gfx::Size& size)
-    : buffer_(buffer), size_(size) {
-  if (buffer.get())
-    buffer->AddClient(this);
-}
-
-GLImageSync::~GLImageSync() {
-  if (buffer_.get())
-    buffer_->RemoveClient(this);
-}
-
-gfx::Size GLImageSync::GetSize() {
-  return size_;
-}
-
-unsigned GLImageSync::GetInternalFormat() {
-  return GL_RGBA;
-}
-
-unsigned GLImageSync::GetDataType() {
-  return GL_UNSIGNED_BYTE;
-}
-
-GLImageSync::BindOrCopy GLImageSync::ShouldBindOrCopy() {
-  return BIND;
-}
-
-bool GLImageSync::BindTexImage(unsigned target) {
-  NOTREACHED();
-  return false;
-}
-
-void GLImageSync::ReleaseTexImage(unsigned target) {
-  NOTREACHED();
-}
-
-bool GLImageSync::CopyTexImage(unsigned target) {
-  return false;
-}
-
-bool GLImageSync::CopyTexSubImage(unsigned target,
-                                  const gfx::Point& offset,
-                                  const gfx::Rect& rect) {
-  return false;
-}
-
-bool GLImageSync::ScheduleOverlayPlane(
-    gfx::AcceleratedWidget widget,
-    int z_order,
-    gfx::OverlayTransform transform,
-    const gfx::Rect& bounds_rect,
-    const gfx::RectF& crop_rect,
-    bool enable_blend,
-    std::unique_ptr<gfx::GpuFence> gpu_fence) {
-  NOTREACHED();
-  return false;
-}
-
-void GLImageSync::OnMemoryDump(base::trace_event::ProcessMemoryDump* pmd,
-                               uint64_t process_tracing_id,
-                               const std::string& dump_name) {
-  // TODO(ericrk): Implement GLImage OnMemoryDump. crbug.com/514914
-}
-
-#if !defined(OS_MAC)
-class NativeImageBufferEGL : public NativeImageBuffer {
- public:
-  static scoped_refptr<NativeImageBufferEGL> Create(GLuint texture_id);
-
- private:
-  NativeImageBufferEGL(EGLDisplay display, EGLImageKHR image);
-  ~NativeImageBufferEGL() override;
-  void AddClient(gl::GLImage* client) override;
-  void RemoveClient(gl::GLImage* client) override;
-  bool IsClient(gl::GLImage* client) override;
-  void BindToTexture(GLenum target) const override;
-
-  const EGLDisplay egl_display_;
-  const EGLImageKHR egl_image_;
-
-  base::Lock lock_;
-
-  struct ClientInfo {
-    explicit ClientInfo(gl::GLImage* client);
-    ~ClientInfo();
-
-    gl::GLImage* client;
-    bool needs_wait_before_read;
-  };
-  std::list<ClientInfo> client_infos_;
-  gl::GLImage* write_client_;
-
-  DISALLOW_COPY_AND_ASSIGN(NativeImageBufferEGL);
-};
-
-scoped_refptr<NativeImageBufferEGL> NativeImageBufferEGL::Create(
-    GLuint texture_id) {
-  EGLDisplay egl_display = gl::GLSurfaceEGL::GetHardwareDisplay();
-  EGLContext egl_context = eglGetCurrentContext();
-
-  DCHECK_NE(EGL_NO_CONTEXT, egl_context);
-  DCHECK_NE(EGL_NO_DISPLAY, egl_display);
-  DCHECK(glIsTexture(texture_id));
-
-  DCHECK(gl::g_driver_egl.ext.b_EGL_KHR_image_base &&
-         gl::g_driver_egl.ext.b_EGL_KHR_gl_texture_2D_image &&
-         gl::g_current_gl_driver->ext.b_GL_OES_EGL_image);
-
-  const EGLint egl_attrib_list[] = {
-      EGL_GL_TEXTURE_LEVEL_KHR, 0, EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
-  EGLClientBuffer egl_buffer = reinterpret_cast<EGLClientBuffer>(texture_id);
-  EGLenum egl_target = EGL_GL_TEXTURE_2D_KHR;
-
-  EGLImageKHR egl_image = eglCreateImageKHR(
-      egl_display, egl_context, egl_target, egl_buffer, egl_attrib_list);
-
-  if (egl_image == EGL_NO_IMAGE_KHR) {
-    LOG(ERROR) << "eglCreateImageKHR for cross-thread sharing failed: 0x"
-               << std::hex << eglGetError();
-    return nullptr;
-  }
-
-  return new NativeImageBufferEGL(egl_display, egl_image);
-}
-
-NativeImageBufferEGL::ClientInfo::ClientInfo(gl::GLImage* client)
-    : client(client), needs_wait_before_read(true) {}
-
-NativeImageBufferEGL::ClientInfo::~ClientInfo() = default;
-
-NativeImageBufferEGL::NativeImageBufferEGL(EGLDisplay display,
-                                           EGLImageKHR image)
-    : NativeImageBuffer(),
-      egl_display_(display),
-      egl_image_(image),
-      write_client_(nullptr) {
-  DCHECK(egl_display_ != EGL_NO_DISPLAY);
-  DCHECK(egl_image_ != EGL_NO_IMAGE_KHR);
-}
-
-NativeImageBufferEGL::~NativeImageBufferEGL() {
-  DCHECK(client_infos_.empty());
-  if (egl_image_ != EGL_NO_IMAGE_KHR)
-    eglDestroyImageKHR(egl_display_, egl_image_);
-}
-
-void NativeImageBufferEGL::AddClient(gl::GLImage* client) {
-  base::AutoLock lock(lock_);
-  client_infos_.push_back(ClientInfo(client));
-}
-
-void NativeImageBufferEGL::RemoveClient(gl::GLImage* client) {
-  base::AutoLock lock(lock_);
-  if (write_client_ == client)
-    write_client_ = nullptr;
-  for (std::list<ClientInfo>::iterator it = client_infos_.begin();
-       it != client_infos_.end();
-       it++) {
-    if (it->client == client) {
-      client_infos_.erase(it);
-      return;
-    }
-  }
-  NOTREACHED();
-}
-
-bool NativeImageBufferEGL::IsClient(gl::GLImage* client) {
-  base::AutoLock lock(lock_);
-  for (std::list<ClientInfo>::iterator it = client_infos_.begin();
-       it != client_infos_.end();
-       it++) {
-    if (it->client == client)
-      return true;
-  }
-  return false;
-}
-
-void NativeImageBufferEGL::BindToTexture(GLenum target) const {
-  DCHECK(egl_image_ != EGL_NO_IMAGE_KHR);
-  glEGLImageTargetTexture2DOES(target, egl_image_);
-  DCHECK_EQ(static_cast<EGLint>(EGL_SUCCESS), eglGetError());
-  DCHECK_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
-}
-
-#endif
-
-class NativeImageBufferStub : public NativeImageBuffer {
- public:
-  NativeImageBufferStub() : NativeImageBuffer() {}
-
- private:
-  ~NativeImageBufferStub() override = default;
-  void AddClient(gl::GLImage* client) override {}
-  void RemoveClient(gl::GLImage* client) override {}
-  bool IsClient(gl::GLImage* client) override { return true; }
-  void BindToTexture(GLenum target) const override {}
-
-  DISALLOW_COPY_AND_ASSIGN(NativeImageBufferStub);
-};
-
-bool g_avoid_egl_target_texture_reuse = false;
-
-}  // anonymous namespace
-
-// static
-scoped_refptr<NativeImageBuffer> NativeImageBuffer::Create(GLuint texture_id) {
-  switch (gl::GetGLImplementation()) {
-#if !defined(OS_MAC)
-    case gl::kGLImplementationEGLGLES2:
-    case gl::kGLImplementationEGLANGLE:
-      return NativeImageBufferEGL::Create(texture_id);
-#endif
-    case gl::kGLImplementationMockGL:
-    case gl::kGLImplementationStubGL:
-      return new NativeImageBufferStub;
-    default:
-      NOTREACHED();
-      return nullptr;
-  }
-}
-
-// static
-void TextureDefinition::AvoidEGLTargetTextureReuse() {
-  g_avoid_egl_target_texture_reuse = true;
-}
-
-TextureDefinition::LevelInfo::LevelInfo()
-    : target(0),
-      internal_format(0),
-      width(0),
-      height(0),
-      depth(0),
-      border(0),
-      format(0),
-      type(0) {
-}
-
-TextureDefinition::LevelInfo::LevelInfo(GLenum target,
-                                        GLenum internal_format,
-                                        GLsizei width,
-                                        GLsizei height,
-                                        GLsizei depth,
-                                        GLint border,
-                                        GLenum format,
-                                        GLenum type,
-                                        const gfx::Rect& cleared_rect)
-    : target(target),
-      internal_format(internal_format),
-      width(width),
-      height(height),
-      depth(depth),
-      border(border),
-      format(format),
-      type(type),
-      cleared_rect(cleared_rect) {
-}
-
-TextureDefinition::LevelInfo::LevelInfo(const LevelInfo& other) = default;
-
-TextureDefinition::LevelInfo::~LevelInfo() = default;
-
-TextureDefinition::TextureDefinition()
-    : version_(0),
-      target_(0),
-      min_filter_(0),
-      mag_filter_(0),
-      wrap_s_(0),
-      wrap_t_(0),
-      usage_(0),
-      immutable_(true),
-      immutable_storage_(false),
-      defined_(false) {}
-
-TextureDefinition::TextureDefinition(
-    Texture* texture,
-    unsigned int version,
-    const scoped_refptr<NativeImageBuffer>& image_buffer)
-    : version_(version),
-      target_(texture->target()),
-      image_buffer_(image_buffer),
-      min_filter_(texture->min_filter()),
-      mag_filter_(texture->mag_filter()),
-      wrap_s_(texture->wrap_s()),
-      wrap_t_(texture->wrap_t()),
-      usage_(texture->usage()),
-      immutable_(texture->IsImmutable()),
-      immutable_storage_(texture->HasImmutableStorage()) {
-  const Texture::LevelInfo* level = texture->GetLevelInfo(target_, 0);
-  defined_ = !!level;
-  DCHECK(!image_buffer_.get() || defined_);
-  if (!defined_)
-    return;
-  if (!image_buffer_.get()) {
-    // Don't attempt to share textures that have bound images, as it can't work.
-    if (level->image)
-      return;
-    image_buffer_ = NativeImageBuffer::Create(texture->service_id());
-    DCHECK(image_buffer_.get());
-  }
-
-  if (image_buffer_.get()) {
-    scoped_refptr<gl::GLImage> gl_image(
-        new GLImageSync(image_buffer_, gfx::Size(level->width, level->height)));
-    texture->SetLevelImage(target_, 0, gl_image.get(), Texture::BOUND);
-  }
-
-  level_info_ = LevelInfo(level->target, level->internal_format, level->width,
-                          level->height, level->depth, level->border,
-                          level->format, level->type, level->cleared_rect);
-}
-
-TextureDefinition::TextureDefinition(const TextureDefinition& other) = default;
-
-TextureDefinition::~TextureDefinition() = default;
-
-Texture* TextureDefinition::CreateTexture() const {
-  if (!target_)
-    return nullptr;
-  GLuint texture_id;
-  glGenTextures(1, &texture_id);
-
-  Texture* texture(new Texture(texture_id));
-  UpdateTextureInternal(texture);
-
-  return texture;
-}
-
-void TextureDefinition::UpdateTextureInternal(Texture* texture) const {
-  gl::ScopedTextureBinder texture_binder(target_, texture->service_id());
-  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter_);
-  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter_);
-  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s_);
-  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_t_);
-
-  if (image_buffer_.get()) {
-    gl::GLImage* existing_image = texture->GetLevelImage(target_, 0);
-    // Don't need to re-bind if already bound before.
-    if (!existing_image || !image_buffer_->IsClient(existing_image)) {
-      image_buffer_->BindToTexture(target_);
-    }
-  }
-
-  texture->face_infos_.resize(1);
-  texture->face_infos_[0].level_infos.resize(1);
-  if (defined_) {
-    texture->SetLevelInfo(level_info_.target, 0,
-                          level_info_.internal_format, level_info_.width,
-                          level_info_.height, level_info_.depth,
-                          level_info_.border, level_info_.format,
-                          level_info_.type, level_info_.cleared_rect);
-    texture->face_infos_[0].level_infos.resize(
-        std::max(1, texture->face_infos_[0].num_mip_levels));
-  }
-
-  if (image_buffer_.get()) {
-    texture->SetLevelImage(
-        target_, 0,
-        new GLImageSync(image_buffer_,
-                        gfx::Size(level_info_.width, level_info_.height)),
-        Texture::BOUND);
-  }
-
-  texture->target_ = target_;
-  texture->SetImmutable(immutable_, immutable_storage_);
-  texture->sampler_state_.min_filter = min_filter_;
-  texture->sampler_state_.mag_filter = mag_filter_;
-  texture->sampler_state_.wrap_s = wrap_s_;
-  texture->sampler_state_.wrap_t = wrap_t_;
-  texture->usage_ = usage_;
-}
-
-void TextureDefinition::UpdateTexture(Texture* texture) const {
-  GLuint old_service_id = 0u;
-  if (image_buffer_.get() && g_avoid_egl_target_texture_reuse) {
-    GLuint service_id = 0u;
-    glGenTextures(1, &service_id);
-    old_service_id = texture->service_id();
-    texture->SetServiceId(service_id);
-
-    DCHECK_EQ(static_cast<GLenum>(GL_TEXTURE_2D), target_);
-    GLint bound_id = 0;
-    glGetIntegerv(GL_TEXTURE_BINDING_2D, &bound_id);
-    if (bound_id == static_cast<GLint>(old_service_id)) {
-      glBindTexture(target_, service_id);
-    }
-    texture->SetLevelImage(target_, 0, nullptr, Texture::UNBOUND);
-  }
-
-  UpdateTextureInternal(texture);
-
-  if (old_service_id) {
-    glDeleteTextures(1, &old_service_id);
-  }
-}
-
-bool TextureDefinition::Matches(const Texture* texture) const {
-  DCHECK(target_ == texture->target());
-  if (texture->sampler_state_.min_filter != min_filter_ ||
-      texture->sampler_state_.mag_filter != mag_filter_ ||
-      texture->sampler_state_.wrap_s != wrap_s_ ||
-      texture->sampler_state_.wrap_t != wrap_t_ ||
-      texture->SafeToRenderFrom() != SafeToRenderFrom()) {
-    return false;
-  }
-
-  // Texture became defined.
-  if (!image_buffer_.get() && texture->IsDefined())
-    return false;
-
-  // All structural changes should have orphaned the texture.
-  if (image_buffer_.get() && !texture->GetLevelImage(texture->target(), 0))
-    return false;
-
-  return true;
-}
-
-bool TextureDefinition::SafeToRenderFrom() const {
-  return level_info_.cleared_rect.Contains(
-      gfx::Rect(level_info_.width, level_info_.height));
-}
-
-}  // namespace gles2
-}  // namespace gpu
diff --git a/gpu/command_buffer/service/texture_definition.h b/gpu/command_buffer/service/texture_definition.h
deleted file mode 100644
index 07b65bc..0000000
--- a/gpu/command_buffer/service/texture_definition.h
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef GPU_COMMAND_BUFFER_SERVICE_TEXTURE_DEFINITION_H_
-#define GPU_COMMAND_BUFFER_SERVICE_TEXTURE_DEFINITION_H_
-
-#include <vector>
-
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "gpu/command_buffer/service/gl_utils.h"
-#include "ui/gfx/geometry/rect.h"
-
-namespace gl {
-class GLImage;
-}
-
-namespace gpu {
-namespace gles2 {
-
-class Texture;
-
-class NativeImageBuffer : public base::RefCountedThreadSafe<NativeImageBuffer> {
- public:
-  static scoped_refptr<NativeImageBuffer> Create(GLuint texture_id);
-
-  virtual void AddClient(gl::GLImage* client) = 0;
-  virtual void RemoveClient(gl::GLImage* client) = 0;
-  virtual bool IsClient(gl::GLImage* client) = 0;
-  virtual void BindToTexture(GLenum target) const = 0;
-
- protected:
-  friend class base::RefCountedThreadSafe<NativeImageBuffer>;
-  NativeImageBuffer() = default;
-  virtual ~NativeImageBuffer() = default;
-
-  DISALLOW_COPY_AND_ASSIGN(NativeImageBuffer);
-};
-
-// An immutable description that can be used to create a texture that shares
-// the underlying image buffer(s).
-class TextureDefinition {
- public:
-  static void AvoidEGLTargetTextureReuse();
-
-  TextureDefinition();
-  TextureDefinition(Texture* texture,
-                    unsigned int version,
-                    const scoped_refptr<NativeImageBuffer>& image);
-  TextureDefinition(const TextureDefinition& other);
-  virtual ~TextureDefinition();
-
-  Texture* CreateTexture() const;
-
-  void UpdateTexture(Texture* texture) const;
-
-  unsigned int version() const { return version_; }
-  bool IsOlderThan(unsigned int version) const {
-    return (version - version_) < 0x80000000;
-  }
-  bool Matches(const Texture* texture) const;
-
-  scoped_refptr<NativeImageBuffer> image() const { return image_buffer_; }
-
- private:
-  bool SafeToRenderFrom() const;
-  void UpdateTextureInternal(Texture* texture) const;
-
-  struct LevelInfo {
-    LevelInfo();
-    LevelInfo(GLenum target,
-              GLenum internal_format,
-              GLsizei width,
-              GLsizei height,
-              GLsizei depth,
-              GLint border,
-              GLenum format,
-              GLenum type,
-              const gfx::Rect& cleared_rect);
-    LevelInfo(const LevelInfo& other);
-    ~LevelInfo();
-
-    GLenum target;
-    GLenum internal_format;
-    GLsizei width;
-    GLsizei height;
-    GLsizei depth;
-    GLint border;
-    GLenum format;
-    GLenum type;
-    gfx::Rect cleared_rect;
-  };
-
-  unsigned int version_;
-  GLenum target_;
-  scoped_refptr<NativeImageBuffer> image_buffer_;
-  GLenum min_filter_;
-  GLenum mag_filter_;
-  GLenum wrap_s_;
-  GLenum wrap_t_;
-  GLenum usage_;
-  bool immutable_;
-  bool immutable_storage_;
-  bool defined_;
-
-  // Only support textures with one face and one level.
-  LevelInfo level_info_;
-};
-
-}  // namespage gles2
-}  // namespace gpu
-
-#endif  // GPU_COMMAND_BUFFER_SERVICE_TEXTURE_DEFINITION_H_
diff --git a/gpu/command_buffer/service/texture_manager.h b/gpu/command_buffer/service/texture_manager.h
index cb079bf..85907fb 100644
--- a/gpu/command_buffer/service/texture_manager.h
+++ b/gpu/command_buffer/service/texture_manager.h
@@ -474,7 +474,6 @@
 
  private:
   friend class MailboxManagerTest;
-  friend class TextureDefinition;
   friend class TextureManager;
   friend class TextureRef;
   friend class TextureTestHelper;
diff --git a/gpu/config/gpu_driver_bug_list.json b/gpu/config/gpu_driver_bug_list.json
index 125d57b..af2816e 100644
--- a/gpu/config/gpu_driver_bug_list.json
+++ b/gpu/config/gpu_driver_bug_list.json
@@ -718,9 +718,6 @@
         "value": "1.3",
         "value2": "1.4"
       },
-      "features": [
-        "avoid_egl_image_target_texture_reuse"
-      ],
       "disabled_extensions": [
         "EGL_KHR_wait_sync"
       ]
diff --git a/gpu/config/gpu_workaround_list.txt b/gpu/config/gpu_workaround_list.txt
index 1f9ebc0f..0d2063c 100644
--- a/gpu/config/gpu_workaround_list.txt
+++ b/gpu/config/gpu_workaround_list.txt
@@ -1,6 +1,5 @@
 add_and_true_to_loop_condition
 adjust_src_dst_region_for_blitframebuffer
-avoid_egl_image_target_texture_reuse
 avoid_one_component_egl_images
 avoid_stencil_buffers
 broken_egl_image_ref_counting
diff --git a/infra/config/generated/cr-buildbucket.cfg b/infra/config/generated/cr-buildbucket.cfg
index 9cbb381a..58162e53 100644
--- a/infra/config/generated/cr-buildbucket.cfg
+++ b/infra/config/generated/cr-buildbucket.cfg
@@ -46891,7 +46891,7 @@
       dimensions: "builder:linux-rel"
       dimensions: "cores:8"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04|Ubuntu-18.04"
+      dimensions: "os:Ubuntu-16.04"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
diff --git a/infra/config/subprojects/chromium/try.star b/infra/config/subprojects/chromium/try.star
index 9b622e6..89b42edc 100644
--- a/infra/config/subprojects/chromium/try.star
+++ b/infra/config/subprojects/chromium/try.star
@@ -1229,8 +1229,6 @@
     main_list_view = "try",
     tryjob = try_.job(),
     use_clang_coverage = True,
-    # TODO(crbug/1199425): Remove this once bionic bot is the default option.
-    os = os.LINUX_XENIAL_OR_BIONIC,
 )
 
 # Experimental builder to check dual coverage on linux platform.
diff --git a/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/bottom_sheet/bottom_sheet_navigation_controller.mm b/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/bottom_sheet/bottom_sheet_navigation_controller.mm
index bcf4179a..959d9ca57 100644
--- a/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/bottom_sheet/bottom_sheet_navigation_controller.mm
+++ b/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/bottom_sheet/bottom_sheet_navigation_controller.mm
@@ -8,6 +8,7 @@
 
 #import "base/check.h"
 #import "ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/bottom_sheet/child_bottom_sheet_view_controller.h"
+#import "ios/chrome/common/ui/util/background_util.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -24,7 +25,7 @@
 @interface BottomSheetNavigationController ()
 
 // View to get transparent blurred background.
-@property(nonatomic, strong, readwrite) UIVisualEffectView* visualEffectView;
+@property(nonatomic, strong, readwrite) UIView* backgroundView;
 
 @end
 
@@ -47,7 +48,7 @@
 }
 
 - (void)didUpdateControllerViewFrame {
-  self.visualEffectView.frame = self.view.bounds;
+  self.backgroundView.frame = self.view.bounds;
   // The dimmer view should never be under the bottom sheet view, since the
   // background of the botther sheet is transparent.
   CGRect dimmerViewFrame = self.backgroundDimmerView.superview.bounds;
@@ -59,17 +60,9 @@
 
 - (void)viewDidLoad {
   [super viewDidLoad];
-  UIVisualEffect* blurEffect = nil;
-  if (@available(iOS 13, *)) {
-    blurEffect =
-        [UIBlurEffect effectWithStyle:UIBlurEffectStyleSystemThickMaterial];
-  } else {
-    blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
-  }
-  self.visualEffectView =
-      [[UIVisualEffectView alloc] initWithEffect:blurEffect];
-  [self.view insertSubview:self.visualEffectView atIndex:0];
-  self.visualEffectView.frame = self.view.bounds;
+  self.backgroundView = PrimaryBackgroundBlurView();
+  [self.view insertSubview:self.backgroundView atIndex:0];
+  self.backgroundView.frame = self.view.bounds;
 }
 
 - (void)viewWillAppear:(BOOL)animated {
diff --git a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm
index 89676e3..9a7a3cab 100644
--- a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm
+++ b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm
@@ -327,11 +327,16 @@
     AddressProfileSavePromptCallback callback) {
   DCHECK(base::FeatureList::IsEnabled(
       features::kAutofillAddressProfileSavePrompt));
-  auto delegate = std::make_unique<AutofillSaveAddressProfileDelegateIOS>(
-      profile, std::move(callback));
-  infobar_manager_->AddInfoBar(std::make_unique<InfoBarIOS>(
-      InfobarType::kInfobarTypeSaveAutofillAddressProfile,
-      std::move(delegate)));
+  if (IsInfobarOverlayUIEnabled()) {
+    auto delegate = std::make_unique<AutofillSaveAddressProfileDelegateIOS>(
+        profile, std::move(callback));
+    infobar_manager_->AddInfoBar(std::make_unique<InfoBarIOS>(
+        InfobarType::kInfobarTypeSaveAutofillAddressProfile,
+        std::move(delegate)));
+  } else {
+    DCHECK(personal_data_manager_);
+    personal_data_manager_->SaveImportedProfile(profile);
+  }
 }
 
 bool ChromeAutofillClientIOS::HasCreditCardScanFeature() {
diff --git a/ios/chrome/browser/ui/open_in/BUILD.gn b/ios/chrome/browser/ui/open_in/BUILD.gn
index e76a181..e965a46 100644
--- a/ios/chrome/browser/ui/open_in/BUILD.gn
+++ b/ios/chrome/browser/ui/open_in/BUILD.gn
@@ -3,10 +3,7 @@
 # found in the LICENSE file.
 
 source_set("open_in_ui") {
-  configs += [
-    "//build/config/compiler:enable_arc",
-    "//build/config/ios:disable_implicit_retain_self_warning",
-  ]
+  configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
     "open_in_controller.h",
     "open_in_controller.mm",
diff --git a/ios/chrome/browser/ui/open_in/open_in_controller.mm b/ios/chrome/browser/ui/open_in/open_in_controller.mm
index b3946ed..cc4dbd2 100644
--- a/ios/chrome/browser/ui/open_in/open_in_controller.mm
+++ b/ios/chrome/browser/ui/open_in/open_in_controller.mm
@@ -504,20 +504,8 @@
   // Set completion callback.
   __weak OpenInController* weakSelf = self;
   activityViewController.completionWithItemsHandler =
-      ^(NSString* activityType, BOOL completed, NSArray* returnedItems,
-        NSError* activityError) {
-        weakSelf.sequencedTaskRunner->PostTask(FROM_HERE, base::BindOnce(^{
-                                                 RemoveDocumentAtPath(fileURL);
-                                               }));
-
-        if (IsIPadIdiom()) {
-          _openInTimer = [NSTimer
-              scheduledTimerWithTimeInterval:kOpenInToolbarDisplayDuration
-                                      target:self
-                                    selector:@selector(hideOpenInToolbar)
-                                    userInfo:nil
-                                     repeats:NO];
-        }
+      ^(NSString*, BOOL, NSArray*, NSError*) {
+        [weakSelf completedPresentOpenInMenuForFileAtURL:fileURL];
       };
 
   // UIActivityViewController is presented in a popover on iPad.
@@ -532,6 +520,21 @@
                                       completion:nil];
 }
 
+- (void)completedPresentOpenInMenuForFileAtURL:(NSURL*)fileURL {
+  _sequencedTaskRunner->PostTask(FROM_HERE, base::BindOnce(^{
+                                   RemoveDocumentAtPath(fileURL);
+                                 }));
+
+  if (IsIPadIdiom()) {
+    _openInTimer =
+        [NSTimer scheduledTimerWithTimeInterval:kOpenInToolbarDisplayDuration
+                                         target:self
+                                       selector:@selector(hideOpenInToolbar)
+                                       userInfo:nil
+                                        repeats:NO];
+  }
+}
+
 - (void)showDownloadOverlayView {
   _overlayedView = [[UIView alloc] initWithFrame:[self.baseView bounds]];
   [_overlayedView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth |
diff --git a/ios/chrome/common/ui/util/BUILD.gn b/ios/chrome/common/ui/util/BUILD.gn
index d61204b..2e6c85c 100644
--- a/ios/chrome/common/ui/util/BUILD.gn
+++ b/ios/chrome/common/ui/util/BUILD.gn
@@ -7,6 +7,8 @@
 
 source_set("util") {
   sources = [
+    "background_util.h",
+    "background_util.mm",
     "button_util.h",
     "button_util.mm",
     "constraints_ui_util.h",
diff --git a/ios/chrome/common/ui/util/background_util.h b/ios/chrome/common/ui/util/background_util.h
new file mode 100644
index 0000000..7599eaa
--- /dev/null
+++ b/ios/chrome/common/ui/util/background_util.h
@@ -0,0 +1,14 @@
+// Copyright 2021 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_COMMON_UI_UTIL_BACKGROUND_UTIL_H_
+#define IOS_CHROME_COMMON_UI_UTIL_BACKGROUND_UTIL_H_
+
+#import <UIKit/UIKit.h>
+
+// Returns UIView with the default blur background (for iOS 12, a UIView with a
+// plain color is returned).
+UIView* PrimaryBackgroundBlurView();
+
+#endif  // IOS_CHROME_COMMON_UI_UTIL_BACKGROUND_UTIL_H_
diff --git a/ios/chrome/common/ui/util/background_util.mm b/ios/chrome/common/ui/util/background_util.mm
new file mode 100644
index 0000000..2f6b3e02
--- /dev/null
+++ b/ios/chrome/common/ui/util/background_util.mm
@@ -0,0 +1,24 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/common/ui/util/background_util.h"
+
+#import "ios/chrome/common/ui/colors/semantic_color_names.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+UIView* PrimaryBackgroundBlurView() {
+  UIView* view;
+  if (@available(iOS 13, *)) {
+    UIVisualEffect* blurEffect =
+        [UIBlurEffect effectWithStyle:UIBlurEffectStyleSystemThickMaterial];
+    view = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
+  } else {
+    view = [[UIView alloc] init];
+    view.backgroundColor = [UIColor colorNamed:kPrimaryBackgroundColor];
+  }
+  return view;
+}
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1
index 498d338..7c8d236 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-3af764b5cafbad302990f6161286ce59415de9bf
\ No newline at end of file
+eef3770852bb7da6928c116dd6e7ffbda3a30db7
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1
index cd81241..de81c4e 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-dc921cac5f38aec2178e38f85766a64d7a2cd533
\ No newline at end of file
+aeb1d917f8837e12f5c90cf6b801b61963bdc7b8
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1
index 2c476f6e..af841ac 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-5efeaaf3854bb85b70a973815de1d0110bb5b453
\ No newline at end of file
+dd34ad80e6b0541dfc58b2f93abfb0ed1b54c874
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1
index d1b1a6bc..d65853b 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-25c7512eafc3b56559e805ab83e03616775ada8c
\ No newline at end of file
+128e01acc824e6c646cab937b10dbf369aa98365
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1
index 0f2bc06..4dcf28c 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-de284b7be312a5c654af991d7d44d50998c38302
\ No newline at end of file
+913bf98d713131a8cf2b2c35d3fa7b1229078e78
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1
index 26be152..a392175 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-713f5d59a9e4ec40c49bfd93c8703c959e6e4c64
\ No newline at end of file
+0b8d0e6c979c4c666d7ba2fbf1984a3f3434f750
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1
index 76e29a0..4e57681 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-76e6f691d673373fc5a27fa740c319f0076b6491
\ No newline at end of file
+49f2dc26606d38f05319842c54a7271e0101781e
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1
index b14edad..bc47828 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-eeda5868e070ecc52ea1c0bc37dbaf94335cb7ef
\ No newline at end of file
+e8a0b45e35a0090f91c42fa38b1e494d942a4bab
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1
index de21e149..f1f8de1 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-6f2ca0aabbd83f8ece3e93e2a99c6e4c5c4a094f
\ No newline at end of file
+d8892f035855093a1563f0ea6d1b18be4408211b
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1
index e52f38b..961bd0f 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-1d1b24d0f0266f301d07ae5505598b091cfe358d
\ No newline at end of file
+bfb9431712c6abf09f457fbdc7c2a6d0a043fc35
\ No newline at end of file
diff --git a/media/base/status.cc b/media/base/status.cc
index 1e30c22..8bfa2f9 100644
--- a/media/base/status.cc
+++ b/media/base/status.cc
@@ -5,6 +5,7 @@
 #include "media/base/status.h"
 
 #include <memory>
+#include "base/strings/string_piece.h"
 #include "media/base/media_serializers.h"
 
 namespace media {
@@ -19,7 +20,7 @@
     DCHECK(message.empty());
     return;
   }
-  data_ = std::make_unique<StatusInternal>(code, message.as_string());
+  data_ = std::make_unique<StatusInternal>(code, std::string(message));
   AddFrame(location);
 }
 
diff --git a/media/capture/video/linux/camera_config_chromeos.cc b/media/capture/video/linux/camera_config_chromeos.cc
index c2dbdd38..cf4473b 100644
--- a/media/capture/video/linux/camera_config_chromeos.cc
+++ b/media/capture/video/linux/camera_config_chromeos.cc
@@ -12,6 +12,8 @@
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 
+#include "base/strings/string_piece.h"
+
 namespace media {
 
 namespace {
@@ -129,7 +131,7 @@
     DLOG(ERROR) << "Error after split: " << usb_part;
     return std::string();
   }
-  return usb_id_pieces[0].as_string();
+  return std::string(usb_id_pieces[0]);
 }
 
 void CameraConfigChromeOS::InitializeDeviceInfo(
@@ -203,7 +205,7 @@
         DLOG(ERROR) << "model_id is empty";
         continue;
       }
-      std::string model_id = value.as_string();
+      std::string model_id(value);
       std::transform(model_id.begin(), model_id.end(), model_id.begin(),
                      ::tolower);
       model_id_to_camera_id_[model_id] = camera_id;
@@ -212,7 +214,7 @@
         DLOG(ERROR) << "usb_path is empty";
         continue;
       }
-      usb_id_to_camera_id_[value.as_string()] = camera_id;
+      usb_id_to_camera_id_[std::string(value)] = camera_id;
     }
     // Ignore unknown or unutilized attributes.
   }
diff --git a/media/cdm/BUILD.gn b/media/cdm/BUILD.gn
index a8c81b7..fdc464c 100644
--- a/media/cdm/BUILD.gn
+++ b/media/cdm/BUILD.gn
@@ -130,7 +130,10 @@
 
     libs = [ "Propsys.lib" ]
 
-    deps += [ "//media/base/win:media_foundation_util" ]
+    deps += [
+      ":cdm_paths",
+      "//media/base/win:media_foundation_util",
+    ]
   }
 }
 
diff --git a/media/cdm/cdm_paths.cc b/media/cdm/cdm_paths.cc
index 211169c7..84c4a471 100644
--- a/media/cdm/cdm_paths.cc
+++ b/media/cdm/cdm_paths.cc
@@ -6,6 +6,7 @@
 
 #include <string>
 
+#include "base/system/sys_info.h"
 #include "media/media_buildflags.h"
 
 namespace media {
@@ -39,4 +40,17 @@
       base::FilePath::FromUTF8Unsafe(cdm_base_path));
 }
 
+#if defined(OS_WIN)
+const char kCdmStore[] = "CdmStore";
+
+base::FilePath GetCdmStorePath(const base::FilePath& user_data_dir,
+                               const base::UnguessableToken& cdm_origin_id,
+                               const std::string& key_system) {
+  return user_data_dir.AppendASCII(kCdmStore)
+      .AppendASCII(base::SysInfo::ProcessCPUArchitecture())
+      .AppendASCII(cdm_origin_id.ToString())
+      .AppendASCII(key_system);
+}
+#endif  // defined(OS_WIN)
+
 }  // namespace media
diff --git a/media/cdm/cdm_paths.h b/media/cdm/cdm_paths.h
index 7c67d1f..d761a2b 100644
--- a/media/cdm/cdm_paths.h
+++ b/media/cdm/cdm_paths.h
@@ -9,6 +9,8 @@
 
 #include "base/files/file_path.h"
 #include "base/token.h"
+#include "base/unguessable_token.h"
+#include "build/build_config.h"
 
 namespace media {
 
@@ -41,6 +43,17 @@
     const base::FilePath& cdm_base_path);
 base::FilePath GetPlatformSpecificDirectory(const std::string& cdm_base_path);
 
+#if defined(OS_WIN)
+// Returns the "CDM store path" to be passed to `MediaFoundationCdm`. The
+// `user_data_dir` is typically the LPAC specific path, e.g.
+// C:\Users\<user>\AppData\Local\Packages\
+// cr.sb.cdm4b414ceb52402c4e188a185dd531c100416d8daf\AC\Google\Chrome\User Data
+// TODO(xhwang): Separate by Chromium user profile as well.
+base::FilePath GetCdmStorePath(const base::FilePath& user_data_dir,
+                               const base::UnguessableToken& cdm_origin_id,
+                               const std::string& key_system);
+#endif  // defined(OS_WIN)
+
 }  // namespace media
 
 #endif  // MEDIA_CDM_CDM_PATHS_H_
diff --git a/media/cdm/win/media_foundation_cdm_factory.cc b/media/cdm/win/media_foundation_cdm_factory.cc
index 113ce44..9fa61d8 100644
--- a/media/cdm/win/media_foundation_cdm_factory.cc
+++ b/media/cdm/win/media_foundation_cdm_factory.cc
@@ -19,6 +19,7 @@
 #include "media/base/bind_to_current_loop.h"
 #include "media/base/cdm_config.h"
 #include "media/base/win/mf_helpers.h"
+#include "media/cdm/cdm_paths.h"
 #include "media/cdm/win/media_foundation_cdm.h"
 #include "media/cdm/win/media_foundation_cdm_module.h"
 
@@ -124,28 +125,25 @@
   return S_OK;
 }
 
-HRESULT BuildCdmProperties(const base::UnguessableToken& cdm_origin_id,
+HRESULT BuildCdmProperties(const base::UnguessableToken& origin_id,
+                           const base::FilePath& store_path,
                            ComPtr<IPropertyStore>& properties) {
+  DCHECK(!origin_id.is_empty());
+
   ComPtr<IPropertyStore> temp_properties;
   RETURN_IF_FAILED(PSCreateMemoryPropertyStore(IID_PPV_ARGS(&temp_properties)));
 
-  base::win::ScopedPropVariant cdm_origin_id_var;
+  base::win::ScopedPropVariant origin_id_var;
   RETURN_IF_FAILED(InitPropVariantFromString(
-      base::UTF8ToWide(cdm_origin_id.ToString()).c_str(),
-      cdm_origin_id_var.Receive()));
+      base::UTF8ToWide(origin_id.ToString()).c_str(), origin_id_var.Receive()));
   RETURN_IF_FAILED(temp_properties->SetValue(
-      EME_CONTENTDECRYPTIONMODULE_ORIGIN_ID, cdm_origin_id_var.get()));
+      EME_CONTENTDECRYPTIONMODULE_ORIGIN_ID, origin_id_var.get()));
 
-  // TODO(xhwang): Provide per-user, per-profile and per-key-system path here.
-  base::FilePath temp_dir;
-  CHECK(base::GetTempDir(&temp_dir));
-  CHECK(base::DirectoryExists(temp_dir));
-
-  base::win::ScopedPropVariant cdm_store_path_var;
-  RETURN_IF_FAILED(InitPropVariantFromString(temp_dir.value().c_str(),
-                                             cdm_store_path_var.Receive()));
+  base::win::ScopedPropVariant store_path_var;
+  RETURN_IF_FAILED(InitPropVariantFromString(store_path.value().c_str(),
+                                             store_path_var.Receive()));
   RETURN_IF_FAILED(temp_properties->SetValue(
-      MF_CONTENTDECRYPTIONMODULE_STOREPATH, cdm_store_path_var.get()));
+      MF_CONTENTDECRYPTIONMODULE_STOREPATH, store_path_var.get()));
 
   properties = temp_properties;
   return S_OK;
@@ -154,8 +152,9 @@
 }  // namespace
 
 MediaFoundationCdmFactory::MediaFoundationCdmFactory(
-    std::unique_ptr<CdmAuxiliaryHelper> helper)
-    : helper_(std::move(helper)) {}
+    std::unique_ptr<CdmAuxiliaryHelper> helper,
+    const base::FilePath& user_data_dir)
+    : helper_(std::move(helper)), user_data_dir_(user_data_dir) {}
 
 MediaFoundationCdmFactory::~MediaFoundationCdmFactory() = default;
 
@@ -237,16 +236,27 @@
       key_system_str.c_str(), configurations, ARRAYSIZE(configurations),
       &cdm_access));
 
-  // Don't cache `cdm_origin_id` in this class since user can clear it any time.
-  base::UnguessableToken cdm_origin_id = helper_->GetCdmOriginId();
-  if (cdm_origin_id.is_empty()) {
+  // Don't cache `origin_id` in this class since user can clear it any time.
+  base::UnguessableToken origin_id = helper_->GetCdmOriginId();
+  if (origin_id.is_empty()) {
     DLOG(ERROR) << "Failed to get CDM origin ID";
     return E_FAIL;
   }
 
+  // Provide a per-user, per-arch, per-origin and per-key-system path.
+  auto store_path = GetCdmStorePath(user_data_dir_, origin_id, key_system);
+  DVLOG(1) << "store_path=" << store_path;
+
+  // Ensure the path exists. If it already exists, this call will do nothing.
+  base::File::Error file_error;
+  if (!base::CreateDirectoryAndGetError(store_path, &file_error)) {
+    DLOG(ERROR) << "Create CDM store path failed with " << file_error;
+    return E_FAIL;
+  }
+
   ComPtr<IPropertyStore> cdm_properties;
   ComPtr<IMFContentDecryptionModule> cdm;
-  RETURN_IF_FAILED(BuildCdmProperties(cdm_origin_id, cdm_properties));
+  RETURN_IF_FAILED(BuildCdmProperties(origin_id, store_path, cdm_properties));
   RETURN_IF_FAILED(
       cdm_access->CreateContentDecryptionModule(cdm_properties.Get(), &cdm));
 
diff --git a/media/cdm/win/media_foundation_cdm_factory.h b/media/cdm/win/media_foundation_cdm_factory.h
index 099bf04..4cb2b8d7 100644
--- a/media/cdm/win/media_foundation_cdm_factory.h
+++ b/media/cdm/win/media_foundation_cdm_factory.h
@@ -13,6 +13,7 @@
 #include <string>
 
 #include "base/callback.h"
+#include "base/files/file_path.h"
 #include "base/unguessable_token.h"
 #include "base/win/scoped_com_initializer.h"
 #include "media/base/cdm_factory.h"
@@ -23,8 +24,8 @@
 
 class MEDIA_EXPORT MediaFoundationCdmFactory : public CdmFactory {
  public:
-  explicit MediaFoundationCdmFactory(
-      std::unique_ptr<CdmAuxiliaryHelper> helper);
+  MediaFoundationCdmFactory(std::unique_ptr<CdmAuxiliaryHelper> helper,
+                            const base::FilePath& user_data_dir);
   MediaFoundationCdmFactory(const MediaFoundationCdmFactory&) = delete;
   MediaFoundationCdmFactory& operator=(const MediaFoundationCdmFactory&) =
       delete;
@@ -57,6 +58,7 @@
       Microsoft::WRL::ComPtr<IMFContentDecryptionModule>& mf_cdm);
 
   std::unique_ptr<CdmAuxiliaryHelper> helper_;
+  base::FilePath user_data_dir_;
 
   // IMFContentDecryptionModule implementations typically require MTA to run.
   base::win::ScopedCOMInitializer com_initializer_{
diff --git a/media/cdm/win/media_foundation_cdm_factory_unittest.cc b/media/cdm/win/media_foundation_cdm_factory_unittest.cc
index 2771ac3..c5ac065 100644
--- a/media/cdm/win/media_foundation_cdm_factory_unittest.cc
+++ b/media/cdm/win/media_foundation_cdm_factory_unittest.cc
@@ -39,8 +39,8 @@
     auto cdm_helper =
         std::make_unique<StrictMock<MockCdmAuxiliaryHelper>>(nullptr);
     cdm_helper_ = cdm_helper.get();
-    cdm_factory_ =
-        std::make_unique<MediaFoundationCdmFactory>(std::move(cdm_helper));
+    cdm_factory_ = std::make_unique<MediaFoundationCdmFactory>(
+        std::move(cdm_helper), base::FilePath());
   }
 
   ~MediaFoundationCdmFactoryTest() override = default;
diff --git a/media/fuchsia/audio/fuchsia_audio_renderer.cc b/media/fuchsia/audio/fuchsia_audio_renderer.cc
index 871c566..d505792 100644
--- a/media/fuchsia/audio/fuchsia_audio_renderer.cc
+++ b/media/fuchsia/audio/fuchsia_audio_renderer.cc
@@ -163,33 +163,45 @@
     const AudioDecoderConfig& config) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(!stream_sink_);
-  DCHECK(stream_sink_buffers_.empty());
-  DCHECK_EQ(num_pending_packets_, 0U);
 
   // Allocate input buffers for the StreamSink.
-  stream_sink_buffers_.resize(kNumBuffers);
+  std::vector<VmoBuffer> buffers;
+  buffers.resize(kNumBuffers);
+
   std::vector<zx::vmo> vmos_for_stream_sink;
   vmos_for_stream_sink.reserve(kNumBuffers);
-  for (StreamSinkBuffer& buffer : stream_sink_buffers_) {
-    zx_status_t status = zx::vmo::create(kBufferSize, 0, &buffer.vmo);
+  for (VmoBuffer& buffer : buffers) {
+    zx::vmo vmo;
+    zx_status_t status = zx::vmo::create(kBufferSize, 0, &vmo);
     ZX_CHECK(status == ZX_OK, status) << "zx_vmo_create";
 
     constexpr char kName[] = "cr-audio-renderer";
-    status =
-        buffer.vmo.set_property(ZX_PROP_NAME, kName, base::size(kName) - 1);
+    status = vmo.set_property(ZX_PROP_NAME, kName, base::size(kName) - 1);
     ZX_DCHECK(status == ZX_OK, status);
 
     // Duplicate VMO handle to pass to AudioConsumer.
     zx::vmo readonly_vmo;
-    status = buffer.vmo.duplicate(ZX_RIGHT_DUPLICATE | ZX_RIGHT_TRANSFER |
-                                      ZX_RIGHT_READ | ZX_RIGHT_MAP |
-                                      ZX_RIGHT_GET_PROPERTY,
-                                  &readonly_vmo);
+    status =
+        vmo.duplicate(ZX_RIGHT_DUPLICATE | ZX_RIGHT_TRANSFER | ZX_RIGHT_READ |
+                          ZX_RIGHT_MAP | ZX_RIGHT_GET_PROPERTY,
+                      &readonly_vmo);
     ZX_CHECK(status == ZX_OK, status) << "zx_handle_duplicate";
 
+    bool buffer_initialized =
+        buffer.Initialize(std::move(vmo), /*writable=*/true, /*offset=*/0,
+                          kBufferSize, fuchsia::sysmem::CoherencyDomain::RAM);
+    CHECK(buffer_initialized);
+
     vmos_for_stream_sink.push_back(std::move(readonly_vmo));
   }
 
+  input_queue_.Start(
+      std::move(buffers),
+      base::BindRepeating(&FuchsiaAudioRenderer::SendInputPacket,
+                          base::Unretained(this)),
+      base::BindRepeating(&FuchsiaAudioRenderer::ProcessEndOfStream,
+                          base::Unretained(this)));
+
   auto compression = GetFuchsiaCompressionFromDecoderConfig(config);
   if (!compression) {
     LOG(ERROR) << "Unsupported audio codec: " << GetCodecName(config.codec());
@@ -470,8 +482,7 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
   if (!demuxer_stream_ || read_timer_.IsRunning() || is_demuxer_read_pending_ ||
-      is_at_end_of_stream_ ||
-      num_pending_packets_ >= stream_sink_buffers_.size()) {
+      is_at_end_of_stream_ || input_queue_.IsBlocked()) {
     return;
   }
 
@@ -523,8 +534,7 @@
       OnError(PIPELINE_ERROR_READ);
     } else if (read_status == DemuxerStream::kConfigChanged) {
       stream_sink_.Unbind();
-      stream_sink_buffers_.clear();
-      num_pending_packets_ = 0;
+      input_queue_.ResetBuffers();
 
       InitializeStreamSink(demuxer_stream_->audio_decoder_config());
       ScheduleReadDemuxerStream();
@@ -534,73 +544,63 @@
     return;
   }
 
-  if (buffer->end_of_stream()) {
-    is_at_end_of_stream_ = true;
-    stream_sink_->EndOfStream();
+  if (!buffer->end_of_stream()) {
+    if (buffer->data_size() > kBufferSize) {
+      DLOG(ERROR) << "Demuxer returned buffer that is too big: "
+                  << buffer->data_size();
+      OnError(AUDIO_RENDERER_ERROR);
+      return;
+    }
 
-    // No more data is going to be buffered. Update buffering state to ensure
-    // RendererImpl starts playback in case it was waiting for buffering to
-    // finish.
-    SetBufferState(BUFFERING_HAVE_ENOUGH);
-
-    return;
+    last_packet_timestamp_ = buffer->timestamp();
   }
 
-  if (buffer->data_size() > kBufferSize) {
-    DLOG(ERROR) << "Demuxer returned buffer that is too big: "
-                << buffer->data_size();
-    OnError(AUDIO_RENDERER_ERROR);
-    return;
-  }
+  input_queue_.EnqueueBuffer(std::move(buffer));
 
-  // Find unused buffer.
-  auto it = std::find_if(
-      stream_sink_buffers_.begin(), stream_sink_buffers_.end(),
-      [](const StreamSinkBuffer& b) -> bool { return !b.is_used; });
+  ScheduleReadDemuxerStream();
+}
 
-  // ReadDemuxerStream() is not supposed to be called unless there are unused
-  // buffers.
-  CHECK(it != stream_sink_buffers_.end());
+void FuchsiaAudioRenderer::SendInputPacket(
+    const DecoderBuffer* buffer,
+    StreamProcessorHelper::IoPacket packet) {
+  fuchsia::media::StreamPacket stream_packet;
+  stream_packet.payload_buffer_id = packet.buffer_index();
+  stream_packet.pts = packet.timestamp().ToZxDuration();
+  stream_packet.payload_offset = packet.offset();
+  stream_packet.payload_size = packet.size();
 
-  ++num_pending_packets_;
-  DCHECK_LE(num_pending_packets_, stream_sink_buffers_.size());
-
-  it->is_used = true;
-  zx_status_t status = it->vmo.write(buffer->data(), 0, buffer->data_size());
-  ZX_CHECK(status == ZX_OK, status) << "zx_vmo_write";
-
-  size_t buffer_index = it - stream_sink_buffers_.begin();
-
-  fuchsia::media::StreamPacket packet;
-  packet.payload_buffer_id = buffer_index;
-  packet.pts = buffer->timestamp().ToZxDuration();
-  packet.payload_offset = 0;
-  packet.payload_size = buffer->data_size();
-
-  stream_sink_->SendPacket(std::move(packet), [this, buffer_index]() {
-    OnStreamSendDone(buffer_index);
-  });
+  stream_sink_->SendPacket(
+      std::move(stream_packet),
+      [this, packet = std::make_unique<StreamProcessorHelper::IoPacket>(
+                 std::move(packet))]() mutable {
+        OnStreamSendDone(std::move(packet));
+      });
 
   // AudioConsumer doesn't report exact time when the data is decoded, but it's
   // safe to report it as decoded right away since the packet is expected to be
   // decoded soon after AudioConsumer receives it.
   PipelineStatistics stats;
-  stats.audio_bytes_decoded = buffer->data_size();
+  stats.audio_bytes_decoded = packet.size();
   client_->OnStatisticsUpdate(stats);
-
-  last_packet_timestamp_ = buffer->timestamp();
-
-  ScheduleReadDemuxerStream();
 }
 
-void FuchsiaAudioRenderer::OnStreamSendDone(size_t buffer_index) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  DCHECK_LT(buffer_index, stream_sink_buffers_.size());
-  DCHECK(stream_sink_buffers_[buffer_index].is_used);
-  stream_sink_buffers_[buffer_index].is_used = false;
+void FuchsiaAudioRenderer::ProcessEndOfStream() {
+  is_at_end_of_stream_ = true;
+  stream_sink_->EndOfStream();
 
-  DCHECK_GT(num_pending_packets_, 0U);
-  --num_pending_packets_;
+  // No more data is going to be buffered. Update buffering state to ensure
+  // RendererImpl starts playback in case it was waiting for buffering to
+  // finish.
+  SetBufferState(BUFFERING_HAVE_ENOUGH);
+}
+
+void FuchsiaAudioRenderer::OnStreamSendDone(
+    std::unique_ptr<StreamProcessorHelper::IoPacket> packet) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  // Release the packet first to ensure it's returned to VmoBufferWriterQueue
+  // before ScheduleReadDemuxerStream().
+  packet.reset();
 
   ScheduleReadDemuxerStream();
 }
diff --git a/media/fuchsia/audio/fuchsia_audio_renderer.h b/media/fuchsia/audio/fuchsia_audio_renderer.h
index 65dd995..af0540e3 100644
--- a/media/fuchsia/audio/fuchsia_audio_renderer.h
+++ b/media/fuchsia/audio/fuchsia_audio_renderer.h
@@ -14,6 +14,7 @@
 #include "media/base/buffering_state.h"
 #include "media/base/demuxer_stream.h"
 #include "media/base/time_source.h"
+#include "media/fuchsia/common/vmo_buffer_writer_queue.h"
 
 namespace media {
 
@@ -65,13 +66,6 @@
     kPlaying,
   };
 
-  // Struct used to store state of an input buffer shared with the
-  // |stream_sink_|.
-  struct StreamSinkBuffer {
-    zx::vmo vmo;
-    bool is_used = false;
-  };
-
   // Returns current PlaybackState. Should be used only on the main thread.
   PlaybackState GetPlaybackState() NO_THREAD_SAFETY_ANALYSIS;
 
@@ -105,8 +99,14 @@
   void OnDemuxerStreamReadDone(DemuxerStream::Status status,
                                scoped_refptr<DecoderBuffer> buffer);
 
+  // Callbacks for VmoBufferWriterQueue.
+  void SendInputPacket(const DecoderBuffer* buffer,
+                       StreamProcessorHelper::IoPacket packet);
+  void ProcessEndOfStream();
+
   // Result handler for StreamSink::SendPacket().
-  void OnStreamSendDone(size_t buffer_index);
+  void OnStreamSendDone(
+      std::unique_ptr<StreamProcessorHelper::IoPacket> packet);
 
   // Updates buffer state and notifies the |client_| if necessary.
   void SetBufferState(BufferingState buffer_state);
@@ -160,8 +160,7 @@
   base::TimeDelta last_packet_timestamp_ = base::TimeDelta::Min();
   base::OneShotTimer read_timer_;
 
-  std::vector<StreamSinkBuffer> stream_sink_buffers_;
-  size_t num_pending_packets_ = 0u;
+  VmoBufferWriterQueue input_queue_;
 
   // Lead time range requested by the |audio_consumer_|. Initialized to 0 until
   // the initial AudioConsumerStatus is received.
diff --git a/media/fuchsia/common/BUILD.gn b/media/fuchsia/common/BUILD.gn
index ab11908..bda259b 100644
--- a/media/fuchsia/common/BUILD.gn
+++ b/media/fuchsia/common/BUILD.gn
@@ -16,6 +16,8 @@
     "vmo_buffer_writer_queue.h",
   ]
 
+  configs += [ "//media:subcomponent_config" ]
+
   deps = [
     "//base",
     "//media/base",
diff --git a/media/fuchsia/common/stream_processor_helper.h b/media/fuchsia/common/stream_processor_helper.h
index 3b333d1..4aad3ea 100644
--- a/media/fuchsia/common/stream_processor_helper.h
+++ b/media/fuchsia/common/stream_processor_helper.h
@@ -16,6 +16,7 @@
 #include "base/optional.h"
 #include "base/threading/thread_checker.h"
 #include "base/time/time.h"
+#include "media/base/media_export.h"
 
 namespace media {
 
@@ -23,9 +24,9 @@
 // 1. Data validation check.
 // 2. Stream/Buffer life time management.
 // 3. Configure StreamProcessor and input/output buffer settings.
-class StreamProcessorHelper {
+class MEDIA_EXPORT StreamProcessorHelper {
  public:
-  class IoPacket {
+  class MEDIA_EXPORT IoPacket {
    public:
     static IoPacket CreateInput(size_t index,
                                 size_t size,
diff --git a/media/fuchsia/common/vmo_buffer.h b/media/fuchsia/common/vmo_buffer.h
index 67541c0..b505dddd 100644
--- a/media/fuchsia/common/vmo_buffer.h
+++ b/media/fuchsia/common/vmo_buffer.h
@@ -14,10 +14,11 @@
 #include "base/containers/span.h"
 #include "base/memory/shared_memory_mapping.h"
 #include "base/optional.h"
+#include "media/base/media_export.h"
 
 namespace media {
 
-class VmoBuffer {
+class MEDIA_EXPORT VmoBuffer {
  public:
   // Returns sysmem buffer constraints to use to ensure that sysmem buffer
   // collection is compatible with this class.
diff --git a/media/fuchsia/common/vmo_buffer_writer_queue.cc b/media/fuchsia/common/vmo_buffer_writer_queue.cc
index dbc9cb71..650e521 100644
--- a/media/fuchsia/common/vmo_buffer_writer_queue.cc
+++ b/media/fuchsia/common/vmo_buffer_writer_queue.cc
@@ -42,7 +42,10 @@
   base::Optional<size_t> tail_sysmem_buffer_index;
 };
 
-VmoBufferWriterQueue::VmoBufferWriterQueue() = default;
+VmoBufferWriterQueue::VmoBufferWriterQueue() {
+  DETACH_FROM_THREAD(thread_checker_);
+}
+
 VmoBufferWriterQueue::~VmoBufferWriterQueue() = default;
 
 void VmoBufferWriterQueue::EnqueueBuffer(scoped_refptr<DecoderBuffer> buffer) {
@@ -71,6 +74,10 @@
   PumpPackets();
 }
 
+bool VmoBufferWriterQueue::IsBlocked() const {
+  return input_queue_position_ < pending_buffers_.size();
+}
+
 void VmoBufferWriterQueue::PumpPackets() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   auto weak_this = weak_factory_.GetWeakPtr();
diff --git a/media/fuchsia/common/vmo_buffer_writer_queue.h b/media/fuchsia/common/vmo_buffer_writer_queue.h
index 8590e91d..e9a4321 100644
--- a/media/fuchsia/common/vmo_buffer_writer_queue.h
+++ b/media/fuchsia/common/vmo_buffer_writer_queue.h
@@ -14,6 +14,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
 #include "base/threading/thread_checker.h"
+#include "media/base/media_export.h"
 #include "media/fuchsia/common/stream_processor_helper.h"
 #include "media/fuchsia/common/vmo_buffer.h"
 
@@ -23,7 +24,7 @@
 
 // A helper that keeps a queue of pending DecodeBuffers, writes them to a set of
 // VmoBuffers and generates StreamProcessor packets.
-class VmoBufferWriterQueue {
+class MEDIA_EXPORT VmoBufferWriterQueue {
  public:
   // Callback passed to StartSender(). |buffer| corresponds to the original
   // buffer from which the |packet| was generated.
@@ -76,6 +77,10 @@
   // been allocated (i.e. before Start()).
   size_t num_buffers() const;
 
+  // Returns true of the queue is currently blocked, i.e. buffers passed
+  // to EnqueueBuffer() will not be sent immediately.
+  bool IsBlocked() const;
+
  private:
   struct PendingBuffer;
   class SysmemBuffer;
diff --git a/media/mojo/services/media_foundation_mojo_media_client.cc b/media/mojo/services/media_foundation_mojo_media_client.cc
index 7b130bce..9816233 100644
--- a/media/mojo/services/media_foundation_mojo_media_client.cc
+++ b/media/mojo/services/media_foundation_mojo_media_client.cc
@@ -11,7 +11,9 @@
 
 namespace media {
 
-MediaFoundationMojoMediaClient::MediaFoundationMojoMediaClient() {
+MediaFoundationMojoMediaClient::MediaFoundationMojoMediaClient(
+    const base::FilePath& user_data_dir)
+    : user_data_dir_(user_data_dir) {
   DVLOG_FUNC(1);
 }
 
@@ -34,7 +36,7 @@
     mojom::FrameInterfaceFactory* frame_interfaces) {
   DVLOG_FUNC(1);
   return std::make_unique<MediaFoundationCdmFactory>(
-      std::make_unique<MojoCdmHelper>(frame_interfaces));
+      std::make_unique<MojoCdmHelper>(frame_interfaces), user_data_dir_);
 }
 
 }  // namespace media
diff --git a/media/mojo/services/media_foundation_mojo_media_client.h b/media/mojo/services/media_foundation_mojo_media_client.h
index 45286fb3..a4b4b4d 100644
--- a/media/mojo/services/media_foundation_mojo_media_client.h
+++ b/media/mojo/services/media_foundation_mojo_media_client.h
@@ -5,6 +5,7 @@
 #ifndef MEDIA_MOJO_SERVICES_MEDIA_FOUNDATION_MOJO_MEDIA_CLIENT_H_
 #define MEDIA_MOJO_SERVICES_MEDIA_FOUNDATION_MOJO_MEDIA_CLIENT_H_
 
+#include "base/files/file_path.h"
 #include "base/macros.h"
 #include "base/single_thread_task_runner.h"
 #include "media/mojo/services/mojo_media_client.h"
@@ -16,7 +17,7 @@
 // process hosting MediaFoundationRenderer and MediaFoundationCdm.
 class MediaFoundationMojoMediaClient : public MojoMediaClient {
  public:
-  MediaFoundationMojoMediaClient();
+  explicit MediaFoundationMojoMediaClient(const base::FilePath& user_data_dir);
   ~MediaFoundationMojoMediaClient() final;
 
   // MojoMediaClient implementation.
@@ -28,6 +29,7 @@
       mojom::FrameInterfaceFactory* frame_interfaces) final;
 
  private:
+  base::FilePath user_data_dir_;
   DISALLOW_COPY_AND_ASSIGN(MediaFoundationMojoMediaClient);
 };
 
diff --git a/media/mojo/services/media_foundation_service.cc b/media/mojo/services/media_foundation_service.cc
index f195d586..4410d231 100644
--- a/media/mojo/services/media_foundation_service.cc
+++ b/media/mojo/services/media_foundation_service.cc
@@ -17,9 +17,11 @@
 
 MediaFoundationService::MediaFoundationService(
     mojo::PendingReceiver<mojom::MediaFoundationService> receiver,
+    const base::FilePath& user_data_dir,
     base::OnceClosure ensure_sandboxed_cb)
     : receiver_(this, std::move(receiver)),
-      ensure_sandboxed_cb_(std::move(ensure_sandboxed_cb)) {
+      ensure_sandboxed_cb_(std::move(ensure_sandboxed_cb)),
+      mojo_media_client_(user_data_dir) {
   mojo_media_client_.Initialize();
 }
 
diff --git a/media/mojo/services/media_foundation_service.h b/media/mojo/services/media_foundation_service.h
index 132a24d..0e3745f 100644
--- a/media/mojo/services/media_foundation_service.h
+++ b/media/mojo/services/media_foundation_service.h
@@ -5,6 +5,8 @@
 #ifndef MEDIA_MOJO_SERVICES_MEDIA_FOUNDATION_SERVICE_H_
 #define MEDIA_MOJO_SERVICES_MEDIA_FOUNDATION_SERVICE_H_
 
+#include "base/callback.h"
+#include "base/files/file_path.h"
 #include "media/mojo/mojom/frame_interface_factory.mojom.h"
 #include "media/mojo/mojom/interface_factory.mojom.h"
 #include "media/mojo/mojom/media_foundation_service.mojom.h"
@@ -27,6 +29,7 @@
   // ensure the process is sandboxed.
   MediaFoundationService(
       mojo::PendingReceiver<mojom::MediaFoundationService> receiver,
+      const base::FilePath& user_data_dir,
       base::OnceClosure ensure_sandboxed_cb);
   MediaFoundationService(const MediaFoundationService&) = delete;
   MediaFoundationService operator=(const MediaFoundationService&) = delete;
diff --git a/media/mojo/services/watch_time_recorder.cc b/media/mojo/services/watch_time_recorder.cc
index 6cb630e..d98b03b 100644
--- a/media/mojo/services/watch_time_recorder.cc
+++ b/media/mojo/services/watch_time_recorder.cc
@@ -31,7 +31,7 @@
     base::TimeDelta value,
     base::TimeDelta minimum = kMinimumElapsedWatchTime) {
   DCHECK(!key.empty());
-  base::UmaHistogramCustomTimes(key.as_string(), value, minimum,
+  base::UmaHistogramCustomTimes(std::string(key), value, minimum,
                                 base::TimeDelta::FromHours(10), 50);
 }
 
@@ -47,13 +47,13 @@
 static void RecordDiscardedWatchTime(base::StringPiece key,
                                      base::TimeDelta value) {
   DCHECK(!key.empty());
-  base::UmaHistogramCustomTimes(key.as_string(), value, base::TimeDelta(),
+  base::UmaHistogramCustomTimes(std::string(key), value, base::TimeDelta(),
                                 kMinimumElapsedWatchTime, 50);
 }
 
 static void RecordRebuffersCount(base::StringPiece key, int underflow_count) {
   DCHECK(!key.empty());
-  base::UmaHistogramCounts100(key.as_string(), underflow_count);
+  base::UmaHistogramCounts100(std::string(key), underflow_count);
 }
 
 WatchTimeRecorder::WatchTimeUkmRecord::WatchTimeUkmRecord(
diff --git a/media/mojo/services/watch_time_recorder_unittest.cc b/media/mojo/services/watch_time_recorder_unittest.cc
index 08467f0..b35909f 100644
--- a/media/mojo/services/watch_time_recorder_unittest.cc
+++ b/media/mojo/services/watch_time_recorder_unittest.cc
@@ -14,6 +14,7 @@
 #include "base/hash/hash.h"
 #include "base/run_loop.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
 #include "base/test/test_message_loop.h"
@@ -110,10 +111,10 @@
         continue;
       auto it = std::find(keys.begin(), keys.end(), test_key);
       if (it == keys.end()) {
-        histogram_tester_->ExpectTotalCount(test_key.as_string(), 0);
+        histogram_tester_->ExpectTotalCount(test_key, 0);
       } else {
-        histogram_tester_->ExpectUniqueSample(test_key.as_string(),
-                                              value.InMilliseconds(), 1);
+        histogram_tester_->ExpectUniqueSample(test_key, value.InMilliseconds(),
+                                              1);
       }
     }
   }
@@ -124,9 +125,9 @@
     for (auto key : full_key_list) {
       auto it = std::find(keys.begin(), keys.end(), key);
       if (it == keys.end())
-        histogram_tester_->ExpectTotalCount(key.as_string(), 0);
+        histogram_tester_->ExpectTotalCount(key, 0);
       else
-        histogram_tester_->ExpectUniqueSample(key.as_string(), value, 1);
+        histogram_tester_->ExpectUniqueSample(key, value, 1);
     }
   }
 
@@ -225,7 +226,7 @@
 
     auto key_str = ConvertWatchTimeKeyToStringForUma(key);
     SCOPED_TRACE(key_str.empty() ? base::NumberToString(i)
-                                 : key_str.as_string());
+                                 : std::string(key_str));
 
     // Values for |is_background| and |is_muted| don't matter in this test since
     // they don't prevent the muted or background keys from being recorded.
@@ -558,7 +559,7 @@
   // Nothing should be logged since this doesn't meet requirements.
   ExpectMtbrTime({}, base::TimeDelta());
   for (auto key : smooth_keys_)
-    histogram_tester_->ExpectTotalCount(key.as_string(), 0);
+    histogram_tester_->ExpectTotalCount(key, 0);
 }
 
 TEST_F(WatchTimeRecorderTest, TestRebufferingMetricsMediaStream) {
@@ -607,8 +608,7 @@
 
   // Verify the time was instead logged to the discard keys.
   for (auto key : discard_keys_) {
-    histogram_tester_->ExpectUniqueSample(key.as_string(),
-                                          kWatchTime.InMilliseconds(), 1);
+    histogram_tester_->ExpectUniqueSample(key, kWatchTime.InMilliseconds(), 1);
   }
 
   // UKM watch time won't be logged because we aren't sending "All" keys.
diff --git a/media/video/h265_parser.cc b/media/video/h265_parser.cc
index 61fcbbc3..aababea 100644
--- a/media/video/h265_parser.cc
+++ b/media/video/h265_parser.cc
@@ -988,6 +988,14 @@
     memcpy(reinterpret_cast<uint8_t*>(shdr) + skip_amount,
            reinterpret_cast<uint8_t*>(prior_shdr) + skip_amount,
            sizeof(H265SliceHeader) - skip_amount);
+
+    // We also need to validate the fields that have conditions that depend on
+    // anything unique in this slice (i.e. anything already parsed).
+    if ((shdr->irap_pic ||
+         sps->sps_max_dec_pic_buffering_minus1[pps->temporal_id] == 0) &&
+        nalu.nuh_layer_id == 0) {
+      TRUE_OR_RETURN(shdr->slice_type == 2);
+    }
   } else {
     // Set these defaults if they are not present here.
     shdr->pic_output_flag = 1;
diff --git a/media/video/h265_parser.h b/media/video/h265_parser.h
index e322aa813..b3d4198e 100644
--- a/media/video/h265_parser.h
+++ b/media/video/h265_parser.h
@@ -360,6 +360,10 @@
   size_t header_size;  // calculated, not including emulation prevention bytes
   size_t header_emulation_prevention_bytes;
 
+  // Calculated, but needs to be preserved when we copy slice dependent data
+  // so put it at the front.
+  bool irap_pic;
+
   // Syntax elements.
   bool first_slice_segment_in_pic_flag;
   bool no_output_of_prior_pics_flag;
@@ -403,7 +407,6 @@
   // Calculated.
   int curr_rps_idx;
   int num_pic_total_curr;
-  bool irap_pic;
   // Number of bits st_ref_pic_set takes after removing emulation prevention
   // bytes.
   int st_rps_bits;
diff --git a/mojo/public/cpp/bindings/service_factory.cc b/mojo/public/cpp/bindings/service_factory.cc
index b083ac1..1263b3d 100644
--- a/mojo/public/cpp/bindings/service_factory.cc
+++ b/mojo/public/cpp/bindings/service_factory.cc
@@ -33,6 +33,9 @@
     return false;
 
   auto instance = it->second.Run(std::move(receiver));
+  if (!instance)
+    return false;
+
   auto disconnect_callback =
       base::BindOnce(&ServiceFactory::OnInstanceDisconnected,
                      weak_ptr_factory_.GetWeakPtr(), instance.get());
diff --git a/mojo/public/cpp/bindings/service_factory.h b/mojo/public/cpp/bindings/service_factory.h
index baaf683..5959521 100644
--- a/mojo/public/cpp/bindings/service_factory.h
+++ b/mojo/public/cpp/bindings/service_factory.h
@@ -137,8 +137,11 @@
       Func fn,
       GenericPendingReceiver receiver) {
     using Interface = typename internal::ServiceFactoryTraits<Func>::Interface;
-    return std::make_unique<InstanceHolder<Interface>>(
-        fn(receiver.As<Interface>()));
+    auto impl = fn(receiver.As<Interface>());
+    if (!impl)
+      return nullptr;
+
+    return std::make_unique<InstanceHolder<Interface>>(std::move(impl));
   }
 
   void OnInstanceDisconnected(InstanceHolderBase* instance);
diff --git a/pdf/BUILD.gn b/pdf/BUILD.gn
index b6da8fb..796e3d7 100644
--- a/pdf/BUILD.gn
+++ b/pdf/BUILD.gn
@@ -381,6 +381,7 @@
       "pdf_transform_unittest.cc",
       "pdf_utils/dates_unittest.cc",
       "pdf_view_plugin_base_unittest.cc",
+      "pdf_view_web_plugin_unittest.cc",
       "pdfium/accessibility_unittest.cc",
       "pdfium/findtext_unittest.cc",
       "pdfium/pdfium_engine_exports_unittest.cc",
diff --git a/pdf/pdf_view_web_plugin.cc b/pdf/pdf_view_web_plugin.cc
index 575fb703..0f85077f 100644
--- a/pdf/pdf_view_web_plugin.cc
+++ b/pdf/pdf_view_web_plugin.cc
@@ -145,6 +145,11 @@
   return InitializeCommon(std::make_unique<BlinkContainerWrapper>(container));
 }
 
+bool PdfViewWebPlugin::InitializeForTesting(
+    std::unique_ptr<ContainerWrapper> container_wrapper) {
+  return InitializeCommon(std::move(container_wrapper));
+}
+
 // Modeled on `OutOfProcessInstance::Init()`.
 bool PdfViewWebPlugin::InitializeCommon(
     std::unique_ptr<ContainerWrapper> container_wrapper) {
diff --git a/pdf/pdf_view_web_plugin.h b/pdf/pdf_view_web_plugin.h
index 7752932..f84206a 100644
--- a/pdf/pdf_view_web_plugin.h
+++ b/pdf/pdf_view_web_plugin.h
@@ -118,6 +118,12 @@
   // SkiaGraphics::Client:
   void UpdateSnapshot(sk_sp<SkImage> snapshot) override;
 
+  // Initializes the plugin using the `container_wrapper` provided by tests.
+  bool InitializeForTesting(
+      std::unique_ptr<ContainerWrapper> container_wrapper);
+
+  const gfx::Rect& GetPluginRectForTesting() const { return plugin_rect(); }
+
  protected:
   // PdfViewPluginBase:
   base::WeakPtr<PdfViewPluginBase> GetWeakPtr() override;
diff --git a/pdf/pdf_view_web_plugin_unittest.cc b/pdf/pdf_view_web_plugin_unittest.cc
new file mode 100644
index 0000000..b010445
--- /dev/null
+++ b/pdf/pdf_view_web_plugin_unittest.cc
@@ -0,0 +1,109 @@
+// Copyright 2021 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 "pdf/pdf_view_web_plugin.h"
+
+#include <memory>
+#include <utility>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/web/web_plugin_params.h"
+#include "ui/gfx/geometry/rect.h"
+
+namespace chrome_pdf {
+
+namespace {
+
+class FakeContainerWrapper final : public PdfViewWebPlugin::ContainerWrapper {
+ public:
+  FakeContainerWrapper() = default;
+  FakeContainerWrapper(const FakeContainerWrapper&) = delete;
+  FakeContainerWrapper& operator=(const FakeContainerWrapper&) = delete;
+  ~FakeContainerWrapper() override = default;
+
+  // PdfViewWebPlugin::ContainerWrapper:
+  void Invalidate() override {}
+
+  float DeviceScaleFactor() const override { return device_scale_; }
+
+  blink::WebPluginContainer* Container() override { return nullptr; }
+
+  void set_device_scale(float device_scale) { device_scale_ = device_scale; }
+
+ private:
+  float device_scale_ = 1.0f;
+};
+
+}  // namespace
+
+class PdfViewWebPluginTest : public testing::Test {
+ public:
+  // Custom deleter for `plugin_`. PdfViewWebPlugin must be destroyed by
+  // PdfViewWebPlugin::Destroy() instead of its destructor.
+  struct PluginDeleter {
+    void operator()(PdfViewWebPlugin* ptr) { ptr->Destroy(); }
+  };
+
+  PdfViewWebPluginTest() = default;
+  PdfViewWebPluginTest(const PdfViewWebPluginTest&) = delete;
+  PdfViewWebPluginTest& operator=(const PdfViewWebPluginTest&) = delete;
+  ~PdfViewWebPluginTest() override = default;
+
+  void SetUp() override {
+    plugin_ = std::unique_ptr<PdfViewWebPlugin, PluginDeleter>(
+        new PdfViewWebPlugin(blink::WebPluginParams()));
+
+    auto wrapper = std::make_unique<FakeContainerWrapper>();
+    wrapper_ptr_ = wrapper.get();
+    plugin_->InitializeForTesting(std::move(wrapper));
+  }
+
+  void TearDown() override {
+    plugin_.reset();
+    wrapper_ptr_ = nullptr;
+  }
+
+  void TestUpdateGeometrySetsPluginRect(float device_scale,
+                                        const gfx::Rect& window_rect,
+                                        const gfx::Rect& expected_plugin_rect) {
+    // The plugin container's device scale must be set before calling
+    // UpdateGeometry().
+    ASSERT_TRUE(wrapper_ptr_);
+    wrapper_ptr_->set_device_scale(device_scale);
+    plugin_->UpdateGeometry(window_rect, window_rect, window_rect,
+                            /*is_visible=*/true);
+
+    EXPECT_EQ(expected_plugin_rect, plugin_->GetPluginRectForTesting())
+        << "Failure at device scale of " << device_scale << ", window rect of "
+        << window_rect.ToString();
+  }
+
+  FakeContainerWrapper* wrapper_ptr_;
+  std::unique_ptr<PdfViewWebPlugin, PluginDeleter> plugin_;
+};
+
+TEST_F(PdfViewWebPluginTest, UpdateGeometrySetsPluginRect) {
+  struct UpdateGeometryParams {
+    // The plugin container's device scale.
+    float device_scale;
+
+    //  The window area.
+    gfx::Rect window_rect;
+
+    // The expected plugin rect.
+    gfx::Rect expected_plugin_rect;
+  };
+
+  static constexpr UpdateGeometryParams kUpdateGeometryParams[] = {
+      {1.0f, gfx::Rect(3, 4, 5, 6), gfx::Rect(3, 4, 5, 6)},
+      {2.0f, gfx::Rect(5, 6, 7, 8), gfx::Rect(10, 12, 14, 16)},
+  };
+
+  for (const auto& params : kUpdateGeometryParams) {
+    TestUpdateGeometrySetsPluginRect(params.device_scale, params.window_rect,
+                                     params.expected_plugin_rect);
+  }
+}
+
+}  // namespace chrome_pdf
diff --git a/services/audio/input_stream.cc b/services/audio/input_stream.cc
index ee0cfd5..1264c509e 100644
--- a/services/audio/input_stream.cc
+++ b/services/audio/input_stream.cc
@@ -10,6 +10,7 @@
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/strings/strcat.h"
+#include "base/strings/string_piece.h"
 #include "base/strings/stringprintf.h"
 #include "base/trace_event/trace_event.h"
 #include "media/audio/audio_manager.h"
@@ -231,7 +232,7 @@
 void InputStream::OnLog(base::StringPiece message) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
   if (log_)
-    log_->OnLogMessage(message.as_string() + " [id=" + id_.ToString() + "]");
+    log_->OnLogMessage(std::string(message) + " [id=" + id_.ToString() + "]");
 }
 
 void InputStream::OnMuted(bool is_muted) {
diff --git a/services/audio/output_stream.cc b/services/audio/output_stream.cc
index b5f4b5d..0a99fd4 100644
--- a/services/audio/output_stream.cc
+++ b/services/audio/output_stream.cc
@@ -8,6 +8,7 @@
 
 #include "base/bind.h"
 #include "base/callback_helpers.h"
+#include "base/strings/string_piece.h"
 #include "base/strings/stringprintf.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
@@ -245,7 +246,7 @@
 void OutputStream::OnLog(base::StringPiece message) {
   // No sequence check: |log_| is thread-safe.
   if (log_) {
-    log_->OnLogMessage(base::StringPrintf("%s", message.as_string().c_str()));
+    log_->OnLogMessage(base::StringPrintf("%s", std::string(message).c_str()));
   }
 }
 
diff --git a/services/device/public/cpp/hid/hid_blocklist_unittest.cc b/services/device/public/cpp/hid/hid_blocklist_unittest.cc
index 333cf10..a40a108 100644
--- a/services/device/public/cpp/hid/hid_blocklist_unittest.cc
+++ b/services/device/public/cpp/hid/hid_blocklist_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "base/command_line.h"
 #include "base/guid.h"
+#include "base/strings/string_piece.h"
 #include "components/variations/variations_params_manager.h"
 #include "services/device/hid/test_report_descriptors.h"
 #include "services/device/public/cpp/hid/hid_report_descriptor.h"
@@ -36,7 +37,7 @@
     params_manager_.ClearAllVariationParams();
 
     std::map<std::string, std::string> params;
-    params["blocklist_additions"] = list.as_string();
+    params["blocklist_additions"] = std::string(list);
     params_manager_.SetVariationParams("WebHIDBlocklist", params);
 
     blocklist_.ResetToDefaultValuesForTest();
diff --git a/services/device/serial/serial_device_enumerator_win.cc b/services/device/serial/serial_device_enumerator_win.cc
index a039ef2..f638023 100644
--- a/services/device/serial/serial_device_enumerator_win.cc
+++ b/services/device/serial/serial_device_enumerator_win.cc
@@ -11,6 +11,8 @@
 #include <setupapi.h>
 #include <stdint.h>
 
+#include "base/strings/string_piece.h"
+
 #define INITGUID
 #include <devpkey.h>
 
diff --git a/services/device/time_zone_monitor/time_zone_monitor.cc b/services/device/time_zone_monitor/time_zone_monitor.cc
index d9105334..a71976fd 100644
--- a/services/device/time_zone_monitor/time_zone_monitor.cc
+++ b/services/device/time_zone_monitor/time_zone_monitor.cc
@@ -32,7 +32,7 @@
   VLOG(1) << "timezone reset to " << zone_id_str;
 
   for (auto& client : clients_)
-    client->OnTimeZoneChange(zone_id_str.as_string());
+    client->OnTimeZoneChange(std::string(zone_id_str));
 }
 
 void TimeZoneMonitor::UpdateIcuAndNotifyClients(
diff --git a/services/network/cors/cors_url_loader_unittest.cc b/services/network/cors/cors_url_loader_unittest.cc
index 11126a53..c99e1740 100644
--- a/services/network/cors/cors_url_loader_unittest.cc
+++ b/services/network/cors/cors_url_loader_unittest.cc
@@ -349,9 +349,9 @@
       net::SiteForCookies site_for_cookies = net::SiteForCookies()) {
     net::RedirectInfo redirect_info;
     redirect_info.status_code = status_code;
-    redirect_info.new_method = method.as_string();
+    redirect_info.new_method = std::string(method);
     redirect_info.new_url = url;
-    redirect_info.new_referrer = referrer.as_string();
+    redirect_info.new_referrer = std::string(referrer);
     redirect_info.new_referrer_policy = referrer_policy;
     redirect_info.new_site_for_cookies = site_for_cookies;
     return redirect_info;
diff --git a/services/network/cors/preflight_result.cc b/services/network/cors/preflight_result.cc
index f59de41f..78d1965 100644
--- a/services/network/cors/preflight_result.cc
+++ b/services/network/cors/preflight_result.cc
@@ -6,6 +6,7 @@
 
 #include "base/memory/ptr_util.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
 #include "base/time/default_tick_clock.h"
@@ -91,7 +92,7 @@
       return false;
     }
     set->insert(insert_in_lower_case ? base::ToLowerASCII(value)
-                                     : value.as_string());
+                                     : std::string(value));
   }
   return true;
 }
diff --git a/services/network/public/cpp/content_security_policy/content_security_policy.cc b/services/network/public/cpp/content_security_policy/content_security_policy.cc
index 4cdb4bd..78a854d 100644
--- a/services/network/public/cpp/content_security_policy/content_security_policy.cc
+++ b/services/network/public/cpp/content_security_policy/content_security_policy.cc
@@ -309,7 +309,7 @@
   if (!std::all_of(scheme.begin() + 1, scheme.end(), is_scheme_character))
     return false;
 
-  csp_source->scheme = scheme.as_string();
+  csp_source->scheme = std::string(scheme);
 
   return true;
 }
@@ -343,7 +343,7 @@
         }))
       return false;
   }
-  csp_source->host = host.as_string();
+  csp_source->host = std::string(host);
 
   return true;
 }
@@ -452,7 +452,7 @@
     parsing_errors.emplace_back(base::StringPrintf(
         "The source list for Content Security Policy directive '%s' "
         "contains a source with an invalid path: '%s'. %s",
-        ToString(directive_name).c_str(), expression.as_string().c_str(),
+        ToString(directive_name).c_str(), std::string(expression).c_str(),
         ignoring));
   }
 
@@ -508,7 +508,7 @@
     return false;
   }
 
-  *nonce = subexpression.as_string();
+  *nonce = std::string(subexpression);
   return true;
 }
 
@@ -597,13 +597,13 @@
       continue;
     }
 
-    if (ToCSPDirectiveName(expression.as_string()) !=
+    if (ToCSPDirectiveName(std::string(expression)) !=
         CSPDirectiveName::Unknown) {
       parsing_errors.emplace_back(base::StringPrintf(
           "The Content-Security-Policy directive '%s' contains '%s' as a "
           "source expression. Did you want to add it as a directive and forget "
           "a semicolon?",
-          ToString(directive_name).c_str(), expression.as_string().c_str()));
+          ToString(directive_name).c_str(), std::string(expression).c_str()));
     }
 
     auto csp_source = mojom::CSPSource::New();
@@ -619,7 +619,7 @@
       parsing_errors.emplace_back(base::StringPrintf(
           "The Content-Security-Policy directive 'frame-ancestors' does not "
           "support the source expression '%s'",
-          expression.as_string().c_str()));
+          std::string(expression).c_str()));
       continue;
     }
 
@@ -705,7 +705,7 @@
       parsing_errors.emplace_back(base::StringPrintf(
           "Invalid expression in 'require-trusted-types-for' "
           "Content Security Policy directive: %s.%s\n",
-          expression.as_string().c_str(), hint));
+          std::string(expression).c_str(), hint));
     }
   }
   if (out == network::mojom::CSPRequireTrustedTypesFor::None)
@@ -756,7 +756,7 @@
           "The value of the Content Security Policy directive "
           "'trusted_types' contains an invalid policy: '%s'. "
           "It will be ignored.",
-          expression.as_string().c_str()));
+          std::string(expression).c_str()));
     }
   }
   return out;
@@ -790,7 +790,7 @@
     //   |endpoint| is an arbitrary string. It refers to an endpoint declared in
     //   the "Report-To" header. See https://w3c.github.io/reporting
     if (using_reporting_api) {
-      report_endpoints->push_back(uri.as_string());
+      report_endpoints->push_back(std::string(uri));
 
       // 'report-to' only allows for a single token.
       break;
@@ -827,8 +827,8 @@
         "The Content Security Policy directive '%s' should be empty, but was "
         "delivered with a value of '%s'. The directive has been applied, and "
         "the value ignored.",
-        directive.first.as_string().c_str(),
-        directive.second.as_string().c_str()));
+        std::string(directive.first).c_str(),
+        std::string(directive.second).c_str()));
   }
 }
 
@@ -884,8 +884,8 @@
     const GURL& base_url,
     mojom::ContentSecurityPolicyPtr& out) {
   DirectivesMap directives = ParseHeaderValue(header);
-  out->header =
-      mojom::ContentSecurityPolicyHeader::New(header.as_string(), type, source);
+  out->header = mojom::ContentSecurityPolicyHeader::New(std::string(header),
+                                                        type, source);
   out->self_origin = ComputeSelfOrigin(base_url);
 
   for (auto directive : directives) {
@@ -894,16 +894,16 @@
           "The Content-Security-Policy directive name '%s' contains one or "
           "more invalid characters. Only ASCII alphanumeric characters or "
           "dashes '-' are allowed in directive names.",
-          directive.first.as_string().c_str()));
+          std::string(directive.first).c_str()));
       continue;
     }
 
     CSPDirectiveName directive_name =
-        ToCSPDirectiveName(directive.first.as_string());
+        ToCSPDirectiveName(std::string(directive.first));
 
     if (directive_name == CSPDirectiveName::Unknown) {
       out->parsing_errors.emplace_back(
-          UnrecognizedDirectiveErrorMessage(directive.first.as_string()));
+          UnrecognizedDirectiveErrorMessage(std::string(directive.first)));
       continue;
     }
 
@@ -913,10 +913,10 @@
     if (out->raw_directives.count(directive_name)) {
       out->parsing_errors.emplace_back(base::StringPrintf(
           "Ignoring duplicate Content-Security-Policy directive '%s'.",
-          directive.first.as_string().c_str()));
+          std::string(directive.first).c_str()));
       continue;
     }
-    out->raw_directives[directive_name] = directive.second.as_string();
+    out->raw_directives[directive_name] = std::string(directive.second);
 
     if (!base::ranges::all_of(directive.second, IsDirectiveValueCharacter)) {
       out->parsing_errors.emplace_back(base::StringPrintf(
@@ -928,7 +928,7 @@
           "percent-encoded, as described in RFC 3986, section 2.1 "
           "(http://tools.ietf.org/html/rfc3986#section-2.1), if part of the "
           "path.",
-          directive.first.as_string().c_str()));
+          std::string(directive.first).c_str()));
       continue;
     }
 
@@ -937,7 +937,7 @@
       out->parsing_errors.emplace_back(
           base::StringPrintf("The Content Security Policy directive '%s' is "
                              "ignored when delivered in a report-only policy.",
-                             directive.first.as_string().c_str()));
+                             std::string(directive.first).c_str()));
       continue;
     }
 
diff --git a/services/network/public/cpp/content_security_policy/content_security_policy_unittest.cc b/services/network/public/cpp/content_security_policy/content_security_policy_unittest.cc
index 94d4d314..4430092 100644
--- a/services/network/public/cpp/content_security_policy/content_security_policy_unittest.cc
+++ b/services/network/public/cpp/content_security_policy/content_security_policy_unittest.cc
@@ -5,6 +5,7 @@
 #include "services/network/public/cpp/content_security_policy/content_security_policy.h"
 
 #include "base/containers/contains.h"
+#include "base/strings/string_piece.h"
 #include "base/strings/stringprintf.h"
 #include "net/http/http_response_headers.h"
 #include "services/network/public/cpp/content_security_policy/csp_context.h"
@@ -1486,8 +1487,8 @@
                 policies[0]->directives[mojom::CSPDirectiveName::ScriptSrc]));
 
     EXPECT_EQ(policies[0]->raw_directives[mojom::CSPDirectiveName::ScriptSrc],
-              base::TrimString(test.directive_value, " ", base::TRIM_ALL)
-                  .as_string());
+              std::string(
+                  base::TrimString(test.directive_value, " ", base::TRIM_ALL)));
 
     if (!test.expected_error.empty())
       EXPECT_EQ(test.expected_error, policies[0]->parsing_errors[0]);
diff --git a/services/network/public/cpp/content_security_policy/csp_context_unittest.cc b/services/network/public/cpp/content_security_policy/csp_context_unittest.cc
index 83fe53a1..47b42e9 100644
--- a/services/network/public/cpp/content_security_policy/csp_context_unittest.cc
+++ b/services/network/public/cpp/content_security_policy/csp_context_unittest.cc
@@ -4,6 +4,7 @@
 
 #include <set>
 
+#include "base/strings/string_piece.h"
 #include "services/network/public/cpp/content_security_policy/content_security_policy.h"
 #include "services/network/public/cpp/content_security_policy/csp_context.h"
 #include "services/network/public/mojom/content_security_policy.mojom.h"
@@ -27,7 +28,7 @@
   }
 
   bool SchemeShouldBypassCSP(const base::StringPiece& scheme) override {
-    return scheme_to_bypass_.count(scheme.as_string());
+    return scheme_to_bypass_.count(std::string(scheme));
   }
 
   void ClearViolations() { violations_.clear(); }
diff --git a/services/network/public/cpp/simple_url_loader_unittest.cc b/services/network/public/cpp/simple_url_loader_unittest.cc
index 9b40f425..eb7df92 100644
--- a/services/network/public/cpp/simple_url_loader_unittest.cc
+++ b/services/network/public/cpp/simple_url_loader_unittest.cc
@@ -27,6 +27,7 @@
 #include "base/sequence_checker.h"
 #include "base/sequenced_task_runner.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/bind.h"
 #include "base/test/task_environment.h"
@@ -420,7 +421,7 @@
       done_ = true;
     }
 
-    download_as_stream_response_body_.append(string_piece.as_string());
+    download_as_stream_response_body_.append(std::string(string_piece));
 
     if (download_to_stream_destroy_on_data_received_) {
       run_loop_.Quit();
diff --git a/services/preferences/public/cpp/dictionary_value_update.cc b/services/preferences/public/cpp/dictionary_value_update.cc
index 62a07464..61dca381 100644
--- a/services/preferences/public/cpp/dictionary_value_update.cc
+++ b/services/preferences/public/cpp/dictionary_value_update.cc
@@ -8,6 +8,7 @@
 #include <iterator>
 #include <utility>
 
+#include "base/strings/string_piece.h"
 #include "base/strings/string_split.h"
 #include "base/values.h"
 
@@ -126,7 +127,7 @@
       value_->SetWithoutPathExpansion(path, std::move(in_value)));
 
   std::vector<std::string> full_path = path_;
-  full_path.push_back(path.as_string());
+  full_path.push_back(std::string(path));
   return std::make_unique<DictionaryValueUpdate>(
       report_update_, dictionary_value, std::move(full_path));
 }
@@ -229,7 +230,7 @@
     return false;
 
   std::vector<std::string> full_path = path_;
-  full_path.push_back(key.as_string());
+  full_path.push_back(std::string(key));
   *out_value = std::make_unique<DictionaryValueUpdate>(
       report_update_, dictionary_value, std::move(full_path));
   return true;
@@ -325,7 +326,7 @@
   std::vector<std::string> full_path = base_path;
   full_path.reserve(full_path.size() + path.size());
   std::transform(path.begin(), path.end(), std::back_inserter(full_path),
-                 [](base::StringPiece s) { return s.as_string(); });
+                 [](base::StringPiece s) { return std::string(s); });
   return full_path;
 }
 
diff --git a/services/preferences/public/cpp/scoped_pref_update.cc b/services/preferences/public/cpp/scoped_pref_update.cc
index b771ddd..78acc26 100644
--- a/services/preferences/public/cpp/scoped_pref_update.cc
+++ b/services/preferences/public/cpp/scoped_pref_update.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/strings/string_piece.h"
 #include "components/prefs/pref_service.h"
 #include "services/preferences/public/cpp/dictionary_value_update.h"
 
@@ -14,7 +15,7 @@
 
 ScopedDictionaryPrefUpdate::ScopedDictionaryPrefUpdate(PrefService* service,
                                                        base::StringPiece path)
-    : service_(service), path_(path.as_string()) {}
+    : service_(service), path_(path) {}
 
 ScopedDictionaryPrefUpdate::~ScopedDictionaryPrefUpdate() {
   if (!updated_paths_.empty())
diff --git a/services/proxy_resolver/public/cpp/proxy_resolver_mojom_traits.cc b/services/proxy_resolver/public/cpp/proxy_resolver_mojom_traits.cc
index 93fd87e..a498c9c 100644
--- a/services/proxy_resolver/public/cpp/proxy_resolver_mojom_traits.cc
+++ b/services/proxy_resolver/public/cpp/proxy_resolver_mojom_traits.cc
@@ -5,6 +5,7 @@
 #include "services/proxy_resolver/public/cpp/proxy_resolver_mojom_traits.h"
 
 #include "base/notreached.h"
+#include "base/strings/string_piece.h"
 #include "net/base/host_port_pair.h"
 #include "net/base/proxy_server.h"
 #include "net/proxy_resolution/proxy_info.h"
@@ -103,7 +104,7 @@
   }
 
   *out = net::ProxyServer(scheme,
-                          net::HostPortPair(host.as_string(), data.port()));
+                          net::HostPortPair(std::string(host), data.port()));
   return true;
 }
 
diff --git a/services/resource_coordinator/memory_instrumentation/graph.cc b/services/resource_coordinator/memory_instrumentation/graph.cc
index c88e9e0..2045af9 100644
--- a/services/resource_coordinator/memory_instrumentation/graph.cc
+++ b/services/resource_coordinator/memory_instrumentation/graph.cc
@@ -5,6 +5,7 @@
 #include "services/resource_coordinator/memory_instrumentation/graph.h"
 
 #include "base/callback.h"
+#include "base/strings/string_piece.h"
 #include "base/strings/string_tokenizer.h"
 
 namespace memory_instrumentation {
@@ -80,7 +81,7 @@
                           bool weak) {
   DCHECK(!path.empty());
 
-  std::string path_string = path.as_string();
+  std::string path_string(path);
   base::StringTokenizer tokenizer(path_string, "/");
 
   // Perform a tree traversal, creating the nodes if they do not
@@ -114,7 +115,7 @@
 Node* Process::FindNode(base::StringPiece path) {
   DCHECK(!path.empty());
 
-  std::string path_string = path.as_string();
+  std::string path_string(path);
   base::StringTokenizer tokenizer(path_string, "/");
   Node* current = root_;
   while (tokenizer.GetNext()) {
@@ -134,7 +135,7 @@
   DCHECK(!name.empty());
   DCHECK_EQ(std::string::npos, name.find('/'));
 
-  auto child = children_.find(name.as_string());
+  auto child = children_.find(std::string(name));
   return child == children_.end() ? nullptr : child->second;
 }
 
@@ -142,7 +143,7 @@
   DCHECK(!name.empty());
   DCHECK_EQ(std::string::npos, name.find('/'));
 
-  children_.emplace(name.as_string(), node);
+  children_.emplace(std::string(name), node);
 }
 
 Node* Node::CreateChild(base::StringPiece name) {
diff --git a/services/tracing/public/cpp/perfetto/traced_value_proto_writer.cc b/services/tracing/public/cpp/perfetto/traced_value_proto_writer.cc
index 4eb9fd2e..4af24cb 100644
--- a/services/tracing/public/cpp/perfetto/traced_value_proto_writer.cc
+++ b/services/tracing/public/cpp/perfetto/traced_value_proto_writer.cc
@@ -8,6 +8,7 @@
 
 #include "base/hash/hash.h"
 #include "base/json/string_escape.h"
+#include "base/strings/string_piece.h"
 #include "base/trace_event/trace_event.h"
 #include "base/trace_event/traced_value.h"
 #include "third_party/perfetto/include/perfetto/protozero/message_handle.h"
@@ -125,7 +126,7 @@
   }
 
   void SetValueWithCopiedName(base::StringPiece name, Writer* value) override {
-    SetValue(name.as_string().c_str(), value);
+    SetValue(std::string(name).c_str(), value);
   }
 
   void BeginArray() override {
diff --git a/services/tracing/public/cpp/stack_sampling/reached_code_data_source_android.cc b/services/tracing/public/cpp/stack_sampling/reached_code_data_source_android.cc
index a076b01d..6344baa 100644
--- a/services/tracing/public/cpp/stack_sampling/reached_code_data_source_android.cc
+++ b/services/tracing/public/cpp/stack_sampling/reached_code_data_source_android.cc
@@ -10,6 +10,7 @@
 #include "base/android/reached_addresses_bitset.h"
 #include "base/android/reached_code_profiler.h"
 #include "base/debug/elf_reader.h"
+#include "base/strings/string_piece.h"
 #include "services/tracing/public/cpp/perfetto/perfetto_producer.h"
 #include "services/tracing/public/cpp/stack_sampling/tracing_sampler_profiler.h"
 #include "third_party/perfetto/include/perfetto/tracing/data_source.h"
@@ -80,7 +81,7 @@
   if (library_name) {
     auto* str = interned_data->add_mapping_paths();
     str->set_iid(0);
-    str->set_str(library_name->as_string());
+    str->set_str(std::string(*library_name));
   }
 
   std::vector<uint32_t> offsets = bitset->GetReachedOffsets();
diff --git a/storage/browser/BUILD.gn b/storage/browser/BUILD.gn
index 70a19fa..6ff0d10 100644
--- a/storage/browser/BUILD.gn
+++ b/storage/browser/BUILD.gn
@@ -168,14 +168,10 @@
     "file_system/sandbox_file_system_backend.h",
     "file_system/sandbox_file_system_backend_delegate.cc",
     "file_system/sandbox_file_system_backend_delegate.h",
-    "file_system/sandbox_isolated_origin_database.cc",
-    "file_system/sandbox_isolated_origin_database.h",
     "file_system/sandbox_origin_database.cc",
     "file_system/sandbox_origin_database.h",
     "file_system/sandbox_origin_database_interface.cc",
     "file_system/sandbox_origin_database_interface.h",
-    "file_system/sandbox_prioritized_origin_database.cc",
-    "file_system/sandbox_prioritized_origin_database.h",
     "file_system/sandbox_quota_observer.cc",
     "file_system/sandbox_quota_observer.h",
     "file_system/task_runner_bound_observer_list.h",
@@ -320,9 +316,7 @@
     "file_system/sandbox_file_stream_writer_unittest.cc",
     "file_system/sandbox_file_system_backend_delegate_unittest.cc",
     "file_system/sandbox_file_system_backend_unittest.cc",
-    "file_system/sandbox_isolated_origin_database_unittest.cc",
     "file_system/sandbox_origin_database_unittest.cc",
-    "file_system/sandbox_prioritized_origin_database_unittest.cc",
     "file_system/transient_file_util_unittest.cc",
     "quota/quota_database_unittest.cc",
     "quota/quota_manager_unittest.cc",
diff --git a/storage/browser/file_system/dump_file_system.cc b/storage/browser/file_system/dump_file_system.cc
index fe85f66..84c25c8 100644
--- a/storage/browser/file_system/dump_file_system.cc
+++ b/storage/browser/file_system/dump_file_system.cc
@@ -9,8 +9,6 @@
 // ./out/Release/dump_file_system [options] <filesystem dir> [origin]...
 //
 // If no origin is specified, this dumps all origins in the profile dir.
-// For Chrome App, which has a separate storage directory, specify "primary"
-// as the origin name.
 //
 // Available options:
 //
@@ -46,7 +44,6 @@
 #include "storage/browser/file_system/sandbox_directory_database.h"
 #include "storage/browser/file_system/sandbox_file_system_backend.h"
 #include "storage/browser/file_system/sandbox_origin_database.h"
-#include "storage/browser/file_system/sandbox_prioritized_origin_database.h"
 #include "storage/common/file_system/file_system_types.h"
 #include "storage/common/file_system/file_system_util.h"
 
@@ -136,11 +133,6 @@
 
 static base::FilePath GetOriginDir(const base::FilePath& file_system_dir,
                                    const std::string& origin_name) {
-  if (base::PathExists(file_system_dir.Append(
-          SandboxPrioritizedOriginDatabase::kPrimaryOriginFile))) {
-    return base::FilePath(SandboxPrioritizedOriginDatabase::kPrimaryDirectory);
-  }
-
   SandboxOriginDatabase origin_db(file_system_dir, nullptr);
   base::FilePath origin_dir;
   if (!origin_db.HasOriginPath(origin_name)) {
diff --git a/storage/browser/file_system/obfuscated_file_util.cc b/storage/browser/file_system/obfuscated_file_util.cc
index c2c9332..268b62d 100644
--- a/storage/browser/file_system/obfuscated_file_util.cc
+++ b/storage/browser/file_system/obfuscated_file_util.cc
@@ -29,9 +29,7 @@
 #include "storage/browser/file_system/obfuscated_file_util_memory_delegate.h"
 #include "storage/browser/file_system/quota/quota_limit_type.h"
 #include "storage/browser/file_system/sandbox_file_system_backend.h"
-#include "storage/browser/file_system/sandbox_isolated_origin_database.h"
 #include "storage/browser/file_system/sandbox_origin_database.h"
-#include "storage/browser/file_system/sandbox_prioritized_origin_database.h"
 #include "storage/browser/quota/quota_manager.h"
 #include "storage/common/database/database_identifier.h"
 #include "storage/common/file_system/file_system_util.h"
@@ -979,41 +977,6 @@
   return UsageForPath(VirtualPath::BaseName(path).value().size());
 }
 
-void ObfuscatedFileUtil::MaybePrepopulateDatabase(
-    const std::vector<std::string>& type_strings_to_prepopulate) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  SandboxPrioritizedOriginDatabase database(file_system_directory_,
-                                            env_override_);
-  std::string origin_string = database.GetPrimaryOrigin();
-  if (origin_string.empty() || !database.HasOriginPath(origin_string))
-    return;
-  const url::Origin origin = GetOriginFromIdentifier(origin_string);
-
-  // Prepopulate the directory database(s) if and only if this instance
-  // has primary origin and the directory database is already there.
-  for (const std::string& type_string : type_strings_to_prepopulate) {
-    // Only handles known types.
-    if (!base::Contains(known_type_strings_, type_string))
-      continue;
-    base::File::Error error = base::File::FILE_ERROR_FAILED;
-    base::FilePath path =
-        GetDirectoryForOriginAndType(origin, type_string, false, &error);
-    if (error != base::File::FILE_OK)
-      continue;
-    std::unique_ptr<SandboxDirectoryDatabase> db =
-        std::make_unique<SandboxDirectoryDatabase>(path, env_override_);
-    if (db->Init(SandboxDirectoryDatabase::FAIL_ON_CORRUPTION)) {
-      directories_[GetDirectoryDatabaseKey(origin, type_string)] =
-          std::move(db);
-      MarkUsed();
-      // Don't populate more than one database, as it may rather hurt
-      // performance.
-      break;
-    }
-  }
-}
-
 base::FilePath ObfuscatedFileUtil::GetDirectoryForURL(
     const FileSystemURL& url,
     bool create,
@@ -1340,19 +1303,8 @@
     }
   }
 
-  std::unique_ptr<SandboxPrioritizedOriginDatabase>
-      prioritized_origin_database =
-          std::make_unique<SandboxPrioritizedOriginDatabase>(
-              file_system_directory_, env_override_);
-
-  if (origin_hint.opaque() && HasIsolatedStorage(origin_hint)) {
-    const std::string isolated_origin_string =
-        GetIdentifierFromOrigin(origin_hint);
-    prioritized_origin_database->InitializePrimaryOrigin(
-        isolated_origin_string);
-  }
-
-  origin_database_ = std::move(prioritized_origin_database);
+  origin_database_ = std::make_unique<SandboxOriginDatabase>(
+      file_system_directory_, env_override_);
 
   return true;
 }
diff --git a/storage/browser/file_system/obfuscated_file_util.h b/storage/browser/file_system/obfuscated_file_util.h
index 5bf8012..a7a2e0a 100644
--- a/storage/browser/file_system/obfuscated_file_util.h
+++ b/storage/browser/file_system/obfuscated_file_util.h
@@ -196,13 +196,6 @@
   // on each path segment and add the results.
   static int64_t ComputeFilePathCost(const base::FilePath& path);
 
-  // Tries to prepopulate directory database for the given type strings.
-  // This tries from the first one in the given type_strings and stops
-  // once it succeeds to do so for one database (i.e. it prepopulates
-  // at most one database).
-  void MaybePrepopulateDatabase(
-      const std::vector<std::string>& type_strings_to_prepopulate);
-
   // This will rewrite the databases to remove traces of deleted data from disk.
   void RewriteDatabases();
 
diff --git a/storage/browser/file_system/obfuscated_file_util_unittest.cc b/storage/browser/file_system/obfuscated_file_util_unittest.cc
index cd1d6ce..76fc27e 100644
--- a/storage/browser/file_system/obfuscated_file_util_unittest.cc
+++ b/storage/browser/file_system/obfuscated_file_util_unittest.cc
@@ -36,7 +36,6 @@
 #include "storage/browser/file_system/obfuscated_file_util_memory_delegate.h"
 #include "storage/browser/file_system/sandbox_directory_database.h"
 #include "storage/browser/file_system/sandbox_file_system_backend_delegate.h"
-#include "storage/browser/file_system/sandbox_isolated_origin_database.h"
 #include "storage/browser/file_system/sandbox_origin_database.h"
 #include "storage/browser/quota/quota_manager.h"
 #include "storage/browser/test/async_file_test_helper.h"
diff --git a/storage/browser/file_system/sandbox_file_system_backend_delegate.cc b/storage/browser/file_system/sandbox_file_system_backend_delegate.cc
index 5c7755d..51b07994 100644
--- a/storage/browser/file_system/sandbox_file_system_backend_delegate.cc
+++ b/storage/browser/file_system/sandbox_file_system_backend_delegate.cc
@@ -57,9 +57,6 @@
 const char kPersistentDirectoryName[] = "p";
 const char kSyncableDirectoryName[] = "s";
 
-const char* const kPrepopulateTypes[] = {kPersistentDirectoryName,
-                                         kTemporaryDirectoryName};
-
 enum FileSystemError {
   kOK = 0,
   kIncognito,
@@ -212,19 +209,6 @@
       special_storage_policy_(special_storage_policy),
       file_system_options_(file_system_options),
       is_filesystem_opened_(false) {
-  // Prepopulate database only if it can run asynchronously (i.e. the current
-  // sequence is not file_task_runner). Usually this is the case but may not
-  // in test code.
-  if (!file_system_options.is_incognito() &&
-      !file_task_runner_->RunsTasksInCurrentSequence()) {
-    std::vector<std::string> types_to_prepopulate(
-        &kPrepopulateTypes[0],
-        &kPrepopulateTypes[base::size(kPrepopulateTypes)]);
-    file_task_runner_->PostTask(
-        FROM_HERE, base::BindOnce(&ObfuscatedFileUtil::MaybePrepopulateDatabase,
-                                  base::Unretained(obfuscated_file_util()),
-                                  types_to_prepopulate));
-  }
 }
 
 SandboxFileSystemBackendDelegate::~SandboxFileSystemBackendDelegate() {
diff --git a/storage/browser/file_system/sandbox_isolated_origin_database.cc b/storage/browser/file_system/sandbox_isolated_origin_database.cc
deleted file mode 100644
index a465dcd..0000000
--- a/storage/browser/file_system/sandbox_isolated_origin_database.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "storage/browser/file_system/sandbox_isolated_origin_database.h"
-
-#include "base/files/file_util.h"
-#include "storage/browser/file_system/sandbox_origin_database.h"
-
-namespace storage {
-
-SandboxIsolatedOriginDatabase::SandboxIsolatedOriginDatabase(
-    const std::string& origin,
-    const base::FilePath& file_system_directory,
-    const base::FilePath& origin_directory)
-    : origin_(origin),
-      file_system_directory_(file_system_directory),
-      origin_directory_(origin_directory) {}
-
-SandboxIsolatedOriginDatabase::~SandboxIsolatedOriginDatabase() = default;
-
-bool SandboxIsolatedOriginDatabase::HasOriginPath(const std::string& origin) {
-  return (origin_ == origin);
-}
-
-bool SandboxIsolatedOriginDatabase::GetPathForOrigin(
-    const std::string& origin,
-    base::FilePath* directory) {
-  if (origin != origin_)
-    return false;
-  *directory = origin_directory_;
-  return true;
-}
-
-bool SandboxIsolatedOriginDatabase::RemovePathForOrigin(
-    const std::string& origin) {
-  return true;
-}
-
-bool SandboxIsolatedOriginDatabase::ListAllOrigins(
-    std::vector<OriginRecord>* origins) {
-  origins->push_back(OriginRecord(origin_, origin_directory_));
-  return true;
-}
-
-void SandboxIsolatedOriginDatabase::DropDatabase() {}
-
-void SandboxIsolatedOriginDatabase::RewriteDatabase() {}
-
-}  // namespace storage
diff --git a/storage/browser/file_system/sandbox_isolated_origin_database.h b/storage/browser/file_system/sandbox_isolated_origin_database.h
deleted file mode 100644
index 5700d72..0000000
--- a/storage/browser/file_system/sandbox_isolated_origin_database.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef STORAGE_BROWSER_FILE_SYSTEM_SANDBOX_ISOLATED_ORIGIN_DATABASE_H_
-#define STORAGE_BROWSER_FILE_SYSTEM_SANDBOX_ISOLATED_ORIGIN_DATABASE_H_
-
-#include <string>
-#include <vector>
-
-#include "base/component_export.h"
-#include "base/macros.h"
-#include "storage/browser/file_system/sandbox_origin_database_interface.h"
-
-namespace storage {
-
-// This origin database implementation supports only one origin
-// (therefore is expected to run very fast).
-class COMPONENT_EXPORT(STORAGE_BROWSER) SandboxIsolatedOriginDatabase
-    : public SandboxOriginDatabaseInterface {
- public:
-  static const base::FilePath::CharType kObsoleteOriginDirectory[];
-
-  // Initialize this database for |origin| which makes GetPathForOrigin return
-  // |origin_directory| (in |file_system_directory|).
-  SandboxIsolatedOriginDatabase(const std::string& origin,
-                                const base::FilePath& file_system_directory,
-                                const base::FilePath& origin_directory);
-  ~SandboxIsolatedOriginDatabase() override;
-
-  // SandboxOriginDatabaseInterface overrides.
-  bool HasOriginPath(const std::string& origin) override;
-  bool GetPathForOrigin(const std::string& origin,
-                        base::FilePath* directory) override;
-  bool RemovePathForOrigin(const std::string& origin) override;
-  bool ListAllOrigins(std::vector<OriginRecord>* origins) override;
-  void DropDatabase() override;
-  void RewriteDatabase() override;
-
-  const std::string& origin() const { return origin_; }
-
- private:
-  const std::string origin_;
-  const base::FilePath file_system_directory_;
-  const base::FilePath origin_directory_;
-
-  DISALLOW_COPY_AND_ASSIGN(SandboxIsolatedOriginDatabase);
-};
-
-}  // namespace storage
-
-#endif  // STORAGE_BROWSER_FILE_SYSTEM_SANDBOX_ISOLATED_ORIGIN_DATABASE_H_
diff --git a/storage/browser/file_system/sandbox_isolated_origin_database_unittest.cc b/storage/browser/file_system/sandbox_isolated_origin_database_unittest.cc
deleted file mode 100644
index 9f27cea..0000000
--- a/storage/browser/file_system/sandbox_isolated_origin_database_unittest.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "storage/browser/file_system/sandbox_isolated_origin_database.h"
-#include "base/files/file_util.h"
-#include "base/files/scoped_temp_dir.h"
-#include "storage/browser/file_system/sandbox_origin_database.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace storage {
-
-namespace {
-const base::FilePath::CharType kOriginDirectory[] = FILE_PATH_LITERAL("iso");
-}  // namespace
-
-TEST(SandboxIsolatedOriginDatabaseTest, BasicTest) {
-  base::ScopedTempDir dir;
-  ASSERT_TRUE(dir.CreateUniqueTempDir());
-
-  std::string kOrigin("origin");
-  SandboxIsolatedOriginDatabase database(kOrigin, dir.GetPath(),
-                                         base::FilePath(kOriginDirectory));
-
-  EXPECT_TRUE(database.HasOriginPath(kOrigin));
-
-  base::FilePath path1, path2;
-
-  EXPECT_FALSE(database.GetPathForOrigin(std::string(), &path1));
-  EXPECT_FALSE(database.GetPathForOrigin("foo", &path1));
-
-  EXPECT_TRUE(database.HasOriginPath(kOrigin));
-  EXPECT_TRUE(database.GetPathForOrigin(kOrigin, &path1));
-  EXPECT_TRUE(database.GetPathForOrigin(kOrigin, &path2));
-  EXPECT_FALSE(path1.empty());
-  EXPECT_FALSE(path2.empty());
-  EXPECT_EQ(path1, path2);
-}
-
-}  // namespace storage
diff --git a/storage/browser/file_system/sandbox_prioritized_origin_database.cc b/storage/browser/file_system/sandbox_prioritized_origin_database.cc
deleted file mode 100644
index d364b54..0000000
--- a/storage/browser/file_system/sandbox_prioritized_origin_database.cc
+++ /dev/null
@@ -1,228 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "storage/browser/file_system/sandbox_prioritized_origin_database.h"
-
-#include <memory>
-
-#include "base/check.h"
-#include "base/files/file.h"
-#include "base/files/file_util.h"
-#include "base/pickle.h"
-#include "storage/browser/file_system/sandbox_isolated_origin_database.h"
-#include "storage/browser/file_system/sandbox_origin_database.h"
-#include "third_party/leveldatabase/leveldb_chrome.h"
-
-namespace storage {
-
-const base::FilePath::CharType* const
-    SandboxPrioritizedOriginDatabase::kPrimaryDirectory =
-        FILE_PATH_LITERAL("primary");
-
-const base::FilePath::CharType* const
-    SandboxPrioritizedOriginDatabase::kPrimaryOriginFile =
-        FILE_PATH_LITERAL("primary.origin");
-
-namespace {
-
-bool WritePrimaryOriginFile(const base::FilePath& path,
-                            const std::string& origin) {
-  base::File file(path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_WRITE);
-  if (!file.IsValid())
-    return false;
-  if (!file.created())
-    file.SetLength(0);
-  base::Pickle pickle;
-  pickle.WriteString(origin);
-  file.Write(0, static_cast<const char*>(pickle.data()), pickle.size());
-  file.Flush();
-  return true;
-}
-
-bool ReadPrimaryOriginFile(const base::FilePath& path, std::string* origin) {
-  std::string buffer;
-  if (!base::ReadFileToString(path, &buffer))
-    return false;
-  base::Pickle pickle(buffer.data(), buffer.size());
-  base::PickleIterator iter(pickle);
-  return iter.ReadString(origin) && !origin->empty();
-}
-
-}  // namespace
-
-SandboxPrioritizedOriginDatabase::SandboxPrioritizedOriginDatabase(
-    const base::FilePath& file_system_directory,
-    leveldb::Env* env_override)
-    : file_system_directory_(file_system_directory),
-      env_override_(env_override),
-      primary_origin_file_(file_system_directory_.Append(kPrimaryOriginFile)) {}
-
-SandboxPrioritizedOriginDatabase::~SandboxPrioritizedOriginDatabase() = default;
-
-bool SandboxPrioritizedOriginDatabase::InitializePrimaryOrigin(
-    const std::string& origin) {
-  const bool is_in_memory =
-      env_override_ && leveldb_chrome::IsMemEnv(env_override_);
-  if (!primary_origin_database_ && !is_in_memory) {
-    if (!MaybeLoadPrimaryOrigin() && ResetPrimaryOrigin(origin)) {
-      MaybeMigrateDatabase(origin);
-      primary_origin_database_ =
-          std::make_unique<SandboxIsolatedOriginDatabase>(
-              origin, file_system_directory_,
-              base::FilePath(kPrimaryDirectory));
-      return true;
-    }
-  }
-
-  if (primary_origin_database_)
-    return primary_origin_database_->HasOriginPath(origin);
-
-  return false;
-}
-
-std::string SandboxPrioritizedOriginDatabase::GetPrimaryOrigin() {
-  MaybeLoadPrimaryOrigin();
-  if (primary_origin_database_)
-    return primary_origin_database_->origin();
-  return std::string();
-}
-
-bool SandboxPrioritizedOriginDatabase::HasOriginPath(
-    const std::string& origin) {
-  MaybeInitializeDatabases(false);
-  if (primary_origin_database_ &&
-      primary_origin_database_->HasOriginPath(origin))
-    return true;
-  if (origin_database_)
-    return origin_database_->HasOriginPath(origin);
-  return false;
-}
-
-bool SandboxPrioritizedOriginDatabase::GetPathForOrigin(
-    const std::string& origin,
-    base::FilePath* directory) {
-  MaybeInitializeDatabases(true);
-  if (primary_origin_database_ &&
-      primary_origin_database_->GetPathForOrigin(origin, directory))
-    return true;
-  DCHECK(origin_database_);
-  return origin_database_->GetPathForOrigin(origin, directory);
-}
-
-bool SandboxPrioritizedOriginDatabase::RemovePathForOrigin(
-    const std::string& origin) {
-  MaybeInitializeDatabases(false);
-  if (primary_origin_database_ &&
-      primary_origin_database_->HasOriginPath(origin)) {
-    primary_origin_database_.reset();
-    base::DeletePathRecursively(
-        file_system_directory_.Append(kPrimaryOriginFile));
-    return true;
-  }
-  if (origin_database_)
-    return origin_database_->RemovePathForOrigin(origin);
-  return true;
-}
-
-bool SandboxPrioritizedOriginDatabase::ListAllOrigins(
-    std::vector<OriginRecord>* origins) {
-  // SandboxOriginDatabase may clear the |origins|, so call this before
-  // primary_origin_database_.
-  MaybeInitializeDatabases(false);
-  if (origin_database_ && !origin_database_->ListAllOrigins(origins))
-    return false;
-  if (primary_origin_database_)
-    return primary_origin_database_->ListAllOrigins(origins);
-  return true;
-}
-
-void SandboxPrioritizedOriginDatabase::DropDatabase() {
-  primary_origin_database_.reset();
-  origin_database_.reset();
-}
-
-void SandboxPrioritizedOriginDatabase::RewriteDatabase() {
-  if (primary_origin_database_)
-    primary_origin_database_->RewriteDatabase();
-  if (origin_database_)
-    origin_database_->RewriteDatabase();
-}
-
-bool SandboxPrioritizedOriginDatabase::MaybeLoadPrimaryOrigin() {
-  if (primary_origin_database_)
-    return true;
-  std::string saved_origin;
-  if (!ReadPrimaryOriginFile(primary_origin_file_, &saved_origin))
-    return false;
-  primary_origin_database_ = std::make_unique<SandboxIsolatedOriginDatabase>(
-      saved_origin, file_system_directory_, base::FilePath(kPrimaryDirectory));
-  return true;
-}
-
-bool SandboxPrioritizedOriginDatabase::ResetPrimaryOrigin(
-    const std::string& origin) {
-  DCHECK(!primary_origin_database_);
-  if (!WritePrimaryOriginFile(primary_origin_file_, origin))
-    return false;
-  // We reset the primary origin directory too.
-  // (This means the origin file corruption causes data loss
-  // We could keep the directory there as the same origin will likely
-  // become the primary origin, but let's play conservatively.)
-  base::DeletePathRecursively(file_system_directory_.Append(kPrimaryDirectory));
-  return true;
-}
-
-void SandboxPrioritizedOriginDatabase::MaybeMigrateDatabase(
-    const std::string& origin) {
-  MaybeInitializeNonPrimaryDatabase(false);
-  if (!origin_database_)
-    return;
-  if (origin_database_->HasOriginPath(origin)) {
-    base::FilePath directory_name;
-    if (origin_database_->GetPathForOrigin(origin, &directory_name) &&
-        directory_name != base::FilePath(kPrimaryOriginFile)) {
-      base::FilePath from_path = file_system_directory_.Append(directory_name);
-      base::FilePath to_path = file_system_directory_.Append(kPrimaryDirectory);
-
-      if (base::PathExists(to_path))
-        base::DeletePathRecursively(to_path);
-      base::Move(from_path, to_path);
-    }
-
-    origin_database_->RemovePathForOrigin(origin);
-  }
-
-  std::vector<OriginRecord> origins;
-  origin_database_->ListAllOrigins(&origins);
-  if (origins.empty()) {
-    origin_database_->RemoveDatabase();
-    origin_database_.reset();
-  }
-}
-
-void SandboxPrioritizedOriginDatabase::MaybeInitializeDatabases(bool create) {
-  MaybeLoadPrimaryOrigin();
-  MaybeInitializeNonPrimaryDatabase(create);
-}
-
-void SandboxPrioritizedOriginDatabase::MaybeInitializeNonPrimaryDatabase(
-    bool create) {
-  if (origin_database_)
-    return;
-
-  origin_database_ = std::make_unique<SandboxOriginDatabase>(
-      file_system_directory_, env_override_);
-  if (!create && !base::DirectoryExists(origin_database_->GetDatabasePath())) {
-    origin_database_.reset();
-    return;
-  }
-}
-
-SandboxOriginDatabase*
-SandboxPrioritizedOriginDatabase::GetSandboxOriginDatabase() {
-  MaybeInitializeNonPrimaryDatabase(true);
-  return origin_database_.get();
-}
-
-}  // namespace storage
diff --git a/storage/browser/file_system/sandbox_prioritized_origin_database.h b/storage/browser/file_system/sandbox_prioritized_origin_database.h
deleted file mode 100644
index d848ea7..0000000
--- a/storage/browser/file_system/sandbox_prioritized_origin_database.h
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef STORAGE_BROWSER_FILE_SYSTEM_SANDBOX_PRIORITIZED_ORIGIN_DATABASE_H_
-#define STORAGE_BROWSER_FILE_SYSTEM_SANDBOX_PRIORITIZED_ORIGIN_DATABASE_H_
-
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "base/component_export.h"
-#include "base/files/file_path.h"
-#include "base/macros.h"
-#include "storage/browser/file_system/sandbox_origin_database_interface.h"
-
-namespace leveldb {
-class Env;
-}
-
-namespace storage {
-
-class ObfuscatedFileUtil;
-class SandboxIsolatedOriginDatabase;
-class SandboxOriginDatabase;
-
-class COMPONENT_EXPORT(STORAGE_BROWSER) SandboxPrioritizedOriginDatabase
-    : public SandboxOriginDatabaseInterface {
- public:
-  static const base::FilePath::CharType* const kPrimaryDirectory;
-  static const base::FilePath::CharType* const kPrimaryOriginFile;
-
-  SandboxPrioritizedOriginDatabase(const base::FilePath& file_system_directory,
-                                   leveldb::Env* env_override);
-  ~SandboxPrioritizedOriginDatabase() override;
-
-  // Sets |origin| as primary origin in this database (e.g. may
-  // allow faster access).
-  // Returns false if this database already has a primary origin
-  // which is different from |origin|.
-  bool InitializePrimaryOrigin(const std::string& origin);
-  std::string GetPrimaryOrigin();
-
-  // SandboxOriginDatabaseInterface overrides.
-  bool HasOriginPath(const std::string& origin) override;
-  bool GetPathForOrigin(const std::string& origin,
-                        base::FilePath* directory) override;
-  bool RemovePathForOrigin(const std::string& origin) override;
-  bool ListAllOrigins(std::vector<OriginRecord>* origins) override;
-  void DropDatabase() override;
-  void RewriteDatabase() override;
-
-  const base::FilePath& primary_origin_file() const {
-    return primary_origin_file_;
-  }
-
- private:
-  bool MaybeLoadPrimaryOrigin();
-  bool ResetPrimaryOrigin(const std::string& origin);
-  void MaybeMigrateDatabase(const std::string& origin);
-  void MaybeInitializeDatabases(bool create);
-  void MaybeInitializeNonPrimaryDatabase(bool create);
-
-  // For migration.
-  friend class ObfuscatedFileUtil;
-  SandboxOriginDatabase* GetSandboxOriginDatabase();
-
-  const base::FilePath file_system_directory_;
-  leveldb::Env* env_override_;
-  const base::FilePath primary_origin_file_;
-  std::unique_ptr<SandboxOriginDatabase> origin_database_;
-  std::unique_ptr<SandboxIsolatedOriginDatabase> primary_origin_database_;
-
-  DISALLOW_COPY_AND_ASSIGN(SandboxPrioritizedOriginDatabase);
-};
-
-}  // namespace storage
-
-#endif  // STORAGE_BROWSER_FILE_SYSTEM_SANDBOX_PRIORITIZED_ORIGIN_DATABASE_H_
diff --git a/storage/browser/file_system/sandbox_prioritized_origin_database_unittest.cc b/storage/browser/file_system/sandbox_prioritized_origin_database_unittest.cc
deleted file mode 100644
index 1c805dab..0000000
--- a/storage/browser/file_system/sandbox_prioritized_origin_database_unittest.cc
+++ /dev/null
@@ -1,206 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "storage/browser/file_system/sandbox_prioritized_origin_database.h"
-#include "base/files/file_util.h"
-#include "base/files/scoped_temp_dir.h"
-#include "storage/browser/file_system/sandbox_origin_database.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace storage {
-
-TEST(SandboxPrioritizedOriginDatabaseTest, BasicTest) {
-  base::ScopedTempDir dir;
-  base::FilePath path;
-  ASSERT_TRUE(dir.CreateUniqueTempDir());
-
-  const std::string kOrigin1("origin1");
-  const std::string kOrigin2("origin2");
-
-  SandboxPrioritizedOriginDatabase database(dir.GetPath(), nullptr);
-
-  // Set the kOrigin1 as a parimary origin.
-  EXPECT_TRUE(database.InitializePrimaryOrigin(kOrigin1));
-
-  // Add two origins.
-  EXPECT_TRUE(database.GetPathForOrigin(kOrigin1, &path));
-  EXPECT_TRUE(database.GetPathForOrigin(kOrigin2, &path));
-
-  // Verify them.
-  EXPECT_TRUE(database.HasOriginPath(kOrigin1));
-  EXPECT_TRUE(database.GetPathForOrigin(kOrigin1, &path));
-  EXPECT_FALSE(path.empty());
-  EXPECT_TRUE(database.HasOriginPath(kOrigin2));
-  EXPECT_TRUE(database.GetPathForOrigin(kOrigin2, &path));
-  EXPECT_FALSE(path.empty());
-
-  std::vector<SandboxOriginDatabaseInterface::OriginRecord> origins;
-  database.ListAllOrigins(&origins);
-  ASSERT_EQ(2U, origins.size());
-  EXPECT_TRUE(origins[0].origin == kOrigin1 || origins[1].origin == kOrigin1);
-  EXPECT_TRUE(origins[0].origin == kOrigin2 || origins[1].origin == kOrigin2);
-  EXPECT_NE(origins[0].path, origins[1].path);
-
-  // Try remove path for kOrigin1.
-  database.RemovePathForOrigin(kOrigin1);
-
-  // Verify the removal.
-  EXPECT_FALSE(database.HasOriginPath(kOrigin1));
-  EXPECT_TRUE(database.HasOriginPath(kOrigin2));
-  database.ListAllOrigins(&origins);
-  ASSERT_EQ(1U, origins.size());
-  EXPECT_EQ(kOrigin2, origins[0].origin);
-
-  // Try remove path for kOrigin2.
-  database.RemovePathForOrigin(kOrigin2);
-
-  // Verify the removal.
-  EXPECT_FALSE(database.HasOriginPath(kOrigin1));
-  EXPECT_FALSE(database.HasOriginPath(kOrigin2));
-  database.ListAllOrigins(&origins);
-  EXPECT_TRUE(origins.empty());
-}
-
-TEST(SandboxPrioritizedOriginDatabaseTest, SetPrimaryLaterTest) {
-  base::ScopedTempDir dir;
-  base::FilePath path;
-  ASSERT_TRUE(dir.CreateUniqueTempDir());
-
-  const std::string kOrigin1("origin1");
-  const std::string kOrigin2("origin2");
-
-  SandboxPrioritizedOriginDatabase database(dir.GetPath(), nullptr);
-
-  EXPECT_TRUE(database.GetPrimaryOrigin().empty());
-
-  EXPECT_TRUE(database.GetPathForOrigin(kOrigin1, &path));
-  EXPECT_TRUE(database.GetPathForOrigin(kOrigin2, &path));
-
-  // Set the kOrigin1 as a parimary origin.
-  EXPECT_TRUE(database.InitializePrimaryOrigin(kOrigin1));
-  EXPECT_EQ(kOrigin1, database.GetPrimaryOrigin());
-
-  // Regardless of whether it is initialized as primary or not
-  // they should just work.
-  EXPECT_TRUE(database.HasOriginPath(kOrigin1));
-  EXPECT_TRUE(database.GetPathForOrigin(kOrigin1, &path));
-  EXPECT_FALSE(path.empty());
-  EXPECT_TRUE(database.HasOriginPath(kOrigin2));
-  EXPECT_TRUE(database.GetPathForOrigin(kOrigin2, &path));
-  EXPECT_FALSE(path.empty());
-}
-
-TEST(SandboxPrioritizedOriginDatabaseTest, LostPrimaryOriginFileTest) {
-  base::ScopedTempDir dir;
-  base::FilePath path;
-  ASSERT_TRUE(dir.CreateUniqueTempDir());
-
-  const std::string kOrigin1("origin1");
-  const std::string kData("foo");
-
-  SandboxPrioritizedOriginDatabase database(dir.GetPath(), nullptr);
-
-  EXPECT_TRUE(database.GetPrimaryOrigin().empty());
-
-  // Set the kOrigin1 as a parimary origin.
-  EXPECT_TRUE(database.InitializePrimaryOrigin(kOrigin1));
-  EXPECT_EQ(kOrigin1, database.GetPrimaryOrigin());
-
-  // Make sure it works.
-  EXPECT_TRUE(database.HasOriginPath(kOrigin1));
-  EXPECT_TRUE(database.GetPathForOrigin(kOrigin1, &path));
-
-  // Reset the database.
-  database.DropDatabase();
-
-  // kOrigin1 should still be marked as primary.
-  EXPECT_TRUE(database.HasOriginPath(kOrigin1));
-  EXPECT_TRUE(database.GetPathForOrigin(kOrigin1, &path));
-
-  // Corrupt the primary origin file.
-  base::WriteFile(database.primary_origin_file(), kData);
-
-  // Reset the database.
-  database.DropDatabase();
-
-  // kOrigin1 is no longer marked as primary, and unfortunately we fail
-  // to find the data for the origin.
-  EXPECT_FALSE(database.HasOriginPath(kOrigin1));
-}
-
-TEST(SandboxPrioritizedOriginDatabaseTest, MigrationTest) {
-  base::ScopedTempDir dir;
-  ASSERT_TRUE(dir.CreateUniqueTempDir());
-
-  const std::string kOrigin1("origin1");
-  const std::string kOrigin2("origin2");
-  const std::string kFakeDirectoryData1("0123456789");
-  const std::string kFakeDirectoryData2("abcde");
-  base::FilePath old_dir_db_path1, old_dir_db_path2;
-  base::FilePath path1, path2;
-
-  // Initialize the directory with two origins using the regular
-  // SandboxOriginDatabase.
-  {
-    SandboxOriginDatabase database_old(dir.GetPath(), nullptr);
-    base::FilePath old_db_path = database_old.GetDatabasePath();
-    EXPECT_FALSE(base::PathExists(old_db_path));
-
-    // Initialize paths for kOrigin1 and kOrigin2.
-    EXPECT_TRUE(database_old.GetPathForOrigin(kOrigin1, &path1));
-    EXPECT_FALSE(path1.empty());
-    EXPECT_TRUE(database_old.GetPathForOrigin(kOrigin2, &path2));
-    EXPECT_FALSE(path2.empty());
-
-    EXPECT_TRUE(base::DirectoryExists(old_db_path));
-
-    // Populate the origin directory with some fake data.
-    old_dir_db_path1 = dir.GetPath().Append(path1);
-    ASSERT_TRUE(base::CreateDirectory(old_dir_db_path1));
-    EXPECT_TRUE(base::WriteFile(old_dir_db_path1.AppendASCII("dummy"),
-                                kFakeDirectoryData1));
-    old_dir_db_path2 = dir.GetPath().Append(path2);
-    ASSERT_TRUE(base::CreateDirectory(old_dir_db_path2));
-    EXPECT_TRUE(base::WriteFile(old_dir_db_path2.AppendASCII("dummy"),
-                                kFakeDirectoryData2));
-  }
-
-  // Re-open the directory using sandboxPrioritizedOriginDatabase.
-  SandboxPrioritizedOriginDatabase database(dir.GetPath(), nullptr);
-
-  // Set the kOrigin1 as a parimary origin.
-  // (Trying to initialize another origin should fail).
-  EXPECT_TRUE(database.InitializePrimaryOrigin(kOrigin1));
-  EXPECT_FALSE(database.InitializePrimaryOrigin(kOrigin2));
-
-  EXPECT_EQ(kOrigin1, database.GetPrimaryOrigin());
-
-  // Regardless of whether the origin is registered as primary or not
-  // it should just work.
-  EXPECT_TRUE(database.HasOriginPath(kOrigin1));
-  EXPECT_TRUE(database.GetPathForOrigin(kOrigin1, &path1));
-  EXPECT_TRUE(database.HasOriginPath(kOrigin2));
-  EXPECT_TRUE(database.GetPathForOrigin(kOrigin2, &path2));
-
-  // The directory content must be kept (or migrated if necessary) as well.
-  std::string origin_db_data;
-  base::FilePath dir_db_path = dir.GetPath().Append(path1);
-  EXPECT_TRUE(base::PathExists(dir_db_path.AppendASCII("dummy")));
-  EXPECT_TRUE(base::ReadFileToString(dir_db_path.AppendASCII("dummy"),
-                                     &origin_db_data));
-  EXPECT_EQ(kFakeDirectoryData1, origin_db_data);
-
-  origin_db_data.clear();
-  dir_db_path = dir.GetPath().Append(path2);
-  EXPECT_TRUE(base::PathExists(dir_db_path.AppendASCII("dummy")));
-  EXPECT_TRUE(base::ReadFileToString(dir_db_path.AppendASCII("dummy"),
-                                     &origin_db_data));
-  EXPECT_EQ(kFakeDirectoryData2, origin_db_data);
-
-  // After the migration the kOrigin1 directory database path must be gone.
-  EXPECT_FALSE(base::PathExists(old_dir_db_path1));
-  EXPECT_TRUE(base::PathExists(old_dir_db_path2));
-}
-
-}  // namespace storage
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 699e4f2..67c3ff41 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -1305,7 +1305,7 @@
   },
   "linux-ash-chromium-generator-rel": {
     "additional_compile_targets": [
-      "chrome",
+      "chromiumos_preflight",
       "gen_linux_ash_chromium_cipd_yaml"
     ]
   },
diff --git a/testing/buildbot/chromium.reclient.fyi.json b/testing/buildbot/chromium.reclient.fyi.json
new file mode 100644
index 0000000..adfdde4
--- /dev/null
+++ b/testing/buildbot/chromium.reclient.fyi.json
@@ -0,0 +1,9 @@
+{
+  "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
+  "AAAAA2 See generate_buildbot_json.py to make changes": {},
+  "Linux Builder Re-Client Staging": {
+    "additional_compile_targets": [
+      "all"
+    ]
+  }
+}
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index ffa2cc8..2f60106 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -1719,7 +1719,7 @@
       },
       'linux-ash-chromium-generator-rel': {
         'additional_compile_targets': [
-          'chrome',
+          'chromiumos_preflight',
           'gen_linux_ash_chromium_cipd_yaml',
         ],
       },
@@ -5498,6 +5498,22 @@
     },
   },
   {
+    'name': 'chromium.reclient.fyi',
+    'mixins': ['chromium-tester-service-account'],
+    'machines': {
+      'Linux Builder Re-Client Staging': {
+        # Copied from
+        # https://source.chromium.org/chromium/chromium/src/+/main:testing/buildbot/waterfalls.pyl;l=4844-4854;drc=75f767e92e86611728189739fb26f4e2cdf212d9
+        'mixins': [
+          'isolate_profile_data',
+        ],
+        'additional_compile_targets': [
+          'all'
+        ],
+      },
+    },
+  },
+  {
     'name': 'chromium.swangle',
     'mixins': ['chromium-tester-service-account'],
     'machines': {
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 344596e..3f6816d2 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -918,6 +918,26 @@
             ]
         }
     ],
+    "AutofillEnableOfferNotification": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "chromeos_lacros",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "AutofillEnableOfferNotification"
+                    ]
+                }
+            ]
+        }
+    ],
     "AutofillEnableSupportForMoreStructureInAddresses": [
         {
             "platforms": [
diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD.gn
index 3e4bdc9d..b1bb5f8 100644
--- a/third_party/android_deps/BUILD.gn
+++ b/third_party/android_deps/BUILD.gn
@@ -1258,6 +1258,26 @@
   }
 
   # This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
+  android_aar_prebuilt("google_firebase_firebase_iid_java") {
+    aar_path = "libs/com_google_firebase_firebase_iid/firebase-iid-21.0.1.aar"
+    info_path = "libs/com_google_firebase_firebase_iid/com_google_firebase_firebase_iid.info"
+    deps = [
+      ":google_firebase_firebase_common_java",
+      ":google_firebase_firebase_components_java",
+      ":google_firebase_firebase_iid_interop_java",
+      ":google_firebase_firebase_installations_interop_java",
+      ":google_firebase_firebase_installations_java",
+      ":google_play_services_basement_java",
+      ":google_play_services_cloud_messaging_java",
+      ":google_play_services_stats_java",
+      ":google_play_services_tasks_java",
+      "//third_party/androidx:androidx_collection_collection_java",
+      "//third_party/androidx:androidx_core_core_java",
+      "//third_party/androidx:androidx_legacy_legacy_support_core_utils_java",
+    ]
+  }
+
+  # This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
   android_aar_prebuilt("google_firebase_firebase_messaging_java") {
     aar_path = "libs/com_google_firebase_firebase_messaging/firebase-messaging-21.0.1.aar"
     info_path = "libs/com_google_firebase_firebase_messaging/com_google_firebase_firebase_messaging.info"
@@ -1979,33 +1999,6 @@
   }
 
   # This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-  android_aar_prebuilt("google_firebase_firebase_iid_java") {
-    aar_path = "libs/com_google_firebase_firebase_iid/firebase-iid-21.0.1.aar"
-    info_path = "libs/com_google_firebase_firebase_iid/com_google_firebase_firebase_iid.info"
-
-    # To remove visibility constraint, add this dependency to
-    # //third_party/android_deps/build.gradle.
-    visibility = [
-      ":*",
-      "//third_party/androidx:*",
-    ]
-    deps = [
-      ":google_firebase_firebase_common_java",
-      ":google_firebase_firebase_components_java",
-      ":google_firebase_firebase_iid_interop_java",
-      ":google_firebase_firebase_installations_interop_java",
-      ":google_firebase_firebase_installations_java",
-      ":google_play_services_basement_java",
-      ":google_play_services_cloud_messaging_java",
-      ":google_play_services_stats_java",
-      ":google_play_services_tasks_java",
-      "//third_party/androidx:androidx_collection_collection_java",
-      "//third_party/androidx:androidx_core_core_java",
-      "//third_party/androidx:androidx_legacy_legacy_support_core_utils_java",
-    ]
-  }
-
-  # This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
   android_aar_prebuilt("google_firebase_firebase_iid_interop_java") {
     aar_path = "libs/com_google_firebase_firebase_iid_interop/firebase-iid-interop-17.0.0.aar"
     info_path = "libs/com_google_firebase_firebase_iid_interop/com_google_firebase_firebase_iid_interop.info"
diff --git a/third_party/android_deps/build.gradle b/third_party/android_deps/build.gradle
index 02d7f9f3..3c9f8cd 100644
--- a/third_party/android_deps/build.gradle
+++ b/third_party/android_deps/build.gradle
@@ -96,6 +96,7 @@
     compile "com.android.support:multidex:1.0.0"
 
     compile "com.google.code.findbugs:jsr305:3.0.2"
+    compile "com.google.firebase:firebase-iid:21.0.1"
     compile "com.google.firebase:firebase-messaging:21.0.1"
     compile "com.google.guava:failureaccess:1.0.1"
     compile "com.google.j2objc:j2objc-annotations:1.1"
diff --git a/third_party/blink/public/mojom/webid/federated_auth_request.mojom b/third_party/blink/public/mojom/webid/federated_auth_request.mojom
index 17659a4..ffef84f 100644
--- a/third_party/blink/public/mojom/webid/federated_auth_request.mojom
+++ b/third_party/blink/public/mojom/webid/federated_auth_request.mojom
@@ -24,6 +24,12 @@
   kError,
 };
 
+enum LogoutStatus {
+  kSuccess,
+  kErrorTooManyRequests,
+  kError,
+};
+
 
 enum RequestMode {
   kMediated,
@@ -37,4 +43,7 @@
   // Requests an IdToken to be generated, given an IDP URL and an OAuth request.
   // Returns the raw content of the IdToken.
   RequestIdToken(url.mojom.Url provider, string id_request, RequestMode mode) => (RequestIdTokenStatus status, string? id_token);
+
+  // Contact the list of Relying Party logout endpoints to attempt to initiate user logout.
+  Logout(array<string> rp_logout_endpoints) => (LogoutStatus status);
 };
diff --git a/third_party/blink/public/platform/platform.h b/third_party/blink/public/platform/platform.h
index 81104a9..fb98c93 100644
--- a/third_party/blink/public/platform/platform.h
+++ b/third_party/blink/public/platform/platform.h
@@ -304,19 +304,28 @@
     return blink::UserAgentMetadata();
   }
 
-  // A suggestion to cache this metadata in association with this URL.
+  // A request to cache code generated by the Renderer for the resource fetched
+  // from the given URL at the given response time. The cached code can be
+  // fetched on subsequent loads of the resource to speed up processing. The
+  // Browser may silently ignore this request if |size| is too large or storage
+  // isn't available.
   virtual void CacheMetadata(blink::mojom::CodeCacheType cache_type,
                              const WebURL&,
                              base::Time response_time,
                              const uint8_t* data,
                              size_t data_size) {}
 
-  // A request to fetch contents associated with this URL from metadata cache.
   using FetchCachedCodeCallback =
       base::OnceCallback<void(base::Time, mojo_base::BigBuffer)>;
+  // A request to fetch previously cached code for the resource fetched from the
+  // given URL. The code fetch should be made at the same time as the resource
+  // fetch so the Renderer can bypass code generation for the resource.
   virtual void FetchCachedCode(blink::mojom::CodeCacheType cache_type,
                                const WebURL&,
                                FetchCachedCodeCallback) {}
+  // A request to clear cached code from storage. This can be called by the
+  // Renderer after fetching cached code that has become stale due to changes
+  // in the Renderer or runtime environment.
   virtual void ClearCodeCacheEntry(blink::mojom::CodeCacheType cache_type,
                                    const GURL&) {}
 
diff --git a/third_party/blink/public/web/web_ax_object.h b/third_party/blink/public/web/web_ax_object.h
index 3397057..4e3a595 100644
--- a/third_party/blink/public/web/web_ax_object.h
+++ b/third_party/blink/public/web/web_ax_object.h
@@ -145,7 +145,7 @@
   BLINK_EXPORT bool IsModal() const;
   // Returns true if this object is an input element of a text field type, such
   // as type="text" or type="tel", or a textarea.
-  BLINK_EXPORT bool IsNativeTextField() const;
+  BLINK_EXPORT bool IsAtomicTextField() const;
   BLINK_EXPORT bool IsOffScreen() const;
   BLINK_EXPORT bool IsSelectedOptionActive() const;
   BLINK_EXPORT bool IsVisited() const;
diff --git a/third_party/blink/renderer/core/css/element_rule_collector.cc b/third_party/blink/renderer/core/css/element_rule_collector.cc
index 0954e912..9225ae7d 100644
--- a/third_party/blink/renderer/core/css/element_rule_collector.cc
+++ b/third_party/blink/renderer/core/css/element_rule_collector.cc
@@ -136,19 +136,11 @@
 void ElementRuleCollector::CollectMatchingRulesForList(
     const RuleDataListType* rules,
     const MatchRequest& match_request,
+    const SelectorChecker& checker,
     PartRequest* part_request) {
   if (!rules)
     return;
 
-  SelectorChecker::Init init;
-  init.mode = mode_;
-  init.is_ua_rule = matching_ua_rules_;
-  init.element_style = style_;
-  init.scrollbar = pseudo_style_request_.scrollbar;
-  init.scrollbar_part = pseudo_style_request_.scrollbar_part;
-  init.part_names = part_request ? &part_request->part_names : nullptr;
-  init.pseudo_argument = pseudo_style_request_.pseudo_argument;
-  SelectorChecker checker(init);
   SelectorChecker::SelectorCheckingContext context(&context_.GetElement());
   context.scope = match_request.scope;
   context.pseudo_id = pseudo_style_request_.pseudo_id;
@@ -235,18 +227,21 @@
     bool matching_tree_boundary_rules) {
   DCHECK(match_request.rule_set);
 
+  SelectorChecker checker(style_, nullptr, pseudo_style_request_, mode_,
+                          matching_ua_rules_);
+
   Element& element = context_.GetElement();
   const AtomicString& pseudo_id = element.ShadowPseudoId();
   if (!pseudo_id.IsEmpty()) {
     DCHECK(element.IsStyledElement());
     CollectMatchingRulesForList(
         match_request.rule_set->UAShadowPseudoElementRules(pseudo_id),
-        match_request);
+        match_request, checker);
   }
 
   if (element.IsVTTElement()) {
     CollectMatchingRulesForList(match_request.rule_set->CuePseudoRules(),
-                                match_request);
+                                match_request, checker);
   }
   // Check whether other types of rules are applicable in the current tree
   // scope. Criteria for this:
@@ -264,50 +259,54 @@
   if (element.HasID()) {
     CollectMatchingRulesForList(
         match_request.rule_set->IdRules(element.IdForStyleResolution()),
-        match_request);
+        match_request, checker);
   }
   if (element.IsStyledElement() && element.HasClass()) {
     for (wtf_size_t i = 0; i < element.ClassNames().size(); ++i) {
       CollectMatchingRulesForList(
           match_request.rule_set->ClassRules(element.ClassNames()[i]),
-          match_request);
+          match_request, checker);
     }
   }
 
   if (element.IsLink()) {
     CollectMatchingRulesForList(match_request.rule_set->LinkPseudoClassRules(),
-                                match_request);
+                                match_request, checker);
   }
   if (inside_link_ == EInsideLink::kInsideVisitedLink) {
     CollectMatchingRulesForList(match_request.rule_set->VisitedDependentRules(),
-                                match_request);
+                                match_request, checker);
   }
   if (SelectorChecker::MatchesFocusPseudoClass(element)) {
     CollectMatchingRulesForList(match_request.rule_set->FocusPseudoClassRules(),
-                                match_request);
+                                match_request, checker);
   }
   if (SelectorChecker::MatchesFocusVisiblePseudoClass(element)) {
     CollectMatchingRulesForList(
-        match_request.rule_set->FocusVisiblePseudoClassRules(), match_request);
+        match_request.rule_set->FocusVisiblePseudoClassRules(), match_request,
+        checker);
   }
   if (SelectorChecker::MatchesSpatialNavigationInterestPseudoClass(element)) {
     CollectMatchingRulesForList(
         match_request.rule_set->SpatialNavigationInterestPseudoClassRules(),
-        match_request);
+        match_request, checker);
   }
   AtomicString element_name = matching_ua_rules_
                                   ? element.localName()
                                   : element.LocalNameForSelectorMatching();
   CollectMatchingRulesForList(match_request.rule_set->TagRules(element_name),
-                              match_request);
+                              match_request, checker);
   CollectMatchingRulesForList(match_request.rule_set->UniversalRules(),
-                              match_request);
+                              match_request, checker);
 }
 
 void ElementRuleCollector::CollectMatchingShadowHostRules(
     const MatchRequest& match_request) {
+  SelectorChecker checker(style_, nullptr, pseudo_style_request_, mode_,
+                          matching_ua_rules_);
+
   CollectMatchingRulesForList(match_request.rule_set->ShadowHostRules(),
-                              match_request);
+                              match_request, checker);
 }
 
 void ElementRuleCollector::CollectMatchingPartPseudoRules(
@@ -315,8 +314,11 @@
     PartNames& part_names,
     bool for_shadow_pseudo) {
   PartRequest request{part_names, for_shadow_pseudo};
+  SelectorChecker checker(style_, &part_names, pseudo_style_request_, mode_,
+                          matching_ua_rules_);
+
   CollectMatchingRulesForList(match_request.rule_set->PartPseudoRules(),
-                              match_request, &request);
+                              match_request, checker, &request);
 }
 
 template <class CSSRuleCollection>
diff --git a/third_party/blink/renderer/core/css/element_rule_collector.h b/third_party/blink/renderer/core/css/element_rule_collector.h
index 7371d6e..23cc4cc8 100644
--- a/third_party/blink/renderer/core/css/element_rule_collector.h
+++ b/third_party/blink/renderer/core/css/element_rule_collector.h
@@ -154,6 +154,7 @@
   template <typename RuleDataListType>
   void CollectMatchingRulesForList(const RuleDataListType*,
                                    const MatchRequest&,
+                                   const SelectorChecker&,
                                    PartRequest* = nullptr);
 
   bool Match(SelectorChecker&,
diff --git a/third_party/blink/renderer/core/css/media_query_list.cc b/third_party/blink/renderer/core/css/media_query_list.cc
index 657fc3f7..bbcc09c 100644
--- a/third_party/blink/renderer/core/css/media_query_list.cc
+++ b/third_party/blink/renderer/core/css/media_query_list.cc
@@ -102,7 +102,7 @@
 bool MediaQueryList::matches() {
   // If this is an iframe, viewport size depends on the layout of the embedding
   // document.
-  if (matcher_->GetDocument()) {
+  if (matcher_->GetDocument() && matcher_->GetDocument()->GetFrame()) {
     if (auto* owner =
             matcher_->GetDocument()->GetFrame()->OwnerLayoutObject()) {
       owner->GetDocument().UpdateStyleAndLayout(
diff --git a/third_party/blink/renderer/core/css/selector_checker.h b/third_party/blink/renderer/core/css/selector_checker.h
index 6729520..92debfba 100644
--- a/third_party/blink/renderer/core/css/selector_checker.h
+++ b/third_party/blink/renderer/core/css/selector_checker.h
@@ -31,6 +31,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_SELECTOR_CHECKER_H_
 
 #include "third_party/blink/renderer/core/css/css_selector.h"
+#include "third_party/blink/renderer/core/css/style_request.h"
 #include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/scroll/scroll_types.h"
 
@@ -73,27 +74,27 @@
     kQueryingRules,
   };
 
-  struct Init {
-    STACK_ALLOCATED();
+  explicit inline SelectorChecker(const Mode& mode)
+      : element_style_(nullptr),
+        scrollbar_(nullptr),
+        part_names_(nullptr),
+        pseudo_argument_(g_null_atom),
+        scrollbar_part_(kNoPart),
+        mode_(mode),
+        is_ua_rule_(false) {}
+  inline SelectorChecker(ComputedStyle* element_style,
+                         PartNames* part_names,
+                         const StyleRequest& style_request,
+                         const Mode& mode,
+                         const bool& is_ua_rule)
+      : element_style_(element_style),
+        scrollbar_(style_request.scrollbar),
+        part_names_(part_names),
+        pseudo_argument_(style_request.pseudo_argument),
+        scrollbar_part_(style_request.scrollbar_part),
+        mode_(mode),
+        is_ua_rule_(is_ua_rule) {}
 
-   public:
-    Mode mode = kResolvingStyle;
-    bool is_ua_rule = false;
-    ComputedStyle* element_style = nullptr;
-    CustomScrollbar* scrollbar = nullptr;
-    ScrollbarPart scrollbar_part = kNoPart;
-    PartNames* part_names = nullptr;
-    AtomicString pseudo_argument = g_null_atom;
-  };
-
-  explicit SelectorChecker(const Init& init)
-      : element_style_(init.element_style),
-        scrollbar_(init.scrollbar),
-        part_names_(init.part_names),
-        scrollbar_part_(init.scrollbar_part),
-        mode_(init.mode),
-        is_ua_rule_(init.is_ua_rule),
-        pseudo_argument_(init.pseudo_argument) {}
   SelectorChecker(const SelectorChecker&) = delete;
   SelectorChecker& operator=(const SelectorChecker&) = delete;
 
@@ -190,10 +191,10 @@
   ComputedStyle* element_style_;
   CustomScrollbar* scrollbar_;
   PartNames* part_names_;
+  const String pseudo_argument_;
   ScrollbarPart scrollbar_part_;
   Mode mode_;
   bool is_ua_rule_;
-  AtomicString pseudo_argument_;
 #if DCHECK_IS_ON()
   mutable bool inside_match_ = false;
 #endif
diff --git a/third_party/blink/renderer/core/css/selector_query.cc b/third_party/blink/renderer/core/css/selector_query.cc
index 2b29519e..52d3dd9 100644
--- a/third_party/blink/renderer/core/css/selector_query.cc
+++ b/third_party/blink/renderer/core/css/selector_query.cc
@@ -96,10 +96,8 @@
 
 inline bool SelectorMatches(const CSSSelector& selector,
                             Element& element,
-                            const ContainerNode& root_node) {
-  SelectorChecker::Init init;
-  init.mode = SelectorChecker::kQueryingRules;
-  SelectorChecker checker(init);
+                            const ContainerNode& root_node,
+                            const SelectorChecker& checker) {
   SelectorChecker::SelectorCheckingContext context(&element);
   context.selector = &selector;
   context.scope = &root_node;
@@ -146,11 +144,12 @@
     const AtomicString& class_name,
     const CSSSelector* selector,
     typename SelectorQueryTrait::OutputType& output) {
+  SelectorChecker checker(SelectorChecker::kQueryingRules);
   for (Element& element : ElementTraversal::DescendantsOf(root_node)) {
     QUERY_STATS_INCREMENT(fast_class);
     if (!element.HasClassName(class_name))
       continue;
-    if (selector && !SelectorMatches(*selector, element, root_node))
+    if (selector && !SelectorMatches(*selector, element, root_node, checker))
       continue;
     SelectorQueryTrait::AppendElement(output, element);
     if (SelectorQueryTrait::kShouldOnlyMatchFirstElement)
@@ -267,10 +266,11 @@
   DCHECK_EQ(selectors_.size(), 1u);
 
   const CSSSelector& selector = *selectors_[0];
+  SelectorChecker checker(SelectorChecker::kQueryingRules);
 
   for (Element& element : ElementTraversal::DescendantsOf(traverse_root)) {
     QUERY_STATS_INCREMENT(fast_scan);
-    if (SelectorMatches(selector, element, root_node)) {
+    if (SelectorMatches(selector, element, root_node, checker)) {
       SelectorQueryTrait::AppendElement(output, element);
       if (SelectorQueryTrait::kShouldOnlyMatchFirstElement)
         return;
@@ -280,8 +280,9 @@
 
 bool SelectorQuery::SelectorListMatches(ContainerNode& root_node,
                                         Element& element) const {
+  SelectorChecker checker(SelectorChecker::kQueryingRules);
   for (auto* const selector : selectors_) {
-    if (SelectorMatches(*selector, element, root_node))
+    if (SelectorMatches(*selector, element, root_node, checker))
       return true;
   }
   return false;
@@ -310,6 +311,7 @@
 
   const CSSSelector& first_selector = *selectors_[0];
   const TreeScope& scope = root_node.ContainingTreeScope();
+  SelectorChecker checker(SelectorChecker::kQueryingRules);
 
   if (scope.ContainsMultipleElementsWithId(selector_id_)) {
     // We don't currently handle cases where there's multiple elements with the
@@ -323,7 +325,7 @@
       if (!element->IsDescendantOf(&root_node))
         continue;
       QUERY_STATS_INCREMENT(fast_id);
-      if (SelectorMatches(first_selector, *element, root_node)) {
+      if (SelectorMatches(first_selector, *element, root_node, checker)) {
         SelectorQueryTrait::AppendElement(output, *element);
         if (SelectorQueryTrait::kShouldOnlyMatchFirstElement)
           return;
@@ -339,7 +341,7 @@
     if (!element->IsDescendantOf(&root_node))
       return;
     QUERY_STATS_INCREMENT(fast_id);
-    if (SelectorMatches(first_selector, *element, root_node))
+    if (SelectorMatches(first_selector, *element, root_node, checker))
       SelectorQueryTrait::AppendElement(output, *element);
     return;
   }
diff --git a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc
index 4e103872..89e4eb2 100644
--- a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc
@@ -2906,17 +2906,18 @@
                         grid_item.block_axis_alignment));
 
     // Grid is special in that %-based offsets resolve against the grid-area.
-    // Adjust the offset here (instead of in the builder). This is safe as grid
-    // *also* has special inflow-bounds logic (otherwise this wouldn't work).
-    LogicalOffset adjusted_offset = containing_grid_area.offset;
+    // Determine the relative offset here (instead of in the builder). This is
+    // safe as grid *also* has special inflow-bounds logic (otherwise this
+    // wouldn't work).
+    base::Optional<LogicalOffset> relative_offset = LogicalOffset();
     if (item_style.GetPosition() == EPosition::kRelative) {
-      adjusted_offset += ComputeRelativeOffsetForBoxFragment(
+      *relative_offset += ComputeRelativeOffsetForBoxFragment(
           physical_fragment, ConstraintSpace().GetWritingDirection(),
           containing_grid_area.size);
     }
 
-    container_builder_.AddResult(*result, adjusted_offset,
-                                 /* offset_includes_relative_position */ true);
+    container_builder_.AddResult(*result, containing_grid_area.offset,
+                                 relative_offset);
     NGBlockNode(grid_item.node).StoreMargins(ConstraintSpace(), margins);
 
     // Compares GridArea objects in row-major grid order for baseline
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc
index 730a8bf2..a0f6282 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc
@@ -348,6 +348,8 @@
   // An empty box fragment is still flat that we do not have to defer.
   // Also, placeholders cannot be reordred if empty.
   placeholder.rect.offset.inline_offset += box_data.margin_line_left;
+  // TODO(almaher): Handle inline relative positioning correctly for OOF
+  // fragmentation.
   placeholder.rect.offset +=
       ComputeRelativeOffsetForInline(space, *box_data.item->Style());
   LayoutUnit advance = box_data.margin_border_padding_line_left +
@@ -616,6 +618,8 @@
   for (BoxData& box_data : box_data_list_) {
     unsigned start = box_data.fragment_start;
     unsigned end = box_data.fragment_end;
+    // TODO(almaher): Handle inline relative positioning correctly for OOF
+    // fragmentation.
     const LogicalOffset relative_offset =
         ComputeRelativeOffsetForInline(space, *box_data.item->Style());
 
@@ -711,8 +715,11 @@
 
     // Propagate any OOF-positioned descendants from any atomic-inlines, etc.
     if (child.layout_result) {
+      // TODO(almaher): Handle the inline case correctly for OOF fragmentation.
+      // The relative offset should not always be set to LogicalOffset() here.
       box.PropagateChildData(child.layout_result->PhysicalFragment(),
-                             child.rect.offset - rect.offset);
+                             child.rect.offset - rect.offset,
+                             /* relative_offset */ LogicalOffset());
     }
 
     // |NGFragmentItems| has a flat list of all descendants, except
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc
index 11360790..ac9a217e 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc
@@ -760,6 +760,8 @@
     const auto* physical_fragment = child.PhysicalFragment();
     if (!physical_fragment)
       continue;
+    // TODO(almaher): Handle inline relative positioning correctly for OOF
+    // fragmentation.
     child.rect.offset += ComputeRelativeOffsetForInline(
         ConstraintSpace(), physical_fragment->Style());
   }
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc
index f808c61..7c4d0c1e 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_line_box_fragment_builder.cc
@@ -43,8 +43,11 @@
   for (unsigned index = 0; index < children.size(); ++index) {
     auto& child = children[index];
     if (child.layout_result) {
+      // TODO(almaher): Handle the inline case correctly for OOF fragmentation.
+      // The relative offset should not always be set to LogicalOffset() here.
       PropagateChildData(child.layout_result->PhysicalFragment(),
-                         child.Offset());
+                         child.Offset(),
+                         /* relative_offset */ LogicalOffset());
 
       // Skip over any children, the information should have already been
       // propagated into this layout result.
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_node.cc b/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
index f90e162..c4f4fc6e 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
@@ -177,7 +177,7 @@
   } else if (box.IsLayoutNGGrid() &&
              RuntimeEnabledFeatures::LayoutNGGridEnabled()) {
     CreateAlgorithmAndRun<NGGridLayoutAlgorithm>(params, callback);
-  } else if (box.IsLayoutImage()) {
+  } else if (box.IsLayoutReplaced()) {
     DCHECK(RuntimeEnabledFeatures::LayoutNGReplacedEnabled());
     CreateAlgorithmAndRun<NGReplacedLayoutAlgorithm>(params, callback);
   } else if (box.IsLayoutNGFieldset()) {
@@ -693,13 +693,13 @@
   const auto& physical_fragment =
       To<NGPhysicalBoxFragment>(layout_result->PhysicalFragment());
 
-  if (box_->IsLayoutImage()) {
+  if (box_->IsLayoutReplaced()) {
     DCHECK(RuntimeEnabledFeatures::LayoutNGReplacedEnabled());
     DCHECK(CanUseNewLayout());
-    // NG images are painted with legacy painters. We need to force a legacy
-    // "layout" so that paint invalidation flags are updated. But we don't want
-    // to use the size that legacy calculates, so we force legacy to use NG's
-    // size via BoxLayoutExtraInput's override fields.
+    // NG replaced elements are painted with legacy painters. We need to force
+    // a legacy "layout" so that paint invalidation flags are updated. But we
+    // don't want to use the size that legacy calculates, so we force legacy to
+    // use NG's size via BoxLayoutExtraInput's override fields.
     BoxLayoutExtraInput input(*box_);
     SetupBoxLayoutExtraInput(constraint_space, *box_, &input);
     NGBoxFragment fragment(constraint_space.GetWritingDirection(),
@@ -1023,7 +1023,7 @@
   if (box.ForceLegacyLayout())
     return false;
   return box.IsLayoutNGMixin() ||
-         (box.IsLayoutImage() && !box.IsMedia() && !box.IsListMarkerImage() &&
+         (box.IsLayoutReplaced() &&
           RuntimeEnabledFeatures::LayoutNGReplacedEnabled());
 }
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc b/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc
index ccb7102b..6d2e377 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc
@@ -163,10 +163,11 @@
   child_break_tokens_.push_back(token);
 }
 
-void NGBoxFragmentBuilder::AddResult(const NGLayoutResult& child_layout_result,
-                                     const LogicalOffset offset,
-                                     bool offset_includes_relative_position,
-                                     bool propagate_oof_descendants) {
+void NGBoxFragmentBuilder::AddResult(
+    const NGLayoutResult& child_layout_result,
+    const LogicalOffset offset,
+    base::Optional<LogicalOffset> relative_offset,
+    bool propagate_oof_descendants) {
   const auto& fragment = child_layout_result.PhysicalFragment();
   if (items_builder_) {
     if (const NGPhysicalLineBoxFragment* line =
@@ -186,8 +187,8 @@
     adjustment_for_oof_propagation = BlockOffsetAdjustmentForFragmentainer();
 
   AddChild(fragment, offset, /* inline_container */ nullptr, &end_margin_strut,
-           child_layout_result.IsSelfCollapsing(),
-           offset_includes_relative_position, adjustment_for_oof_propagation);
+           child_layout_result.IsSelfCollapsing(), relative_offset,
+           adjustment_for_oof_propagation);
   if (fragment.IsBox())
     PropagateBreak(child_layout_result);
 }
@@ -198,129 +199,132 @@
     const LayoutInline* inline_container,
     const NGMarginStrut* margin_strut,
     bool is_self_collapsing,
-    bool offset_includes_relative_position,
+    base::Optional<LogicalOffset> relative_offset,
     base::Optional<LayoutUnit> adjustment_for_oof_propagation) {
-  LogicalOffset adjusted_offset = child_offset;
+#if DCHECK_IS_ON()
+  needs_inflow_bounds_explicitly_set_ = !!relative_offset;
+  needs_may_have_descendant_above_block_start_explicitly_set_ =
+      !!relative_offset;
+#endif
 
-  if (box_type_ != NGPhysicalBoxFragment::NGBoxType::kInlineBox &&
-      !offset_includes_relative_position) {
-    if (child.IsCSSBox()) {
-      // Apply the relative position offset.
-      const auto& box_child = To<NGPhysicalBoxFragment>(child);
-      if (box_child.Style().GetPosition() == EPosition::kRelative) {
-        adjusted_offset += ComputeRelativeOffsetForBoxFragment(
-            box_child, GetWritingDirection(), child_available_size_);
-      }
-
-      // The |may_have_descendant_above_block_start_| flag is used to determine
-      // if a fragment can be re-used when preceding floats are present. This
-      // is relatively rare, and is true if:
-      //  - An inflow child is positioned above our block-start edge.
-      //  - Any inflow descendants (within the same formatting-context) which
-      //    *may* have a child positioned above our block-start edge.
-      if ((child_offset.block_offset < LayoutUnit() &&
-           !box_child.IsOutOfFlowPositioned()) ||
-          (!box_child.IsFormattingContextRoot() &&
-           box_child.MayHaveDescendantAboveBlockStart()))
-        may_have_descendant_above_block_start_ = true;
-    }
-
-    // If we are a scroll container, we need to track the maximum bounds of any
-    // inflow children (including line-boxes) to calculate the layout-overflow.
-    //
-    // This is used for determining the "padding-box" of the scroll container
-    // which is *sometimes* considered as part of the scrollable area. Inflow
-    // children contribute to this area, out-of-flow positioned children don't.
-    //
-    // Out-of-flow positioned children still contribute to the layout-overflow,
-    // but just don't influence where this padding is.
-    if (Node().IsScrollContainer() &&
-        box_type_ != NGPhysicalBoxFragment::NGBoxType::kColumnBox &&
-        !child.IsOutOfFlowPositioned()) {
-      NGBoxStrut margins;
+  if (!relative_offset) {
+    relative_offset = LogicalOffset();
+    if (box_type_ != NGPhysicalBoxFragment::NGBoxType::kInlineBox) {
       if (child.IsCSSBox()) {
-        margins =
-            ComputeMarginsFor(child.Style(), child_available_size_.inline_size,
-                              GetWritingDirection());
+        // Apply the relative position offset.
+        const auto& box_child = To<NGPhysicalBoxFragment>(child);
+        if (box_child.Style().GetPosition() == EPosition::kRelative) {
+          relative_offset = ComputeRelativeOffsetForBoxFragment(
+              box_child, GetWritingDirection(), child_available_size_);
+        }
+
+        // The |may_have_descendant_above_block_start_| flag is used to
+        // determine if a fragment can be re-used when preceding floats are
+        // present. This is relatively rare, and is true if:
+        //  - An inflow child is positioned above our block-start edge.
+        //  - Any inflow descendants (within the same formatting-context) which
+        //    *may* have a child positioned above our block-start edge.
+        if ((child_offset.block_offset < LayoutUnit() &&
+             !box_child.IsOutOfFlowPositioned()) ||
+            (!box_child.IsFormattingContextRoot() &&
+             box_child.MayHaveDescendantAboveBlockStart()))
+          may_have_descendant_above_block_start_ = true;
       }
 
-      // If we are in block-flow layout we use the end *margin-strut* as the
-      // block-end "margin" (instead of just the block-end margin).
-      if (margin_strut) {
-        NGMarginStrut end_margin_strut = *margin_strut;
-        end_margin_strut.Append(margins.block_end, /* is_quirky */ false);
-
-        // Self-collapsing blocks are special, their end margin-strut is part
-        // of their inflow position. To correctly determine the "end" margin,
-        // we need to the "final" margin-strut from their end margin-strut.
-        margins.block_end = is_self_collapsing
-                                ? end_margin_strut.Sum() - margin_strut->Sum()
-                                : end_margin_strut.Sum();
-      }
-
-      // Use the original offset (*without* relative-positioning applied).
-      NGFragment fragment(GetWritingDirection(), child);
-      LogicalRect bounds = {child_offset, fragment.Size()};
-
-      // Margins affect the inflow-bounds in interesting ways.
+      // If we are a scroll container, we need to track the maximum bounds of
+      // any inflow children (including line-boxes) to calculate the
+      // layout-overflow.
       //
-      // For the margin which is closest to the direction which we are
-      // scrolling, we allow negative margins, but only up to the size of the
-      // fragment. For the margin furthest away we disallow negative margins.
-      if (!margins.IsEmpty()) {
-        // Convert the physical overflow directions to logical.
-        const bool has_top_overflow = Node().HasTopOverflow();
-        const bool has_left_overflow = Node().HasLeftOverflow();
-        PhysicalToLogical<bool> converter(GetWritingDirection(),
-                                          has_top_overflow, !has_left_overflow,
-                                          !has_top_overflow, has_left_overflow);
-
-        if (converter.InlineStart()) {
-          margins.inline_end = margins.inline_end.ClampNegativeToZero();
-          margins.inline_start =
-              std::max(margins.inline_start, -fragment.InlineSize());
-        } else {
-          margins.inline_start = margins.inline_start.ClampNegativeToZero();
-          margins.inline_end =
-              std::max(margins.inline_end, -fragment.InlineSize());
-        }
-        if (converter.BlockStart()) {
-          margins.block_end = margins.block_end.ClampNegativeToZero();
-          margins.block_start =
-              std::max(margins.block_start, -fragment.BlockSize());
-        } else {
-          margins.block_start = margins.block_start.ClampNegativeToZero();
-          margins.block_end =
-              std::max(margins.block_end, -fragment.BlockSize());
+      // This is used for determining the "padding-box" of the scroll container
+      // which is *sometimes* considered as part of the scrollable area. Inflow
+      // children contribute to this area, out-of-flow positioned children
+      // don't.
+      //
+      // Out-of-flow positioned children still contribute to the
+      // layout-overflow, but just don't influence where this padding is.
+      if (Node().IsScrollContainer() &&
+          box_type_ != NGPhysicalBoxFragment::NGBoxType::kColumnBox &&
+          !child.IsOutOfFlowPositioned()) {
+        NGBoxStrut margins;
+        if (child.IsCSSBox()) {
+          margins = ComputeMarginsFor(child.Style(),
+                                      child_available_size_.inline_size,
+                                      GetWritingDirection());
         }
 
-        // Shift the bounds by the (potentially clamped) margins.
-        bounds.offset -= {margins.inline_start, margins.block_start};
-        bounds.size.inline_size += margins.InlineSum();
-        bounds.size.block_size += margins.BlockSum();
+        // If we are in block-flow layout we use the end *margin-strut* as the
+        // block-end "margin" (instead of just the block-end margin).
+        if (margin_strut) {
+          NGMarginStrut end_margin_strut = *margin_strut;
+          end_margin_strut.Append(margins.block_end, /* is_quirky */ false);
 
-        // Our bounds size should never go negative.
-        DCHECK_GE(bounds.size.inline_size, LayoutUnit());
-        DCHECK_GE(bounds.size.block_size, LayoutUnit());
+          // Self-collapsing blocks are special, their end margin-strut is part
+          // of their inflow position. To correctly determine the "end" margin,
+          // we need to the "final" margin-strut from their end margin-strut.
+          margins.block_end = is_self_collapsing
+                                  ? end_margin_strut.Sum() - margin_strut->Sum()
+                                  : end_margin_strut.Sum();
+        }
+
+        // Use the original offset (*without* relative-positioning applied).
+        NGFragment fragment(GetWritingDirection(), child);
+        LogicalRect bounds = {child_offset, fragment.Size()};
+
+        // Margins affect the inflow-bounds in interesting ways.
+        //
+        // For the margin which is closest to the direction which we are
+        // scrolling, we allow negative margins, but only up to the size of the
+        // fragment. For the margin furthest away we disallow negative margins.
+        if (!margins.IsEmpty()) {
+          // Convert the physical overflow directions to logical.
+          const bool has_top_overflow = Node().HasTopOverflow();
+          const bool has_left_overflow = Node().HasLeftOverflow();
+          PhysicalToLogical<bool> converter(
+              GetWritingDirection(), has_top_overflow, !has_left_overflow,
+              !has_top_overflow, has_left_overflow);
+
+          if (converter.InlineStart()) {
+            margins.inline_end = margins.inline_end.ClampNegativeToZero();
+            margins.inline_start =
+                std::max(margins.inline_start, -fragment.InlineSize());
+          } else {
+            margins.inline_start = margins.inline_start.ClampNegativeToZero();
+            margins.inline_end =
+                std::max(margins.inline_end, -fragment.InlineSize());
+          }
+          if (converter.BlockStart()) {
+            margins.block_end = margins.block_end.ClampNegativeToZero();
+            margins.block_start =
+                std::max(margins.block_start, -fragment.BlockSize());
+          } else {
+            margins.block_start = margins.block_start.ClampNegativeToZero();
+            margins.block_end =
+                std::max(margins.block_end, -fragment.BlockSize());
+          }
+
+          // Shift the bounds by the (potentially clamped) margins.
+          bounds.offset -= {margins.inline_start, margins.block_start};
+          bounds.size.inline_size += margins.InlineSum();
+          bounds.size.block_size += margins.BlockSum();
+
+          // Our bounds size should never go negative.
+          DCHECK_GE(bounds.size.inline_size, LayoutUnit());
+          DCHECK_GE(bounds.size.block_size, LayoutUnit());
+        }
+
+        // Even an empty (0x0) fragment contributes to the inflow-bounds.
+        if (!inflow_bounds_)
+          inflow_bounds_ = bounds;
+        else
+          inflow_bounds_->UniteEvenIfEmpty(bounds);
       }
-
-      // Even an empty (0x0) fragment contributes to the inflow-bounds.
-      if (!inflow_bounds_)
-        inflow_bounds_ = bounds;
-      else
-        inflow_bounds_->UniteEvenIfEmpty(bounds);
     }
   }
 
-#if DCHECK_IS_ON()
-  needs_inflow_bounds_explicitly_set_ |= offset_includes_relative_position;
-  needs_may_have_descendant_above_block_start_explicitly_set_ |=
-      offset_includes_relative_position;
-#endif
-
-  PropagateChildData(child, adjusted_offset, inline_container,
+  DCHECK(relative_offset);
+  PropagateChildData(child, child_offset, *relative_offset, inline_container,
                      adjustment_for_oof_propagation);
-  AddChildInternal(&child, adjusted_offset);
+  AddChildInternal(&child, child_offset + *relative_offset);
 }
 
 void NGBoxFragmentBuilder::AddBreakToken(const NGBreakToken* token,
diff --git a/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h b/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h
index 8a3ef01e..291cc3a 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h
@@ -197,10 +197,13 @@
 
   // Add a layout result. This involves appending the fragment and its relative
   // offset to the builder, but also keeping track of out-of-flow positioned
-  // descendants, propagating fragmentainer breaks, and more.
+  // descendants, propagating fragmentainer breaks, and more. In some cases,
+  // such as grid, the relative offset may need to be computed ahead of time.
+  // If so, a |relative_offset| will be passed in. Otherwise, the relative
+  // offset will be calculated as normal.
   void AddResult(const NGLayoutResult&,
                  const LogicalOffset,
-                 bool offset_includes_relative_position = false,
+                 base::Optional<LogicalOffset> relative_offset = base::nullopt,
                  bool propagate_oof_descendants = true);
 
   void AddChild(
@@ -209,7 +212,7 @@
       const LayoutInline* inline_container = nullptr,
       const NGMarginStrut* margin_strut = nullptr,
       bool is_self_collapsing = false,
-      bool offset_includes_relative_position = false,
+      base::Optional<LogicalOffset> relative_offset = base::nullopt,
       base::Optional<LayoutUnit> adjustment_for_oof_propagation = LayoutUnit());
 
   // Manually add a break token to the builder. Note that we're assuming that
diff --git a/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc b/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc
index 32b6ff0..885a8d1 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc
@@ -42,11 +42,14 @@
 // a child fragment or a child fragment item.
 void NGContainerFragmentBuilder::PropagateChildData(
     const NGPhysicalFragment& child,
-    const LogicalOffset& child_offset,
+    LogicalOffset child_offset,
+    LogicalOffset relative_offset,
     const LayoutInline* inline_container,
     base::Optional<LayoutUnit> adjustment_for_oof_propagation) {
   if (adjustment_for_oof_propagation) {
-    PropagateOOFPositionedInfo(child, child_offset, inline_container,
+    PropagateOOFPositionedInfo(child, child_offset, relative_offset,
+                               /* offset_adjustment */ LogicalOffset(),
+                               inline_container,
                                *adjustment_for_oof_propagation);
   }
 
@@ -299,16 +302,19 @@
 void NGContainerFragmentBuilder::PropagateOOFPositionedInfo(
     const NGPhysicalFragment& fragment,
     LogicalOffset offset,
+    LogicalOffset relative_offset,
+    LogicalOffset offset_adjustment,
     const LayoutInline* inline_container,
     LayoutUnit containing_block_adjustment,
     const NGContainingBlock<LogicalOffset>* fixedpos_containing_block,
     LogicalOffset additional_fixedpos_offset) {
+  LogicalOffset adjusted_offset = offset + relative_offset + offset_adjustment;
+
   // Collect the child's out of flow descendants.
   const WritingModeConverter converter(GetWritingDirection(), fragment.Size());
   for (const auto& descendant : fragment.OutOfFlowPositionedDescendants()) {
     NGLogicalStaticPosition static_position =
         descendant.static_position.ConvertToLogical(converter);
-    static_position.offset += offset;
 
     const LayoutInline* new_inline_container = descendant.inline_container;
     if (!new_inline_container &&
@@ -331,9 +337,7 @@
          additional_fixedpos_offset != LogicalOffset()) &&
         descendant.node.Style().GetPosition() == EPosition::kFixed) {
       static_position.offset += additional_fixedpos_offset;
-      if (fixedpos_containing_block && fixedpos_containing_block->fragment &&
-          descendant.node.Style().GetPosition() == EPosition::kFixed) {
-        static_position.offset -= offset;
+      if (fixedpos_containing_block && fixedpos_containing_block->fragment) {
         AddOutOfFlowFragmentainerDescendant(
             {descendant.node, static_position, new_inline_container,
              /* needs_block_offset_adjustment */ false,
@@ -341,6 +345,7 @@
         continue;
       }
     }
+    static_position.offset += adjusted_offset;
 
     // |oof_positioned_candidates_| should not have duplicated entries.
     DCHECK(std::none_of(
@@ -377,10 +382,15 @@
       // continue to adjust the |multicol_offset| to be relative to the current
       // |fragment|.
       LogicalOffset fixedpos_containing_block_offset;
+      LogicalOffset fixedpos_containing_block_rel_offset;
       if (fixedpos_containing_block_fragment) {
         fixedpos_containing_block_offset =
             converter.ToLogical(multicol_info->fixedpos_containing_block.offset,
                                 fixedpos_containing_block_fragment->Size());
+        fixedpos_containing_block_rel_offset = converter.ToLogical(
+            multicol_info->fixedpos_containing_block.relative_offset,
+            fixedpos_containing_block_fragment->Size());
+        fixedpos_containing_block_rel_offset += relative_offset;
         // We want the fixedpos containing block offset to be the offset from
         // the containing block to the top of the fragmentation context root,
         // such that it includes the block offset contributions of previous
@@ -388,17 +398,18 @@
         // has already been applied. As such, avoid unnecessarily adding an
         // additional inline/block offset of any fragmentainers.
         if (!box_fragment->IsFragmentainerBox())
-          fixedpos_containing_block_offset.block_offset += offset.block_offset;
+          fixedpos_containing_block_offset += offset;
         fixedpos_containing_block_offset.block_offset +=
             containing_block_adjustment;
       } else {
-        multicol_offset += offset;
+        multicol_offset += adjusted_offset;
       }
       AddMulticolWithPendingOOFs(
           NGBlockNode(multicol.key),
           MakeGarbageCollected<NGMulticolWithPendingOOFs<LogicalOffset>>(
               multicol_offset, NGContainingBlock<LogicalOffset>(
                                    fixedpos_containing_block_offset,
+                                   fixedpos_containing_block_rel_offset,
                                    fixedpos_containing_block_fragment)));
     }
   }
@@ -423,8 +434,12 @@
 
     LogicalOffset containing_block_offset = converter.ToLogical(
         descendant.containing_block.offset, containing_block_fragment->Size());
+    LogicalOffset containing_block_rel_offset =
+        converter.ToLogical(descendant.containing_block.relative_offset,
+                            containing_block_fragment->Size());
+    containing_block_rel_offset += relative_offset;
     if (!box_fragment->IsFragmentainerBox())
-      containing_block_offset.block_offset += offset.block_offset;
+      containing_block_offset += offset;
     containing_block_offset.block_offset += containing_block_adjustment;
 
     const NGPhysicalFragment* fixedpos_containing_block_fragment =
@@ -435,12 +450,17 @@
       fixedpos_containing_block_fragment = box_fragment;
 
     LogicalOffset fixedpos_containing_block_offset;
+    LogicalOffset fixedpos_containing_block_rel_offset;
     if (fixedpos_containing_block_fragment) {
       fixedpos_containing_block_offset =
           converter.ToLogical(descendant.fixedpos_containing_block.offset,
                               fixedpos_containing_block_fragment->Size());
+      fixedpos_containing_block_rel_offset = converter.ToLogical(
+          descendant.fixedpos_containing_block.relative_offset,
+          fixedpos_containing_block_fragment->Size());
+      fixedpos_containing_block_rel_offset += relative_offset;
       if (!box_fragment->IsFragmentainerBox())
-        fixedpos_containing_block_offset.block_offset += offset.block_offset;
+        fixedpos_containing_block_offset += offset;
       fixedpos_containing_block_offset.block_offset +=
           containing_block_adjustment;
     }
@@ -448,6 +468,8 @@
     if (!fixedpos_containing_block_fragment && fixedpos_containing_block) {
       fixedpos_containing_block_fragment = fixedpos_containing_block->fragment;
       fixedpos_containing_block_offset = fixedpos_containing_block->offset;
+      fixedpos_containing_block_rel_offset =
+          fixedpos_containing_block->relative_offset;
     }
 
     // The static position should remain relative to its containing block
@@ -460,8 +482,10 @@
         {descendant.node, static_position, descendant.inline_container,
          /* needs_block_offset_adjustment */ false,
          NGContainingBlock<LogicalOffset>(containing_block_offset,
+                                          containing_block_rel_offset,
                                           containing_block_fragment),
          NGContainingBlock<LogicalOffset>(fixedpos_containing_block_offset,
+                                          fixedpos_containing_block_rel_offset,
                                           fixedpos_containing_block_fragment)});
   }
 }
diff --git a/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h b/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h
index 30315f4..062c980 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h
@@ -213,6 +213,8 @@
   void PropagateOOFPositionedInfo(
       const NGPhysicalFragment& fragment,
       LogicalOffset offset,
+      LogicalOffset relative_offset,
+      LogicalOffset offset_adjustment = LogicalOffset(),
       const LayoutInline* inline_container = nullptr,
       LayoutUnit containing_block_adjustment = LayoutUnit(),
       const NGContainingBlock<LogicalOffset>* fixedpos_containing_block =
@@ -288,7 +290,8 @@
 
   void PropagateChildData(
       const NGPhysicalFragment& child,
-      const LogicalOffset& child_offset,
+      LogicalOffset child_offset,
+      LogicalOffset relative_offset,
       const LayoutInline* inline_container = nullptr,
       base::Optional<LayoutUnit> adjustment_for_oof_propagation = LayoutUnit());
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
index 3bf707a..6d9ac6c1 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
@@ -305,11 +305,10 @@
 // context root. In such cases, we cannot use default |ContainingBlockInfo|
 // since the fragmentation root is not the containing block of the positioned
 // nodes. Rather, we must generate their ContainingBlockInfo based on the
-// provided |containing_block_fragment|.
+// |candidate.containing_block.fragment|.
 const NGOutOfFlowLayoutPart::ContainingBlockInfo
 NGOutOfFlowLayoutPart::GetContainingBlockInfo(
-    const NGLogicalOutOfFlowPositionedNode& candidate,
-    const NGPhysicalFragment* containing_block_fragment) {
+    const NGLogicalOutOfFlowPositionedNode& candidate) {
   if (candidate.containing_block_rect)
     return {default_writing_direction_, *candidate.containing_block_rect};
   if (candidate.inline_container) {
@@ -317,9 +316,11 @@
     DCHECK(it != containing_blocks_map_.end());
     return it->value;
   }
-  if (containing_block_fragment) {
+  if (candidate.containing_block.fragment) {
     DCHECK(container_builder_->IsBlockFragmentationContextRoot());
 
+    const NGPhysicalFragment* containing_block_fragment =
+        candidate.containing_block.fragment;
     const LayoutObject* containing_block =
         containing_block_fragment->GetLayoutObject();
     DCHECK(containing_block);
@@ -345,7 +346,8 @@
     container_offset += candidate.containing_block.offset;
 
     ContainingBlockInfo containing_block_info{
-        writing_direction, LogicalRect(container_offset, content_size)};
+        writing_direction, LogicalRect(container_offset, content_size),
+        candidate.containing_block.relative_offset};
 
     return containing_blocks_map_
         .insert(containing_block, containing_block_info)
@@ -620,7 +622,7 @@
       multicol_container_builder.AddChild(
           *fragment, offset, /* inline_container */ nullptr,
           /* margin_strut */ nullptr, /* is_self_collapsing */ false,
-          /* offset_includes_relative_position */ false,
+          /* relative_offset */ base::nullopt,
           /* adjustment_for_oof_propagation */ base::nullopt);
       multicol_children.emplace_back(MulticolChildInfo(&child));
     }
@@ -665,14 +667,21 @@
       LogicalOffset containing_block_offset =
           converter.ToLogical(descendant.containing_block.offset,
                               containing_block_fragment->Size());
+      LogicalOffset containing_block_rel_offset =
+          converter.ToLogical(descendant.containing_block.relative_offset,
+                              containing_block_fragment->Size());
 
       const NGPhysicalFragment* fixedpos_containing_block_fragment =
           descendant.fixedpos_containing_block.fragment;
       LogicalOffset fixedpos_containing_block_offset;
+      LogicalOffset fixedpos_containing_block_rel_offset;
       if (fixedpos_containing_block_fragment) {
         fixedpos_containing_block_offset =
             converter.ToLogical(descendant.fixedpos_containing_block.offset,
                                 fixedpos_containing_block_fragment->Size());
+        fixedpos_containing_block_rel_offset = converter.ToLogical(
+            descendant.fixedpos_containing_block.relative_offset,
+            fixedpos_containing_block_fragment->Size());
       }
 
       // The static position should remain relative to its containing block
@@ -689,8 +698,10 @@
           descendant.inline_container,
           /* needs_block_offset_adjustment */ false,
           NGContainingBlock<LogicalOffset>(containing_block_offset,
+                                           containing_block_rel_offset,
                                            containing_block_fragment),
           NGContainingBlock<LogicalOffset>(fixedpos_containing_block_offset,
+                                           fixedpos_containing_block_rel_offset,
                                            fixedpos_containing_block_fragment)};
       oof_nodes_to_layout.push_back(node);
     }
@@ -830,8 +841,7 @@
          node.GetLayoutBox()->ContainingBlock()->IsTable());
 #endif
 
-  const ContainingBlockInfo container_info =
-      GetContainingBlockInfo(oof_node, containing_block_fragment);
+  const ContainingBlockInfo container_info = GetContainingBlockInfo(oof_node);
   const auto default_writing_direction =
       containing_block_fragment
           ? containing_block_fragment->Style().GetWritingDirection()
@@ -1244,15 +1254,30 @@
   const NGLayoutResult* result =
       LayoutOOFNode(descendant, /* only_layout */ nullptr, fragmentainer_space);
 
+  // Apply the relative positioned offset now that fragmentation is complete.
+  LogicalOffset oof_offset = result->OutOfFlowPositionedOffset();
+  LogicalOffset relative_offset =
+      descendant.node_info.container_info.relative_offset;
+  LogicalOffset adjusted_offset = oof_offset + relative_offset;
+
+  // In the case where an OOF descendant of |descendant| has its containing
+  // block outside the current fragmentation context, we will want to apply an
+  // additional offset to |oof_offset| in PropagateOOFPositionedInfo() such that
+  // it's the offset relative to the current builder rather than the offset such
+  // that all fragmentainers are stacked on top of each other.
+  LogicalOffset offset_adjustment = fragmentainer_offset;
+
   // If we are adding the result to the last fragmentainer rather than
   // creating a new fragmentainer to hold it, adjust the inline offset as if
   // we had created a new fragmentainer.
   if (add_to_last_fragment) {
-    LogicalOffset oof_offset = result->OutOfFlowPositionedOffset();
-    oof_offset.inline_offset += additional_inline_offset;
-    result->GetMutableForOutOfFlow().SetOutOfFlowPositionedOffset(
-        oof_offset, allow_first_tier_oof_cache_);
+    adjusted_offset.inline_offset += additional_inline_offset;
+    offset_adjustment.inline_offset += additional_inline_offset;
   }
+
+  result->GetMutableForOutOfFlow().SetOutOfFlowPositionedOffset(
+      adjusted_offset, allow_first_tier_oof_cache_);
+
   LogicalOffset additional_fixedpos_offset =
       descendant.additional_fixedpos_offset;
   if (descendant.node_info.fixedpos_containing_block.fragment) {
@@ -1286,11 +1311,9 @@
   LayoutUnit containing_block_adjustment =
       container_builder_->BlockOffsetAdjustmentForFragmentainer(
           fragmentainer_consumed_block_size_);
-  LogicalOffset oof_offset_rel_to_builder = result->OutOfFlowPositionedOffset();
-  oof_offset_rel_to_builder += fragmentainer_offset;
-
   container_builder_->PropagateOOFPositionedInfo(
-      result->PhysicalFragment(), oof_offset_rel_to_builder,
+      result->PhysicalFragment(), oof_offset, relative_offset,
+      offset_adjustment,
       /* inline_container */ nullptr, containing_block_adjustment,
       &descendant.node_info.fixedpos_containing_block,
       additional_fixedpos_offset);
@@ -1326,7 +1349,7 @@
     container_builder_->AddChild(
         new_result->PhysicalFragment(), offset, /* inline_container */ nullptr,
         /* margin_strut */ nullptr, /* is_self_collapsing */ false,
-        /* offset_includes_relative_position */ false,
+        /* relative_offset */ base::nullopt,
         /* adjustment_for_oof_propagation */ base::nullopt);
   } else {
     const NGLayoutResult* new_result = algorithm->Layout();
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h
index 7b1fa7f..845d921e 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h
@@ -27,7 +27,6 @@
 class NGBlockBreakToken;
 class NGBoxFragmentBuilder;
 class NGLayoutResult;
-class NGPhysicalFragment;
 class NGSimplifiedOOFLayoutAlgorithm;
 template <typename OffsetType>
 struct NGContainingBlock;
@@ -87,6 +86,9 @@
                                               TextDirection::kLtr};
     // Size and offset of the container.
     LogicalRect rect;
+    // The relative positioned offset to be applied after fragmentation is
+    // completed.
+    LogicalOffset relative_offset;
   };
 
   // This stores the information needed to update a multicol child inside an
@@ -198,8 +200,7 @@
       HeapHashSet<Member<const LayoutObject>>* placed_objects);
 
   const ContainingBlockInfo GetContainingBlockInfo(
-      const NGLogicalOutOfFlowPositionedNode&,
-      const NGPhysicalFragment* = nullptr);
+      const NGLogicalOutOfFlowPositionedNode&);
 
   void ComputeInlineContainingBlocks(
       const HeapVector<NGLogicalOutOfFlowPositionedNode>&);
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_node.h b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_node.h
index 6aad6de..8ac2faf6 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_node.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_node.h
@@ -26,12 +26,19 @@
 
  public:
   OffsetType offset;
+  // The relative offset is stored separately to ensure that it is applied after
+  // fragmentation: https://www.w3.org/TR/css-break-3/#transforms.
+  OffsetType relative_offset;
   Member<const NGPhysicalFragment> fragment;
 
   NGContainingBlock() : fragment(nullptr) {}
 
-  NGContainingBlock(OffsetType offset, const NGPhysicalFragment* fragment)
-      : offset(offset), fragment(std::move(fragment)) {}
+  NGContainingBlock(OffsetType offset,
+                    OffsetType relative_offset,
+                    const NGPhysicalFragment* fragment)
+      : offset(offset),
+        relative_offset(relative_offset),
+        fragment(std::move(fragment)) {}
 
   void Trace(Visitor* visitor) const { visitor->Trace(fragment); }
 };
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc
index 132bd55..0ce0cea 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc
@@ -89,6 +89,22 @@
   }
 }
 
+NGContainingBlock<PhysicalOffset> PhysicalContainingBlock(
+    NGBoxFragmentBuilder* builder,
+    PhysicalSize size,
+    const NGContainingBlock<LogicalOffset>& containing_block) {
+  return NGContainingBlock<PhysicalOffset>(
+      containing_block.offset.ConvertToPhysical(
+          builder->Style().GetWritingDirection(), size,
+          containing_block.fragment ? containing_block.fragment->Size()
+                                    : PhysicalSize()),
+      containing_block.relative_offset.ConvertToPhysical(
+          builder->Style().GetWritingDirection(), size,
+          containing_block.fragment ? containing_block.fragment->Size()
+                                    : PhysicalSize()),
+      containing_block.fragment);
+}
+
 }  // namespace
 
 // static
@@ -432,20 +448,9 @@
         descendant.node,
         descendant.static_position.ConvertToPhysical(converter),
         descendant.inline_container,
-        NGContainingBlock<PhysicalOffset>(
-            descendant.containing_block.offset.ConvertToPhysical(
-                builder->Style().GetWritingDirection(), size,
-                descendant.containing_block.fragment
-                    ? descendant.containing_block.fragment->Size()
-                    : PhysicalSize()),
-            descendant.containing_block.fragment),
-        NGContainingBlock<PhysicalOffset>(
-            descendant.fixedpos_containing_block.offset.ConvertToPhysical(
-                builder->Style().GetWritingDirection(), size,
-                descendant.fixedpos_containing_block.fragment
-                    ? descendant.fixedpos_containing_block.fragment->Size()
-                    : PhysicalSize()),
-            descendant.fixedpos_containing_block.fragment));
+        PhysicalContainingBlock(builder, size, descendant.containing_block),
+        PhysicalContainingBlock(builder, size,
+                                descendant.fixedpos_containing_block));
   }
   for (const auto& multicol : builder->multicols_with_pending_oofs_) {
     auto& value = multicol.value;
@@ -454,13 +459,8 @@
         MakeGarbageCollected<NGMulticolWithPendingOOFs<PhysicalOffset>>(
             value->multicol_offset.ConvertToPhysical(
                 builder->Style().GetWritingDirection(), size, PhysicalSize()),
-            NGContainingBlock<PhysicalOffset>(
-                value->fixedpos_containing_block.offset.ConvertToPhysical(
-                    builder->Style().GetWritingDirection(), size,
-                    value->fixedpos_containing_block.fragment
-                        ? value->fixedpos_containing_block.fragment->Size()
-                        : PhysicalSize()),
-                value->fixedpos_containing_block.fragment)));
+            PhysicalContainingBlock(builder, size,
+                                    value->fixedpos_containing_block)));
   }
   if (builder->table_grid_rect_)
     table_grid_rect = *builder->table_grid_rect_;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.cc
index b1a4df68..6127306 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_simplified_layout_algorithm.cc
@@ -287,12 +287,13 @@
       WritingModeConverter(writing_direction_,
                            previous_physical_container_size_)
           .ToLogical(old_fragment.Offset(), new_fragment.Size());
+  // Any relative offset will have already been applied, avoid re-adding one.
+  base::Optional<LogicalOffset> relative_offset = LogicalOffset();
 
   // Add the new fragment to the builder.
   container_builder_.AddChild(new_fragment, child_offset,
                               /* inline_container */ nullptr, margin_strut,
-                              is_self_collapsing,
-                              /* offset_includes_relative_position */ true);
+                              is_self_collapsing, relative_offset);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/ng_simplified_oof_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_simplified_oof_layout_algorithm.cc
index 32d6790..30ee1c3d 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_simplified_oof_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_simplified_oof_layout_algorithm.cc
@@ -66,7 +66,7 @@
 void NGSimplifiedOOFLayoutAlgorithm::AppendOutOfFlowResult(
     const NGLayoutResult* result) {
   container_builder_.AddResult(*result, result->OutOfFlowPositionedOffset(),
-                               /* offset_includes_relative_position */ false,
+                               /* relative_offset */ base::nullopt,
                                /* propagate_oof_descendants */ false);
 
   // If there is an incoming child break token, make sure that it matches
@@ -92,12 +92,14 @@
       WritingModeConverter(writing_direction_,
                            previous_physical_container_size_)
           .ToLogical(child.Offset(), fragment->Size());
+  // Any relative offset will have already been applied, avoid re-adding one.
+  base::Optional<LogicalOffset> relative_offset = LogicalOffset();
 
   // Add the fragment to the builder.
   container_builder_.AddChild(
       *fragment, child_offset, /* inline_container */ nullptr,
       /* margin_strut */ nullptr, /* is_self_collapsing */ false,
-      /* offset_includes_relative_position */ true,
+      relative_offset,
       /* adjustment_for_oof_propagation */ base::nullopt);
 }
 
diff --git a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
index ad51dd7..f01d117c 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
@@ -1638,7 +1638,7 @@
       WebAXAutofillState::kAutocompleteAvailable)
     return "list";
 
-  if (IsNativeTextField() || IsARIATextField()) {
+  if (IsAtomicTextField() || IsARIATextField()) {
     const AtomicString& aria_auto_complete =
         GetAOMPropertyOrARIAAttribute(AOMStringProperty::kAutocomplete)
             .LowerASCII();
@@ -2484,7 +2484,7 @@
     return select_element->InnerElement().innerText();
   }
 
-  if (IsNativeTextField()) {
+  if (IsAtomicTextField()) {
     // This is an "<input type=text>" or a "<textarea>": We should not simply
     // return the "value" attribute because it might be sanitized in some input
     // control types, e.g. email fields. If we do that, then "selectionStart"
@@ -2569,7 +2569,7 @@
 }
 
 String AXNodeObject::SlowGetValueForControlIncludingContentEditable() const {
-  if (IsNonNativeTextField()) {
+  if (IsNonAtomicTextField()) {
     Element* element = GetElement();
     return element ? element->GetInnerTextWithoutUpdate() : String();
   }
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index 24d4ce6..70cbd66 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -966,7 +966,7 @@
       node_data->AddState(ax::mojom::blink::State::kEditable);
       if (IsEditableRoot()) {
         node_data->AddBoolAttribute(
-            ax::mojom::blink::BoolAttribute::kEditableRoot, true);
+            ax::mojom::blink::BoolAttribute::kContentEditableRoot, true);
       }
       if (IsRichlyEditable())
         node_data->AddState(ax::mojom::blink::State::kRichlyEditable);
@@ -1143,7 +1143,7 @@
   SerializeSparseAttributes(node_data);
 
   if (Element* element = GetElement()) {
-    if (IsNativeTextField()) {
+    if (IsAtomicTextField()) {
       // Selection offsets are only used for plain text controls, (input of a
       // text field type, and textarea). Rich editable areas, such as
       // contenteditables, use AXTreeData.
@@ -1575,7 +1575,7 @@
 }
 
 bool AXObject::IsARIATextField() const {
-  if (IsNativeTextField())
+  if (IsAtomicTextField())
     return false;  // Native role supercedes the ARIA one.
   return AriaRoleAttribute() == ax::mojom::blink::Role::kTextField ||
          AriaRoleAttribute() == ax::mojom::blink::Role::kSearchBox ||
@@ -1779,15 +1779,15 @@
   return false;
 }
 
-bool AXObject::IsNativeTextField() const {
+bool AXObject::IsAtomicTextField() const {
   return blink::IsTextControl(GetNode());
 }
 
-bool AXObject::IsNonNativeTextField() const {
+bool AXObject::IsNonAtomicTextField() const {
   // Consivably, an <input type=text> or a <textarea> might also have the
   // contenteditable attribute applied. In such cases, the <input> or <textarea>
   // tags should supercede.
-  if (IsNativeTextField())
+  if (IsAtomicTextField())
     return false;
   return HasContentEditableAttributeSet() || IsARIATextField();
 }
@@ -1849,7 +1849,7 @@
 bool AXObject::IsTextField() const {
   if (IsDetached())
     return false;
-  return IsNativeTextField() || IsNonNativeTextField();
+  return IsAtomicTextField() || IsNonAtomicTextField();
 }
 
 bool AXObject::IsAutofillAvailable() const {
@@ -2456,16 +2456,16 @@
   return false;
 }
 
-const AXObject* AXObject::GetNativeTextControlAncestor(
+const AXObject* AXObject::GetAtomicTextFieldAncestor(
     int max_levels_to_check) const {
-  if (IsNativeTextField())
+  if (IsAtomicTextField())
     return this;
 
   if (max_levels_to_check == 0)
     return nullptr;
 
   if (AXObject* parent = ParentObject())
-    return parent->GetNativeTextControlAncestor(max_levels_to_check - 1);
+    return parent->GetAtomicTextFieldAncestor(max_levels_to_check - 1);
 
   return nullptr;
 }
@@ -3214,7 +3214,7 @@
                                  Vector<int>& word_ends) const {}
 
 int AXObject::TextLength() const {
-  if (IsNativeTextField())
+  if (IsAtomicTextField())
     return GetValueForControl().length();
   return 0;
 }
@@ -3531,7 +3531,7 @@
   // ax::mojom::blink::Role::kComboBoxMenuButton:
   //   <div tabindex=0 role="combobox">Select</div>
   if (role == ax::mojom::blink::Role::kComboBoxGrouping) {
-    if (IsNativeTextField())
+    if (IsAtomicTextField())
       role = ax::mojom::blink::Role::kTextFieldWithComboBox;
     else if (GetElement() && GetElement()->SupportsFocus())
       role = ax::mojom::blink::Role::kComboBoxMenuButton;
@@ -3561,7 +3561,7 @@
 
   // For the purposes of accessibility, atomic text fields  i.e. input and
   // textarea are editable because the user can potentially enter text in them.
-  if (IsNativeTextField())
+  if (IsAtomicTextField())
     return true;
 
   return false;
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.h b/third_party/blink/renderer/modules/accessibility/ax_object.h
index 989b38b..b656880 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.h
@@ -465,12 +465,12 @@
 
   // Returns true if this object is an input element of a text field type, such
   // as type="text" or type="tel", or a textarea.
-  bool IsNativeTextField() const;
+  bool IsAtomicTextField() const;
 
   // Returns true if this object is not an <input> or a <textarea>, and is
-  // either a contenteditable, or has role=textbox role=searchbox or
-  // (on certain platforms) role=combobox.
-  bool IsNonNativeTextField() const;
+  // either a contenteditable, or has the CSS user-modify style set to something
+  // editable.
+  bool IsNonAtomicTextField() const;
 
   // Returns true if this object is a text field that is used for entering
   // passwords, i.e. <input type=password>.
@@ -561,8 +561,7 @@
   bool IsBlockedByAriaModalDialog(IgnoredReasons* = nullptr) const;
   bool IsDescendantOfDisabledNode() const;
   bool ComputeAccessibilityIsIgnoredButIncludedInTree() const;
-  const AXObject* GetNativeTextControlAncestor(
-      int max_levels_to_check = 3) const;
+  const AXObject* GetAtomicTextFieldAncestor(int max_levels_to_check = 3) const;
   const AXObject* DatetimeAncestor(int max_levels_to_check = 3) const;
   const AXObject* DisabledAncestor() const;
   bool LastKnownIsIgnoredValue() const;
@@ -735,13 +734,13 @@
   virtual void GetWordBoundaries(Vector<int>& word_starts,
                                  Vector<int>& word_ends) const;
 
-  // For all inline text fields and native text fields: Returns the length of
-  // the inline's text or the field's value respectively.
+  // For all inline text boxes and atomic text fields: Returns the length of the
+  // inline's text or the field's value respectively.
   virtual int TextLength() const;
 
   // Supported on layout inline, layout text, layout replaced, and layout block
   // flow, provided that they are at inline-level, i.e. "display=inline" or
-  // "display=inline-block". Also supported on native text fields. For all other
+  // "display=inline-block". Also supported on atomic text fields. For all other
   // object types, returns |offset|.
   //
   // For layout inline, text, replaced, and block flow: Translates the given
@@ -756,12 +755,14 @@
   // in the DOM, from the start of the layout inline's deepest block flow
   // ancestor, e.g. the beginning of the paragraph in which the span is found.
   //
-  // For native text fields: Simply returns |offset|, because native text fields
+  // For atomic text fields: Simply returns |offset|, because atomic text fields
   // have no collapsed white space and so no translation from a DOM to an
-  // accessible text offset is necessary.
+  // accessible text offset is necessary. An atomic text field does not expose
+  // its internal implementation to assistive software, appearing as a single
+  // leaf node in the accessibility tree. It includes <input> and <textarea>.
   virtual int TextOffsetInFormattingContext(int offset) const;
 
-  // For all inline text boxes and native text fields. For all other object
+  // For all inline text boxes and atomic text fields. For all other object
   // types, returns |offset|.
   //
   // For inline text boxes: Translates the given character offset to the
@@ -773,7 +774,7 @@
   // characters, excluding any collapsed white space found in the DOM, from the
   // start of the inline text box's static text parent.
   //
-  // For native text fields: Simply returns |offset|, because native text fields
+  // For atomic text fields: Simply returns |offset|, because atomic text fields
   // have no collapsed white space and so no translation from a DOM to an
   // accessible text offset is necessary.
   virtual int TextOffsetInContainer(int offset) const;
diff --git a/third_party/blink/renderer/modules/accessibility/ax_position.cc b/third_party/blink/renderer/modules/accessibility/ax_position.cc
index a21a4ee..6f89a2c 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_position.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_position.cc
@@ -85,7 +85,7 @@
   if (container.IsDetached())
     return {};
 
-  if (container.IsTextObject() || container.IsNativeTextField()) {
+  if (container.IsTextObject() || container.IsAtomicTextField()) {
     AXPosition position(container);
     position.text_offset_or_child_index_ = 0;
 #if DCHECK_IS_ON()
@@ -120,7 +120,7 @@
   if (container.IsDetached())
     return {};
 
-  if (container.IsTextObject() || container.IsNativeTextField()) {
+  if (container.IsTextObject() || container.IsAtomicTextField()) {
     AXPosition position(container);
     position.text_offset_or_child_index_ = position.MaxTextOffset();
 #if DCHECK_IS_ON()
@@ -420,7 +420,7 @@
 
   // TODO(nektar): Make AXObject::TextLength() public and use throughout this
   // method.
-  if (container_object_->IsNativeTextField())
+  if (container_object_->IsAtomicTextField())
     return container_object_->GetValueForControl().length();
 
   const Node* container_node = container_object_->GetNode();
@@ -552,7 +552,7 @@
   if (!container_object_)
     return false;
   return container_object_->IsTextObject() ||
-         container_object_->IsNativeTextField();
+         container_object_->IsAtomicTextField();
 }
 
 const AXPosition AXPosition::CreateNextPosition() const {
@@ -614,7 +614,7 @@
       const AXObject* last_child =
           container_object_->LastChildIncludingIgnored();
       // Dont skip over any intervening text.
-      if (last_child->IsTextObject() || last_child->IsNativeTextField()) {
+      if (last_child->IsTextObject() || last_child->IsAtomicTextField()) {
         return CreatePositionAfterObject(
             *last_child, AXPositionAdjustmentBehavior::kMoveLeft);
       }
@@ -636,7 +636,7 @@
 
   // Dont skip over any intervening text.
   if (object_before_position->IsTextObject() ||
-      object_before_position->IsNativeTextField()) {
+      object_before_position->IsAtomicTextField()) {
     return CreatePositionAfterObject(*object_before_position,
                                      AXPositionAdjustmentBehavior::kMoveLeft);
   }
diff --git a/third_party/blink/renderer/modules/accessibility/ax_relation_cache.cc b/third_party/blink/renderer/modules/accessibility/ax_relation_cache.cc
index ec63424..0b760c7 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_relation_cache.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_relation_cache.cc
@@ -125,7 +125,7 @@
   // An aria-owns is disallowed on editable roots and atomic text fields, such
   // as <input>, <textarea> and content editables, otherwise the result would be
   // unworkable and totally unexpected on the browser side.
-  if (owner->IsNativeTextField() || owner->IsEditableRoot())
+  if (owner->IsTextField())
     return false;
 
   // Images can only use <img usemap> to "own" <area> children.
diff --git a/third_party/blink/renderer/modules/accessibility/ax_selection.cc b/third_party/blink/renderer/modules/accessibility/ax_selection.cc
index 614423b..595e6cd 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_selection.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_selection.cc
@@ -264,18 +264,18 @@
   // boundaries, replaced elements, CSS user-select, etc.
   //
 
-  if (base_.IsTextPosition() && base_.ContainerObject()->IsNativeTextField() &&
+  if (base_.IsTextPosition() && base_.ContainerObject()->IsAtomicTextField() &&
       !(base_.ContainerObject() == extent_.ContainerObject() &&
         extent_.IsTextPosition() &&
-        extent_.ContainerObject()->IsNativeTextField())) {
+        extent_.ContainerObject()->IsAtomicTextField())) {
     return false;
   }
 
   if (extent_.IsTextPosition() &&
-      extent_.ContainerObject()->IsNativeTextField() &&
+      extent_.ContainerObject()->IsAtomicTextField() &&
       !(base_.ContainerObject() == extent_.ContainerObject() &&
         base_.IsTextPosition() &&
-        base_.ContainerObject()->IsNativeTextField())) {
+        base_.ContainerObject()->IsAtomicTextField())) {
     return false;
   }
 
@@ -358,7 +358,7 @@
   if (text_control_selection.has_value()) {
     DCHECK_LE(text_control_selection->start, text_control_selection->end);
     TextControlElement& text_control = ToTextControl(
-        *base_.ContainerObject()->GetNativeTextControlAncestor()->GetNode());
+        *base_.ContainerObject()->GetAtomicTextFieldAncestor()->GetNode());
     if (!text_control.SetSelectionRange(text_control_selection->start,
                                         text_control_selection->end,
                                         text_control_selection->direction)) {
@@ -435,7 +435,7 @@
   }
 
   const AXObject* text_control =
-      base_.ContainerObject()->GetNativeTextControlAncestor();
+      base_.ContainerObject()->GetAtomicTextFieldAncestor();
   if (!text_control)
     return {};
 
diff --git a/third_party/blink/renderer/modules/exported/web_ax_object.cc b/third_party/blink/renderer/modules/exported/web_ax_object.cc
index cf7a27b..c53e86eb 100644
--- a/third_party/blink/renderer/modules/exported/web_ax_object.cc
+++ b/third_party/blink/renderer/modules/exported/web_ax_object.cc
@@ -316,11 +316,11 @@
   return private_->IsModal();
 }
 
-bool WebAXObject::IsNativeTextField() const {
+bool WebAXObject::IsAtomicTextField() const {
   if (IsDetached())
     return false;
 
-  return private_->IsNativeTextField();
+  return private_->IsAtomicTextField();
 }
 
 bool WebAXObject::IsOffScreen() const {
@@ -695,7 +695,7 @@
     return;
 
   const auto ax_selection =
-      focus.private_->IsNativeTextField()
+      focus.private_->IsAtomicTextField()
           ? AXSelection::FromCurrentSelection(
                 ToTextControl(*focus.private_->GetNode()))
           : AXSelection::FromCurrentSelection(*focus.private_->GetDocument());
@@ -743,7 +743,7 @@
                                   ax::mojom::blink::Action::kSetSelection);
   AXPosition ax_base, ax_extent;
   if (static_cast<const AXObject*>(anchor_object)->IsTextObject() ||
-      static_cast<const AXObject*>(anchor_object)->IsNativeTextField()) {
+      static_cast<const AXObject*>(anchor_object)->IsAtomicTextField()) {
     ax_base =
         AXPosition::CreatePositionInTextObject(*anchor_object, anchor_offset);
   } else if (anchor_offset <= 0) {
@@ -757,7 +757,7 @@
   }
 
   if (static_cast<const AXObject*>(focus_object)->IsTextObject() ||
-      static_cast<const AXObject*>(focus_object)->IsNativeTextField()) {
+      static_cast<const AXObject*>(focus_object)->IsAtomicTextField()) {
     ax_extent =
         AXPosition::CreatePositionInTextObject(*focus_object, focus_offset);
   } else if (focus_offset <= 0) {
diff --git a/third_party/blink/renderer/modules/webcodecs/audio_frame.idl b/third_party/blink/renderer/modules/webcodecs/audio_frame.idl
index b832e18c..397bf7a 100644
--- a/third_party/blink/renderer/modules/webcodecs/audio_frame.idl
+++ b/third_party/blink/renderer/modules/webcodecs/audio_frame.idl
@@ -10,7 +10,7 @@
   [RaisesException] constructor(AudioFrameInit init);
 
   // Creates a AudioFrame, which needs to be independently closed.
-  [RaisesException] VideoFrame clone();
+  [RaisesException] AudioFrame clone();
 
   void close();
 
diff --git a/third_party/blink/renderer/modules/webid/web_id.cc b/third_party/blink/renderer/modules/webid/web_id.cc
index 7f87c58..2ef8db4c 100644
--- a/third_party/blink/renderer/modules/webid/web_id.cc
+++ b/third_party/blink/renderer/modules/webid/web_id.cc
@@ -18,6 +18,7 @@
 
 namespace {
 
+using mojom::blink::LogoutStatus;
 using mojom::blink::ProvideIdTokenStatus;
 using mojom::blink::RequestIdTokenStatus;
 using mojom::blink::RequestMode;
@@ -99,6 +100,19 @@
   }
 }
 
+void OnLogout(ScriptPromiseResolver* resolver, LogoutStatus status) {
+  // TODO(kenrb); There should be more thought put into how this API works.
+  // Returning success or failure doesn't have a lot of meaning. If some
+  // logout attempts fail and others succeed, and even different attempts
+  // fail for different reasons, how does that get conveyed to the caller?
+  if (status != LogoutStatus::kSuccess) {
+    resolver->Reject(MakeGarbageCollected<DOMException>(
+        DOMExceptionCode::kNetworkError, "Error logging out endpoints."));
+    return;
+  }
+  resolver->Resolve();
+}
+
 void OnProvideIdToken(ScriptPromiseResolver* resolver,
                       ProvideIdTokenStatus status) {
   // TODO(kenrb): Provide better messages for different error codes.
@@ -168,9 +182,18 @@
 
 ScriptPromise WebId::logout(ScriptState* script_state,
                             const Vector<String>& logout_endpoints) {
+  if (logout_endpoints.IsEmpty()) {
+    return ScriptPromise();
+  }
+
+  BindRemote(auth_request_);
+
   auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
   ScriptPromise promise = resolver->Promise();
-  resolver->Resolve();
+
+  auth_request_->Logout(logout_endpoints,
+                        WTF::Bind(&OnLogout, WrapPersistent(resolver)));
+
   return promise;
 }
 
diff --git a/third_party/blink/renderer/platform/heap/impl/heap.cc b/third_party/blink/renderer/platform/heap/impl/heap.cc
index 8f15913..577bb37c 100644
--- a/third_party/blink/renderer/platform/heap/impl/heap.cc
+++ b/third_party/blink/renderer/platform/heap/impl/heap.cc
@@ -125,7 +125,8 @@
 
   if (BasePage* page = LookupPageForAddress(address)) {
 #if DCHECK_IS_ON()
-    DCHECK(page->Contains(address));
+    DCHECK(page->Contains(address))
+        << "address " << address << " not part of page " << page;
 #endif
     DCHECK(page_bloom_filter_->MayContain(address));
     DCHECK(&visitor->Heap() == &page->Arena()->GetThreadState()->Heap());
diff --git a/third_party/blink/web_tests/FlagExpectations/composite-after-paint b/third_party/blink/web_tests/FlagExpectations/composite-after-paint
index 1cfc0e4..f6e335b 100644
--- a/third_party/blink/web_tests/FlagExpectations/composite-after-paint
+++ b/third_party/blink/web_tests/FlagExpectations/composite-after-paint
@@ -69,9 +69,11 @@
 virtual/layout_ng_block_frag/external/wpt/css/css-break/out-of-flow-in-multicolumn-039.html [ Pass ]
 virtual/layout_ng_block_frag/external/wpt/css/css-break/out-of-flow-in-multicolumn-040.html [ Pass ]
 virtual/layout_ng_block_frag/external/wpt/css/css-break/out-of-flow-in-multicolumn-041.html [ Pass ]
+virtual/layout_ng_block_frag/external/wpt/css/css-break/out-of-flow-in-multicolumn-044.html [ Pass ]
 crbug.com/829028 external/wpt/css/css-break/out-of-flow-in-multicolumn-029.html [ Crash Failure ]
 crbug.com/829028 external/wpt/css/css-break/out-of-flow-in-multicolumn-036.html [ Crash Failure ]
 crbug.com/829028 external/wpt/css/css-break/out-of-flow-in-multicolumn-038.html [ Crash Failure ]
 crbug.com/829028 external/wpt/css/css-break/out-of-flow-in-multicolumn-039.html [ Crash Failure ]
 crbug.com/829028 external/wpt/css/css-break/out-of-flow-in-multicolumn-040.html [ Crash Failure ]
 crbug.com/829028 external/wpt/css/css-break/out-of-flow-in-multicolumn-041.html [ Crash Failure ]
+crbug.com/829028 external/wpt/css/css-break/out-of-flow-in-multicolumn-044.html [ Crash Failure ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index c31aede..ce22197d 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1019,6 +1019,10 @@
 virtual/layout_ng_block_frag/external/wpt/css/css-break/out-of-flow-in-multicolumn-039.html [ Pass ]
 virtual/layout_ng_block_frag/external/wpt/css/css-break/out-of-flow-in-multicolumn-040.html [ Pass ]
 virtual/layout_ng_block_frag/external/wpt/css/css-break/out-of-flow-in-multicolumn-041.html [ Pass ]
+virtual/layout_ng_block_frag/external/wpt/css/css-break/out-of-flow-in-multicolumn-042.html [ Pass ]
+virtual/layout_ng_block_frag/external/wpt/css/css-break/out-of-flow-in-multicolumn-043.html [ Pass ]
+virtual/layout_ng_block_frag/external/wpt/css/css-break/out-of-flow-in-multicolumn-044.html [ Pass ]
+virtual/layout_ng_block_frag/external/wpt/css/css-break/out-of-flow-in-multicolumn-045.html [ Pass ]
 virtual/layout_ng_block_frag/external/wpt/css/css-break/overflow-clip-000.html [ Pass ]
 virtual/layout_ng_block_frag/external/wpt/css/css-break/overflow-clip-001.html [ Pass ]
 virtual/layout_ng_block_frag/external/wpt/css/css-break/overflowed-block-with-no-room-after-000.html [ Pass ]
@@ -1080,7 +1084,6 @@
 virtual/layout_ng_block_frag/fast/multicol/vertical-rl/nested-columns.html [ Pass ]
 
 ### Tests failing with LayoutNGBlockFragmentation enabled:
-crbug.com/1113911 virtual/layout_ng_block_frag/external/wpt/css/css-break/fieldset-001.html [ Failure ]
 crbug.com/1146973 virtual/layout_ng_block_frag/external/wpt/css/css-break/out-of-flow-in-multicolumn-009.html [ Failure ]
 crbug.com/1079031 virtual/layout_ng_block_frag/external/wpt/css/css-multicol/change-out-of-flow-type-and-remove-inner-multicol-crash.html [ Crash Failure ]
 crbug.com/996655 virtual/layout_ng_block_frag/external/wpt/css/css-multicol/going-out-of-flow-after-spanner.html [ Crash Failure Pass ]
@@ -1165,7 +1168,6 @@
 crbug.com/958381 [ Mac ] external/wpt/css/CSS2/tables/table-anonymous-objects-092.xht [ Failure ]
 crbug.com/958381 [ Mac ] virtual/text-antialias/hyphen-min-preferred-width.html [ Failure ]
 crbug.com/958381 [ Win ] virtual/text-antialias/hyphen-min-preferred-width.html [ Failure ]
-crbug.com/958381 [ Mac ] virtual/layout_ng_block_frag/fragmentation/fragmented-table-cell.html [ Failure ]
 crbug.com/958381 [ Mac ] fragmentation/single-line-cells-paginated-with-text.html [ Failure ]
 
 # TablesNG ends
@@ -3925,6 +3927,10 @@
 crbug.com/829028 external/wpt/css/css-break/out-of-flow-in-multicolumn-039.html [ Failure ]
 crbug.com/829028 external/wpt/css/css-break/out-of-flow-in-multicolumn-040.html [ Failure ]
 crbug.com/829028 external/wpt/css/css-break/out-of-flow-in-multicolumn-041.html [ Failure ]
+crbug.com/829028 external/wpt/css/css-break/out-of-flow-in-multicolumn-042.html [ Failure ]
+crbug.com/829028 external/wpt/css/css-break/out-of-flow-in-multicolumn-043.html [ Failure ]
+crbug.com/829028 external/wpt/css/css-break/out-of-flow-in-multicolumn-044.html [ Failure ]
+crbug.com/829028 external/wpt/css/css-break/out-of-flow-in-multicolumn-045.html [ Failure ]
 crbug.com/829028 external/wpt/css/css-break/overflow-clip-000.html [ Failure ]
 crbug.com/829028 external/wpt/css/css-break/overflow-clip-001.html [ Failure ]
 crbug.com/829028 external/wpt/css/css-break/overflowed-block-with-no-room-after-001.html [ Failure ]
diff --git a/third_party/blink/web_tests/WebGPUExpectations b/third_party/blink/web_tests/WebGPUExpectations
index 4e31478..bd4400f 100644
--- a/third_party/blink/web_tests/WebGPUExpectations
+++ b/third_party/blink/web_tests/WebGPUExpectations
@@ -123,11 +123,13 @@
 # These tests aren't working on CQ, unclear whether the test or harness (or Chrome) is broken.
 # Mac: mostly works
 # Linux: actual is white/blank - is actually crashing silently
-crbug.com/1083478 [ Linux ] wpt_internal/webgpu/webgpu/web_platform/reftests/canvas_clear.html [ Skip ]
-crbug.com/1083478 [ Linux ] wpt_internal/webgpu/webgpu/web_platform/reftests/canvas_complex_bgra8unorm.html [ Skip ]
+crbug.com/1083478 [ Linux ] wpt_internal/webgpu/web_platform/reftests/canvas_clear.html [ Skip ]
+crbug.com/1083478 [ Linux ] wpt_internal/webgpu/web_platform/reftests/canvas_complex_bgra8unorm.html [ Skip ]
 # Win: takeScreenshot crashes
-crbug.com/1083478 [ Win ] wpt_internal/webgpu/webgpu/web_platform/reftests/canvas_clear.html [ Skip ]
-crbug.com/1083478 [ Win ] wpt_internal/webgpu/webgpu/web_platform/reftests/canvas_complex_bgra8unorm.html [ Skip ]
+crbug.com/1083478 [ Win ] wpt_internal/webgpu/web_platform/reftests/canvas_clear.html [ Skip ]
+crbug.com/1083478 [ Win ] wpt_internal/webgpu/web_platform/reftests/canvas_complex_bgra8unorm.html [ Skip ]
+# Mac: Shifted by about half a pixel
+crbug.com/1083478 [ Mac ] wpt_internal/webgpu/web_platform/reftests/canvas_complex_bgra8unorm.html [ Failure ]
 
 # Spec was changed so BGLs should eagerly apply per-pipeline limits. Tests need fixing, then Dawn
 # needs to pass them. https://github.com/gpuweb/cts/issues/230
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-042.html b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-042.html
new file mode 100644
index 0000000..4539201
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-042.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>
+  Relative insets apply to abspos in a multicol after fragmentation.
+</title>
+<link rel="help" href="https://www.w3.org/TR/css-break-3/#transforms">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<style>
+  .multicol {
+    column-count: 2;
+    column-fill: auto;
+    column-gap: 0px;
+    height: 100px;
+    width: 100px;
+    margin-left: -150px;
+    margin-top: -150px;
+  }
+  .rel {
+    position: relative;
+    height: 200px;
+  }
+  .abs {
+    position: absolute;
+    width: 100%;
+    height: 200px;
+    background: green;
+  }
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div class="multicol">
+  <div class="rel" style="top: 50px; left: 50px;">
+    <div class="rel" style="top: 100px; left: 100px; background: red;">
+      <div class="abs"></div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-043.html b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-043.html
new file mode 100644
index 0000000..bdda35d0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-043.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<title>
+  Relative insets apply to nested abspos in a multicol after fragmentation.
+</title>
+<link rel="help" href="https://www.w3.org/TR/css-break-3/#transforms">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<style>
+  .multicol {
+    column-count: 2;
+    column-fill: auto;
+    column-gap: 0px;
+    height: 100px;
+    width: 100px;
+    margin-left: -150px;
+    margin-top: -150px;
+  }
+  .rel {
+    position: relative;
+    height: 200px;
+  }
+  .abs {
+    position: absolute;
+    width: 100%;
+    height: 200px;
+  }
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div class="multicol">
+  <div class="rel" style="top: 50px; left: 50px;">
+    <div class="rel" style="top: 100px; left: 100px; background: red;">
+      <div class="abs">
+        <div class="abs" style="background: green;"></div>
+      </div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-044.html b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-044.html
new file mode 100644
index 0000000..4c2b9c9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-044.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<title>
+  Relative insets apply to nested fixedpos in a multicol after fragmentation.
+</title>
+<link rel="help" href="https://www.w3.org/TR/css-break-3/#transforms">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<style>
+  .multicol {
+    column-count: 2;
+    column-fill: auto;
+    column-gap: 0px;
+    height: 100px;
+    width: 100px;
+    margin-left: -50px;
+    margin-top: -50px;
+  }
+  .rel {
+    position: relative;
+  }
+  .abs {
+    position: absolute;
+  }
+  .fixed {
+    position: fixed;
+    width: 100%;
+    height: 200px;
+    background: green;
+  }
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div class="multicol">
+  <div class="rel" style="top: 50px; left: 50px;">
+    <div style="transform: translateX(0); height: 200px; background: red;">
+      <div class="rel" style="top: 100px; left: 100px;">
+        <div class="abs">
+          <div class="abs">
+            <div class="fixed" style="background: green;"></div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-045.html b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-045.html
new file mode 100644
index 0000000..c4cda49
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-045.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>
+  Ancestor inline offsets apply to abspos in a multicol.
+</title>
+<link rel="help" href="https://www.w3.org/TR/css-break-3/#transforms">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<style>
+  .multicol {
+    column-count: 2;
+    column-fill: auto;
+    column-gap: 0px;
+    height: 100px;
+    width: 100px;
+    margin-left: -110px;
+    margin-top: -100px;
+  }
+  .rel {
+    position: relative;
+    height: 200px;
+  }
+  .abs {
+    position: absolute;
+    width: 50px;
+    height: 200px;
+    background: green;
+  }
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div class="multicol">
+  <div style="margin-left: 10px;">
+    <div class="rel" style="top: 100px; left: 100px; background: red;">
+      <div class="abs"></div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/fast/media/media-query-list-detached-document.html b/third_party/blink/web_tests/fast/media/media-query-list-detached-document.html
new file mode 100644
index 0000000..3953609
--- /dev/null
+++ b/third_party/blink/web_tests/fast/media/media-query-list-detached-document.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+
+<iframe></iframe>
+
+<script>
+  async_test(t => {
+    addEventListener("load", () => {
+      let iframe_element = document.querySelector("iframe");
+      let iframe_window = iframe_element.contentWindow;
+      iframe_element.remove();
+      let mql = iframe_window.matchMedia("(max-width:100px)");
+      requestAnimationFrame(() => { setTimeout(() => {
+        mql.matches;
+        t.done();
+      }); });
+    });
+  }, "window.matchMedia does not crash on a detached frame");
+</script>
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/web_platform/reftests/canvas_clear.html b/third_party/blink/web_tests/wpt_internal/webgpu/web_platform/reftests/canvas_clear.html
new file mode 100644
index 0000000..83a7535b
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/web_platform/reftests/canvas_clear.html
@@ -0,0 +1,11 @@
+<html class="reftest-wait">
+  <base href="/gen/third_party/webgpu-cts/src/webgpu/web_platform/reftests/" />
+  <title>WebGPU canvas_clear</title>
+  <meta charset="utf-8" />
+  <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+  <meta name="assert" content="WebGPU cleared canvas should be presented correctly" />
+  <link rel="match" href="./ref/canvas_clear-ref.html" />
+  <canvas id="gpucanvas" width="10" height="10"></canvas>
+  <script src="/common/reftest-wait.js"></script>
+  <script type="module" src="canvas_clear.js"></script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/web_platform/reftests/canvas_complex_bgra8unorm.html b/third_party/blink/web_tests/wpt_internal/webgpu/web_platform/reftests/canvas_complex_bgra8unorm.html
new file mode 100644
index 0000000..0d3b163
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/web_platform/reftests/canvas_complex_bgra8unorm.html
@@ -0,0 +1,19 @@
+<html class="reftest-wait">
+  <base href="/gen/third_party/webgpu-cts/src/webgpu/web_platform/reftests/" />
+  <title>WebGPU canvas_complex_bgra8unorm</title>
+  <meta charset="utf-8" />
+  <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+  <meta
+    name="assert"
+    content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
+  />
+  <link rel="match" href="./ref/canvas_complex-ref.html" />
+
+  <canvas id="cvs" width="2" height="2" style="width: 20px; height: 20px;"></canvas>
+  <script src="/common/reftest-wait.js"></script>
+  <script type="module">
+    import { run } from './canvas_complex.js';
+    run('bgra8unorm');
+    // TODO: make a copy of this test for rgba16float
+  </script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/web_platform/reftests/ref/canvas_clear-ref.html b/third_party/blink/web_tests/wpt_internal/webgpu/web_platform/reftests/ref/canvas_clear-ref.html
new file mode 100644
index 0000000..a3b8706
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/web_platform/reftests/ref/canvas_clear-ref.html
@@ -0,0 +1,13 @@
+<html>
+  <base href="/gen/third_party/webgpu-cts/src/webgpu/web_platform/reftests/ref/" />
+  <title>WebGPU canvas_clear (ref)</title>
+  <meta charset="utf-8" />
+  <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+  <canvas id="myCanvas" width="10" height="10"></canvas>
+  <script>
+    var c = document.getElementById('myCanvas');
+    var ctx = c.getContext('2d');
+    ctx.fillStyle = '#00FF00';
+    ctx.fillRect(0, 0, c.width, c.height);
+  </script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/web_platform/reftests/ref/canvas_complex-ref.html b/third_party/blink/web_tests/wpt_internal/webgpu/web_platform/reftests/ref/canvas_complex-ref.html
new file mode 100644
index 0000000..15b1687
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/web_platform/reftests/ref/canvas_complex-ref.html
@@ -0,0 +1,18 @@
+<html>
+  <base href="/gen/third_party/webgpu-cts/src/webgpu/web_platform/reftests/ref/" />
+  <title>WebGPU canvas_complex (ref)</title>
+  <meta charset="utf-8" />
+  <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+  <canvas id="cvs" width="2" height="2" style="width: 20px; height: 20px;"></canvas>
+  <script>
+    const ctx = cvs.getContext('2d');
+    ctx.fillStyle = '#7F0000';
+    ctx.fillRect(0, 0, 1, 1);
+    ctx.fillStyle = '#007F00';
+    ctx.fillRect(1, 0, 1, 1);
+    ctx.fillStyle = '#00007F';
+    ctx.fillRect(0, 1, 1, 1);
+    ctx.fillStyle = '#7F7F00';
+    ctx.fillRect(1, 1, 1, 1);
+  </script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/webid/resources/webid-mock.js b/third_party/blink/web_tests/wpt_internal/webid/resources/webid-mock.js
index 22a624f..a60b771 100644
--- a/third_party/blink/web_tests/wpt_internal/webid/resources/webid-mock.js
+++ b/third_party/blink/web_tests/wpt_internal/webid/resources/webid-mock.js
@@ -1,4 +1,4 @@
-import { RequestMode, RequestIdTokenStatus, FederatedAuthRequest, FederatedAuthRequestReceiver } from '/gen/third_party/blink/public/mojom/webid/federated_auth_request.mojom.m.js';
+import { RequestMode, RequestIdTokenStatus, LogoutStatus, FederatedAuthRequest, FederatedAuthRequestReceiver } from '/gen/third_party/blink/public/mojom/webid/federated_auth_request.mojom.m.js';
 
 function toMojoIdTokenStatus(status) {
   switch(status) {
@@ -28,6 +28,7 @@
     this.interceptor_.start();
     this.idToken_ = null;
     this.status_ = RequestIdTokenStatus.kError;
+    this.logoutStatus_ = LogoutStatus.kError;
   }
 
   // Causes the subsequent `navigator.id.get()` to resolve with the token.
@@ -53,9 +54,16 @@
     });
   }
 
+  async logout(logout_endpoints) {
+    return Promise.resolve({
+      status: this.logoutStatus_
+    });
+  }
+
   async reset() {
     this.idToken_ = null;
     this.status_ = RequestIdTokenStatus.kError;
+    this.logoutStatus_ = LogoutStatus.kError;
     this.receiver_.$.close();
     this.interceptor_.stop();
 
diff --git a/third_party/closure_compiler/externs/automation.js b/third_party/closure_compiler/externs/automation.js
index 3494446..b94a996 100644
--- a/third_party/closure_compiler/externs/automation.js
+++ b/third_party/closure_compiler/externs/automation.js
@@ -273,7 +273,6 @@
   MENU_LIST_POPUP: 'menuListPopup',
   METER: 'meter',
   NAVIGATION: 'navigation',
-  NONE: 'none',
   NOTE: 'note',
   PANE: 'pane',
   PARAGRAPH: 'paragraph',
@@ -432,11 +431,11 @@
  */
 chrome.automation.DescriptionFromType = {
   ARIA_DESCRIPTION: 'ariaDescription',
-  BUTTON_LABEL: 'button-label',
+  BUTTON_LABEL: 'buttonLabel',
   RELATED_ELEMENT: 'relatedElement',
   RUBY_ANNOTATION: 'rubyAnnotation',
-  SUMMARY: 'summary',  // HTML-AAM 5.8.2
-  TABLE_CAPTION: 'tableCaption',  // HTML-AAM 5.9.2
+  SUMMARY: 'summary',
+  TABLE_CAPTION: 'tableCaption',
   TITLE: 'title',
 };
 
@@ -2022,11 +2021,12 @@
 chrome.automation.AutomationNode.prototype.fontFamily;
 
 /**
- * Indicates whether this is a root of an editable subtree.
+ * Indicates whether the object is at the root of a content editable region, or
+ * at a <body> element that has "design-mode" set to "on".
  * @type {boolean}
- * @see https://developer.chrome.com/extensions/automation#type-editableRoot
+ * @see https://developer.chrome.com/extensions/automation#type-contentEditableRoot
  */
-chrome.automation.AutomationNode.prototype.editableRoot;
+chrome.automation.AutomationNode.prototype.contentEditableRoot;
 
 /**
  * Indicates aria-current state.
diff --git a/third_party/webgpu-cts/scripts/regenerate_internal_cts_html.py b/third_party/webgpu-cts/scripts/regenerate_internal_cts_html.py
index cb6778c..d3309b253 100755
--- a/third_party/webgpu-cts/scripts/regenerate_internal_cts_html.py
+++ b/third_party/webgpu-cts/scripts/regenerate_internal_cts_html.py
@@ -5,6 +5,7 @@
 
 import argparse
 import ast
+import errno
 import tempfile
 import os
 import shutil
@@ -17,10 +18,23 @@
 from compile_src import compile_src_for_node
 
 
-def generate_internal_cts_html():
+def check_or_write_file(filepath, content, check):
+    if check:
+        with open(filepath, 'rb') as dst:
+            if dst.read() != content:
+                raise RuntimeError(
+                    '%s is out of date. Please re-run //third_party/webgpu-cts/scripts/run_regenerate_internal_cts_html.py\n'
+                    % filepath)
+    else:
+        with open(filepath, 'wb') as dst:
+            dst.write(content)
+
+
+def generate_internal_cts_html(check):
     split_list_fd, split_list_fname = tempfile.mkstemp()
     cts_html_fd, cts_html_fname = tempfile.mkstemp()
     js_out_dir = tempfile.mkdtemp()
+    contents = None
     try:
         print('WebGPU CTS: Extracting expectation names...')
         old_sys_path = sys.path
@@ -62,7 +76,7 @@
         finally:
             sys.path = old_sys_path
 
-        print('WebGPU CTS: Generating cts.html contents...')
+        print('WebGPU CTS: Generating cts.html...')
         cmd = [
             os.path.join(js_out_dir,
                          'common/tools/gen_wpt_cts_html.js'), cts_html_fname,
@@ -75,13 +89,76 @@
         print(RunNode(cmd))
 
         with open(cts_html_fname, 'rb') as f:
-            return f.read()
+            contents = f.read()
 
     finally:
         os.close(split_list_fd)
         os.close(cts_html_fd)
         shutil.rmtree(js_out_dir)
 
+    out_cts_html = os.path.join(third_party_dir, 'blink', 'web_tests',
+                                'wpt_internal', 'webgpu', 'cts.html')
+
+    if not contents:
+        raise RuntimeError('Failed to generate %s' % out_cts_html)
+
+    check_or_write_file(out_cts_html, contents, check=check)
+
+
+def generate_reftest_html(check):
+    # Update this to add/remove subdirectories to check.
+    sub_dirs = ['web_platform']
+
+    for sub_dir in sub_dirs:
+        html_search_dir = os.path.join(third_party_dir, 'webgpu-cts', 'src',
+                                       'src', 'webgpu', sub_dir)
+        wpt_internal_dir = os.path.join(third_party_dir, 'blink', 'web_tests',
+                                        'wpt_internal', 'webgpu', sub_dir)
+
+        if not check:
+            shutil.rmtree(wpt_internal_dir)
+        print('WebGPU CTS: Generating HTML tests from %s...' % html_search_dir)
+        for root, dirnames, filenames in os.walk(html_search_dir):
+            for filename in filenames:
+                if filename.endswith('.html'):
+                    filepath = os.path.join(root, filename)
+                    relative_dir = os.path.relpath(os.path.dirname(filepath),
+                                                   html_search_dir)
+                    dst_dir = os.path.join(wpt_internal_dir, relative_dir)
+                    gen_base_dir = os.path.join(
+                        '/gen/third_party/webgpu-cts/src/webgpu', sub_dir,
+                        relative_dir)
+                    gen_base_dir = gen_base_dir.replace('\\', '/') + '/'
+
+                    try:
+                        os.makedirs(dst_dir)
+                    except OSError as e:
+                        if e.errno == errno.EEXIST and os.path.isdir(dst_dir):
+                            pass
+                        else:
+                            raise
+
+                    with open(filepath, 'rb') as src:
+                        src_content = src.read()
+
+                    # Find the starting html tag
+                    i = src_content.find('<html')
+                    assert i != -1
+
+                    # Then find the end of the starting html tag
+                    i = src_content.find('>', i)
+                    assert i != -1
+
+                    # Bump the index just past the starting <html> tag
+                    i = i + 1
+
+                    base_tag = b'\n  <base href="%s" />' % gen_base_dir
+                    dst_content = src_content[:i] + base_tag + src_content[i:]
+
+                    check_or_write_file(os.path.join(dst_dir, filename),
+                                        dst_content,
+                                        check=check)
+
 
 if __name__ == '__main__':
     parser = argparse.ArgumentParser()
@@ -91,22 +168,8 @@
     parser.add_argument('--stamp', help='Stamp file to write after success.')
     args = parser.parse_args()
 
-    out_cts_html = os.path.join(third_party_dir, 'blink', 'web_tests',
-                                'wpt_internal', 'webgpu', 'cts.html')
-    contents = generate_internal_cts_html()
-    if not contents:
-        raise RuntimeError('Failed to generate %s' % out_cts_html)
-
-    if args.check:
-        with open(out_cts_html, 'rb') as f:
-            if f.read() != contents:
-                print(contents)
-                raise RuntimeError(
-                    '%s is out of date. Please re-run //third_party/webgpu-cts/scripts/regenerate_internal_cts_html.py\n'
-                    % out_cts_html)
-    else:
-        with open(out_cts_html, 'wb') as f:
-            f.write(contents)
+    generate_internal_cts_html(check=args.check)
+    generate_reftest_html(check=args.check)
 
     if args.stamp:
         with open(args.stamp, 'w') as f:
diff --git a/tools/metrics/histograms/histograms_xml/METRIC_REVIEWER_OWNERS b/tools/metrics/histograms/histograms_xml/METRIC_REVIEWER_OWNERS
index fd1f62b..7040c39a 100644
--- a/tools/metrics/histograms/histograms_xml/METRIC_REVIEWER_OWNERS
+++ b/tools/metrics/histograms/histograms_xml/METRIC_REVIEWER_OWNERS
@@ -7,11 +7,13 @@
 cthomp@chromium.org
 curranmax@chromium.org
 dschinazi@chromium.org
+ender@chromium.org
 harrisonsean@chromium.org
 javierrobles@chromium.org
 mcrouse@chromium.org
 mlippautz@chromium.org
 nancylingwang@chromium.org
+nohle@chromium.org
 sebmarchand@chromium.org
 mthiesse@chromium.org
 rayankans@chromium.org
diff --git a/tools/metrics/histograms/histograms_xml/accessibility/histograms.xml b/tools/metrics/histograms/histograms_xml/accessibility/histograms.xml
index c3719cc..5ff7730409 100644
--- a/tools/metrics/histograms/histograms_xml/accessibility/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/accessibility/histograms.xml
@@ -188,7 +188,7 @@
 </histogram>
 
 <histogram name="Accessibility.CrosAlwaysShowA11yMenu" enum="BooleanEnabled"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>dmazzoni@chromium.org</owner>
   <owner>kenjibaheux@google.com</owner>
   <owner>chrome-a11y-core@google.com</owner>
@@ -211,7 +211,7 @@
 </histogram>
 
 <histogram name="Accessibility.CrosAutoclick.TrayMenu.ChangeAction"
-    enum="AutoclickActionType" expires_after="2021-08-19">
+    enum="AutoclickActionType" expires_after="2021-10-25">
   <owner>katie@chromium.org</owner>
   <owner>dtseng@chromium.org</owner>
   <owner>dmazzoni@chromium.org</owner>
@@ -245,7 +245,7 @@
 </histogram>
 
 <histogram name="Accessibility.CrosCursorColor" enum="BooleanEnabled"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>dmazzoni@chromium.org</owner>
   <owner>katie@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
@@ -266,7 +266,7 @@
 </histogram>
 
 <histogram name="Accessibility.CrosDictation" enum="BooleanEnabled"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>anastasi@google.com</owner>
   <owner>dtseng@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
@@ -438,7 +438,7 @@
 </histogram>
 
 <histogram name="Accessibility.CrosSelectToSpeak.StartSpeechMethod"
-    enum="CrosSelectToSpeakStartSpeechMethod" expires_after="2021-08-22">
+    enum="CrosSelectToSpeakStartSpeechMethod" expires_after="2021-10-25">
   <owner>katie@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
@@ -449,7 +449,7 @@
 </histogram>
 
 <histogram name="Accessibility.CrosSelectToSpeak.StateChangeEvent"
-    enum="CrosSelectToSpeakStateChangeEvent" expires_after="2021-08-22">
+    enum="CrosSelectToSpeakStateChangeEvent" expires_after="2021-10-25">
   <owner>katie@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
@@ -477,7 +477,7 @@
 
 <histogram
     name="Accessibility.CrosShelfNavigationButtonsInTabletModeChanged.OsSettings"
-    enum="BooleanEnabled" expires_after="2021-08-22">
+    enum="BooleanEnabled" expires_after="2021-10-25">
   <owner>gzadina@google.com</owner>
   <owner>tbarzic@chromium.org</owner>
   <summary>
@@ -515,7 +515,7 @@
 </histogram>
 
 <histogram name="Accessibility.CrosSwitchAccess" enum="BooleanEnabled"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>dmazzoni@chromium.org</owner>
   <owner>anastasi@google.com</owner>
   <owner>chrome-a11y-core@google.com</owner>
diff --git a/tools/metrics/histograms/histograms_xml/android/OWNERS b/tools/metrics/histograms/histograms_xml/android/OWNERS
index a533074..2ce3d39 100644
--- a/tools/metrics/histograms/histograms_xml/android/OWNERS
+++ b/tools/metrics/histograms/histograms_xml/android/OWNERS
@@ -2,4 +2,5 @@
 
 # Prefer sending CLs to the owners listed below.
 # Use chromium-metrics-reviews@google.com as a backup.
+ender@chromium.org
 mthiesse@chromium.org
diff --git a/tools/metrics/histograms/histograms_xml/android/histograms.xml b/tools/metrics/histograms/histograms_xml/android/histograms.xml
index abebca8..ce2e4c30 100644
--- a/tools/metrics/histograms/histograms_xml/android/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/android/histograms.xml
@@ -125,7 +125,7 @@
 
 <histogram name="Android.AutofillAssistant.FeatureModuleInstallation"
     enum="AutofillAssistantFeatureModuleInstallation"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>mcarlen@chromium.org</owner>
   <owner>autofill_assistant+watch@google.com</owner>
   <summary>
@@ -570,7 +570,7 @@
 </histogram>
 
 <histogram name="Android.DarkTheme.DarkSearchRequested" enum="Boolean"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>wylieb@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <owner>clank-app-team@google.com</owner>
@@ -583,7 +583,7 @@
 </histogram>
 
 <histogram name="Android.DarkTheme.EnabledReason" enum="DarkThemeEnabledReason"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>twellington@chromium.org</owner>
   <owner>clank-app-team@google.com</owner>
   <summary>
@@ -593,7 +593,7 @@
 </histogram>
 
 <histogram name="Android.DarkTheme.EnabledState" enum="BooleanEnabled"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>twellington@chromium.org</owner>
   <owner>clank-app-team@google.com</owner>
   <summary>
@@ -603,7 +603,7 @@
 </histogram>
 
 <histogram name="Android.DarkTheme.Preference.State"
-    enum="DarkThemePreferences" expires_after="2021-08-22">
+    enum="DarkThemePreferences" expires_after="2021-10-25">
   <owner>twellington@chromium.org</owner>
   <owner>clank-app-team@google.com</owner>
   <summary>
@@ -1844,7 +1844,7 @@
 </histogram>
 
 <histogram name="Android.PackageStats.CacheSize" units="MB"
-    expires_after="2021-08-25">
+    expires_after="2021-10-25">
   <owner>nyquist@chromium.org</owner>
   <owner>yfriedman@chromium.org</owner>
   <summary>
@@ -1853,7 +1853,7 @@
 </histogram>
 
 <histogram name="Android.PackageStats.CodeSize" units="MB"
-    expires_after="2021-08-25">
+    expires_after="2021-10-25">
   <owner>nyquist@chromium.org</owner>
   <owner>yfriedman@chromium.org</owner>
   <summary>
@@ -1864,7 +1864,7 @@
 </histogram>
 
 <histogram name="Android.PackageStats.DataSize" units="MB"
-    expires_after="2021-08-25">
+    expires_after="2021-10-25">
   <owner>nyquist@chromium.org</owner>
   <owner>yfriedman@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/apps/histograms.xml b/tools/metrics/histograms/histograms_xml/apps/histograms.xml
index 0f8d4296..e226a09 100644
--- a/tools/metrics/histograms/histograms_xml/apps/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/apps/histograms.xml
@@ -577,7 +577,7 @@
 </histogram>
 
 <histogram name="Apps.AppList.SuggestedContent.Enabled" enum="BooleanEnabled"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>wrong@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <owner>thanhdng@chromium.org</owner>
@@ -999,7 +999,7 @@
 </histogram>
 
 <histogram name="Apps.AppListFolderOpened" enum="AppListFolderOpened"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>mmourgos@chromium.org</owner>
   <owner>newcomer@chromium.org</owner>
   <summary>
@@ -1029,7 +1029,7 @@
 </histogram>
 
 <histogram name="Apps.AppListLauncherClickedSearchQueryLength"
-    units="characters" expires_after="2021-08-22">
+    units="characters" expires_after="2021-10-25">
   <owner>wrong@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -1060,7 +1060,7 @@
 </histogram>
 
 <histogram name="Apps.AppListPageSwitcherSource"
-    enum="AppListPageSwitcherSource" expires_after="2021-08-22">
+    enum="AppListPageSwitcherSource" expires_after="2021-10-25">
 <!-- Name completed by histogram_suffixes name="TabletOrClamshellMode" -->
 
   <owner>newcomer@chromium.org</owner>
@@ -1545,7 +1545,7 @@
 </histogram>
 
 <histogram name="Apps.LauncherSearchQueryLengthJumped" enum="Boolean"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>wrong@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/arc/histograms.xml b/tools/metrics/histograms/histograms_xml/arc/histograms.xml
index feb83ba3..e4e9f12a 100644
--- a/tools/metrics/histograms/histograms_xml/arc/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/arc/histograms.xml
@@ -190,7 +190,7 @@
 </histogram>
 
 <histogram base="true" name="Arc.Auth.AccountCheck.Status"
-    enum="ArcAuthAccountCheckStatus" expires_after="2021-08-19">
+    enum="ArcAuthAccountCheckStatus" expires_after="2021-10-25">
 <!-- Name completed by histogram_suffixes name="ArcUserTypes" -->
 
   <owner>mhasank@google.com</owner>
@@ -708,7 +708,7 @@
 </histogram>
 
 <histogram name="Arc.OptInResult" enum="ArcOptInResult"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>elijahtaylor@google.com</owner>
   <owner>khmel@google.com</owner>
   <summary>Arc OptIn flow result.</summary>
@@ -1172,7 +1172,7 @@
 </histogram>
 
 <histogram name="Arc.StateByUserType" enum="ArcEnableState"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
 <!-- Name completed by histogram_suffixes name="ArcUserTypes" -->
 
   <owner>elijahtaylor@google.com</owner>
diff --git a/tools/metrics/histograms/histograms_xml/ash/histograms.xml b/tools/metrics/histograms/histograms_xml/ash/histograms.xml
index 101ae84..9224abb3 100644
--- a/tools/metrics/histograms/histograms_xml/ash/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/ash/histograms.xml
@@ -2028,7 +2028,7 @@
 </histogram>
 
 <histogram name="Ash.Shelf.NumberOfItems" units="Icons"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>anasalazar@google.com</owner>
   <owner>mmourgos@google.com</owner>
   <summary>
@@ -2313,7 +2313,7 @@
 </histogram>
 
 <histogram base="true" name="Ash.SplitViewResize.PresentationTime" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
 <!-- Name completed by histogram_suffixes name="SplitViewResizeModes" -->
 
   <owner>amusbach@chromium.org</owner>
@@ -2457,7 +2457,7 @@
 </histogram>
 
 <histogram name="Ash.TabletMode.AnimationSmoothness.Exit" units="%"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>oshima@chromium.org</owner>
   <owner>sammiequon@chromium.org</owner>
   <summary>
@@ -2539,7 +2539,7 @@
 </histogram>
 
 <histogram name="Ash.TouchView.TouchViewInactiveTotal" units="minutes"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>girard@chromium.org</owner>
   <summary>
     The total time that TouchView is not active during a session.
diff --git a/tools/metrics/histograms/histograms_xml/assistant/histograms.xml b/tools/metrics/histograms/histograms_xml/assistant/histograms.xml
index 238dea2f..0c33ae5 100644
--- a/tools/metrics/histograms/histograms_xml/assistant/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/assistant/histograms.xml
@@ -152,7 +152,7 @@
 </histogram>
 
 <histogram name="Assistant.QueryCountPerEntryPoint" enum="AssistantEntryPoint"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>xiaohuic@chromium.org</owner>
   <owner>meilinw@chromium.org</owner>
   <summary>Number of queries fired for each entry point.</summary>
@@ -342,7 +342,7 @@
 </histogram>
 
 <histogram name="QuickAnswers.ContextMenu.Close" enum="BooleanClicked"
-    expires_after="2021-10-17">
+    expires_after="2021-10-25">
   <owner>updowndota@google.com</owner>
   <owner>croissant-eng@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/autofill/histograms.xml b/tools/metrics/histograms/histograms_xml/autofill/histograms.xml
index 7d070f1..c77623fcf 100644
--- a/tools/metrics/histograms/histograms_xml/autofill/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/autofill/histograms.xml
@@ -1191,7 +1191,7 @@
 
 <histogram
     name="Autofill.NumberOfAutofilledFieldsAtSubmission.{AcceptanceStatus}"
-    units="fields" expires_after="M92">
+    units="fields" expires_after="M96">
   <owner>koerber@google.com</owner>
   <owner>chrome-autofill-alerts@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/background/histograms.xml b/tools/metrics/histograms/histograms_xml/background/histograms.xml
index 3ea797b..db4a925 100644
--- a/tools/metrics/histograms/histograms_xml/background/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/background/histograms.xml
@@ -82,7 +82,7 @@
 </histogram>
 
 <histogram name="BackgroundFetch.HasRequestsWithBody" enum="Boolean"
-    expires_after="2021-06-30">
+    expires_after="2021-10-25">
   <owner>nator@chromium.org</owner>
   <owner>rayankans@chromium.org</owner>
   <owner>peter@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/blink/histograms.xml b/tools/metrics/histograms/histograms_xml/blink/histograms.xml
index 1fdc438..bd6511b 100644
--- a/tools/metrics/histograms/histograms_xml/blink/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/blink/histograms.xml
@@ -1563,7 +1563,7 @@
 </histogram>
 
 <histogram name="Blink.Layout.NGRatio.Blocks" units="%"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>tkent@chromium.org</owner>
   <owner>layout-dev@chromium.org</owner>
   <summary>
@@ -1578,7 +1578,7 @@
 </histogram>
 
 <histogram name="Blink.Layout.NGRatio.Calls" units="%"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>tkent@chromium.org</owner>
   <owner>layout-dev@chromium.org</owner>
   <summary>
@@ -1975,7 +1975,7 @@
 </histogram>
 
 <histogram name="Blink.MemoryCache.RevalidationPolicy.AsyncScript"
-    enum="RevalidationPolicy" expires_after="2021-08-22">
+    enum="RevalidationPolicy" expires_after="2021-10-25">
   <owner>hiroshige@chromium.org</owner>
   <owner>lizeb@chromium.org</owner>
   <owner>loading-dev@chromium.org</owner>
@@ -2014,7 +2014,7 @@
 </histogram>
 
 <histogram name="Blink.MemoryCache.RevalidationPolicy.PerTopFrameSite"
-    enum="RevalidationPolicy" expires_after="2021-08-22">
+    enum="RevalidationPolicy" expires_after="2021-10-25">
   <owner>shivanisha@chromium.org</owner>
   <owner>privacy-sandbox-dev@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/browser/histograms.xml b/tools/metrics/histograms/histograms_xml/browser/histograms.xml
index 3d44bac..4b312b4 100644
--- a/tools/metrics/histograms/histograms_xml/browser/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/browser/histograms.xml
@@ -598,7 +598,7 @@
 </histogram>
 
 <histogram name="BrowserRenderProcessHost.ChildKills.OOM" enum="RendererType"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>oshima@chromium.org</owner>
   <summary>
     Out of BrowserRenderProcessHost.ChildKills, numer of kills due to SIGKILL,
diff --git a/tools/metrics/histograms/histograms_xml/chromeos/histograms.xml b/tools/metrics/histograms/histograms_xml/chromeos/histograms.xml
index d5d7c38..792d0c8 100644
--- a/tools/metrics/histograms/histograms_xml/chromeos/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/chromeos/histograms.xml
@@ -41,7 +41,7 @@
 </variants>
 
 <histogram name="ChromeOS.Apps.ExternalProtocolDialog"
-    enum="ArcIntentHandlerAction" expires_after="2021-08-22">
+    enum="ArcIntentHandlerAction" expires_after="2021-10-25">
   <owner>dominickn@chromium.org</owner>
   <owner>melzhang@chromium.org</owner>
   <owner>mxcai@chromium.org</owner>
@@ -591,7 +591,7 @@
 </histogram>
 
 <histogram base="true" name="ChromeOS.HardwareVerifier.Report"
-    enum="HardwareVerifierQualificationStatus" expires_after="2021-08-22">
+    enum="HardwareVerifierQualificationStatus" expires_after="2021-10-25">
 <!-- Name completed by histogram_suffixes name="HardwareVerifierSupportCategory" -->
 
   <owner>itspeter@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/cloud/histograms.xml b/tools/metrics/histograms/histograms_xml/cloud/histograms.xml
index 0147c9d..c404f849 100644
--- a/tools/metrics/histograms/histograms_xml/cloud/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/cloud/histograms.xml
@@ -22,7 +22,7 @@
 <histograms>
 
 <histogram name="CloudPrint.AuthEvent" enum="CloudPrintAuthEventType"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>thestig@chromium.org</owner>
   <summary>Event counts in CloudPrintAuth.</summary>
 </histogram>
@@ -94,7 +94,7 @@
 </histogram>
 
 <histogram name="CloudPrint.ServiceEvents" enum="ServiceProcessEventType"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>thestig@chromium.org</owner>
   <summary>Event counts in ServiceProcessControl.</summary>
 </histogram>
diff --git a/tools/metrics/histograms/histograms_xml/compositing/histograms.xml b/tools/metrics/histograms/histograms_xml/compositing/histograms.xml
index fa76160..acf03bb 100644
--- a/tools/metrics/histograms/histograms_xml/compositing/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/compositing/histograms.xml
@@ -388,7 +388,7 @@
 </histogram>
 
 <histogram name="Compositing.Display.VizScheduledDrawToGpuStartedDrawUs"
-    units="microseconds" expires_after="2021-08-22">
+    units="microseconds" expires_after="2021-10-25">
   <owner>vasilyt@chromium.org</owner>
   <owner>backer@chromium.org</owner>
   <summary>
@@ -488,7 +488,7 @@
 </histogram>
 
 <histogram name="Compositing.Renderer.LayerUpdateSkippedDueToBackface"
-    units="boolean" expires_after="2021-08-22">
+    units="boolean" expires_after="2021-10-25">
   <owner>chrishtr@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/content/histograms.xml b/tools/metrics/histograms/histograms_xml/content/histograms.xml
index f58355c..58361265 100644
--- a/tools/metrics/histograms/histograms_xml/content/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/content/histograms.xml
@@ -191,7 +191,7 @@
 </histogram>
 
 <histogram name="ContentSettings.DefaultCookiesSetting" enum="ContentSetting"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>engedy@chromium.org</owner>
   <owner>src/components/permissions/PERMISSIONS_OWNERS</owner>
   <summary>The default cookies setting at profile open.</summary>
@@ -214,14 +214,14 @@
 </histogram>
 
 <histogram name="ContentSettings.DefaultJavaScriptSetting"
-    enum="ContentSetting" expires_after="2021-08-22">
+    enum="ContentSetting" expires_after="2021-10-25">
   <owner>engedy@chromium.org</owner>
   <owner>src/components/permissions/PERMISSIONS_OWNERS</owner>
   <summary>The default JavaScript setting at profile open.</summary>
 </histogram>
 
 <histogram name="ContentSettings.DefaultLocationSetting" enum="ContentSetting"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>engedy@chromium.org</owner>
   <owner>src/components/permissions/PERMISSIONS_OWNERS</owner>
   <summary>The default location setting at profile open.</summary>
@@ -734,7 +734,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.DisplayStatusOnOpen"
-    enum="ContentSuggestionsDisplayStatus" expires_after="2021-08-22">
+    enum="ContentSuggestionsDisplayStatus" expires_after="2021-10-25">
   <owner>carlosk@chromium.org</owner>
   <owner>harringtond@chromium.org</owner>
   <owner>feed@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/cookie/histograms.xml b/tools/metrics/histograms/histograms_xml/cookie/histograms.xml
index 75370d2..0a40984 100644
--- a/tools/metrics/histograms/histograms_xml/cookie/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/cookie/histograms.xml
@@ -78,7 +78,7 @@
 </histogram>
 
 <histogram name="Cookie.CookieSchemeRequestScheme" enum="CookieRequestScheme"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>bingler@chromium.org</owner>
   <owner>chlily@chromium.org</owner>
   <summary>
@@ -190,7 +190,7 @@
 </histogram>
 
 <histogram name="Cookie.IncludedRequestEffectiveSameSite"
-    enum="CookieEffectiveSameSite" expires_after="2021-08-22">
+    enum="CookieEffectiveSameSite" expires_after="2021-10-25">
   <owner>bingler@chromium.org</owner>
   <owner>chlily@chromium.org</owner>
   <summary>
@@ -200,7 +200,7 @@
 </histogram>
 
 <histogram name="Cookie.IncludedResponseEffectiveSameSite"
-    enum="CookieEffectiveSameSite" expires_after="2021-08-22">
+    enum="CookieEffectiveSameSite" expires_after="2021-10-25">
   <owner>bingler@chromium.org</owner>
   <owner>chlily@chromium.org</owner>
   <summary>
@@ -427,7 +427,7 @@
 </histogram>
 
 <histogram name="Cookie.RequestSameSiteContext" enum="SameSiteCookieContext"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>chlily@chromium.org</owner>
   <owner>morlovich@chromium.org</owner>
   <summary>
@@ -611,7 +611,7 @@
 </histogram>
 
 <histogram name="Cookie.SameSiteNoneIsSecure" enum="Boolean"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>chlily@chromium.org</owner>
   <owner>morlovich@chromium.org</owner>
   <summary>
@@ -731,7 +731,7 @@
   <summary>The amount of time (ms) to migrate a v8 database to v9.</summary>
 </histogram>
 
-<histogram name="Cookie.TimeInitializeDB" units="ms" expires_after="2021-08-22">
+<histogram name="Cookie.TimeInitializeDB" units="ms" expires_after="2021-10-25">
   <owner>chlily@chromium.org</owner>
   <owner>src/net/cookies/OWNERS</owner>
   <summary>The amount of time (ms) to initialize the cookies database.</summary>
diff --git a/tools/metrics/histograms/histograms_xml/cross_device/OWNERS b/tools/metrics/histograms/histograms_xml/cross_device/OWNERS
new file mode 100644
index 0000000..edffd6c2
--- /dev/null
+++ b/tools/metrics/histograms/histograms_xml/cross_device/OWNERS
@@ -0,0 +1,5 @@
+per-file OWNERS=file://tools/metrics/histograms/histograms_xml/METRIC_REVIEWER_OWNERS
+
+# Prefer sending CLs to the owners listed below.
+# Use chromium-metrics-reviews@google.com as a backup.
+nohle@chromium.org
diff --git a/tools/metrics/histograms/histograms_xml/cross_device/histograms.xml b/tools/metrics/histograms/histograms_xml/cross_device/histograms.xml
index 8ab9575a..f15d9c9 100644
--- a/tools/metrics/histograms/histograms_xml/cross_device/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/cross_device/histograms.xml
@@ -1049,6 +1049,42 @@
   </summary>
 </histogram>
 
+<histogram name="CryptAuth.GetLocalDeviceMetadata.HasId" enum="BooleanPresent"
+    expires_after="2022-02-02">
+  <owner>nohle@chromium.org</owner>
+  <owner>better-together-dev@google.com</owner>
+  <summary>
+    Records whether or not the loaded local-device has a non-trivial identifier.
+    We always expect a non-trivial ID. Emitted every time the DeviceSync client
+    tries to load local device data, typically during initialization, after an
+    Enrollment or after a DeviceSync.
+  </summary>
+</histogram>
+
+<histogram name="CryptAuth.GetLocalDeviceMetadata.IsReady" enum="BooleanReady"
+    expires_after="2022-02-02">
+  <owner>nohle@chromium.org</owner>
+  <owner>better-together-dev@google.com</owner>
+  <summary>
+    Records whether or not the DeviceSync client is fully set up/ready when
+    GetLocalDeviceMetadata() is invoked. We always expect the client to be ready
+    when GetLocalDeviceMetadata() is called. Emitted every time the DeviceSync
+    client tries to load local device data, typically during initialization,
+    after an Enrollment or after a DeviceSync.
+  </summary>
+</histogram>
+
+<histogram name="CryptAuth.GetLocalDeviceMetadata.Result" enum="BooleanSuccess"
+    expires_after="2022-02-02">
+  <owner>nohle@chromium.org</owner>
+  <owner>better-together-dev@google.com</owner>
+  <summary>
+    Records whether or not the local-device-data request to the DeviceSync
+    client was successful. We expect this to always be successful. Emitted every
+    time applications, e.g., Smart Lock, request the local device data.
+  </summary>
+</histogram>
+
 <histogram name="CryptAuth.InstanceId.DidInstanceIdChange"
     enum="BooleanChanged" expires_after="2021-10-06">
   <owner>nohle@chromium.org</owner>
@@ -1664,7 +1700,7 @@
 
 <histogram
     name="MultiDevice.SecureChannel.BLE.Performance.ConnectionToAuthenticationDuration.Background"
-    units="ms" expires_after="2021-08-22">
+    units="ms" expires_after="2021-10-25">
   <owner>vecore@google.com</owner>
   <owner>better-together-dev@google.com</owner>
   <summary>
@@ -1882,7 +1918,7 @@
 </histogram>
 
 <histogram name="MultiDevice.Setup.HasDuplicateEligibleHostDeviceNames"
-    enum="BooleanDuplicate" expires_after="2021-08-22">
+    enum="BooleanDuplicate" expires_after="2021-10-25">
   <owner>nohle@google.com</owner>
   <owner>better-together-dev@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/cryptohome/histograms.xml b/tools/metrics/histograms/histograms_xml/cryptohome/histograms.xml
index ba41a2f..74b534e 100644
--- a/tools/metrics/histograms/histograms_xml/cryptohome/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/cryptohome/histograms.xml
@@ -194,7 +194,7 @@
 </histogram>
 
 <histogram name="Cryptohome.DiskCleanupProgress"
-    enum="CryptohomeDiskCleanupProgress" expires_after="2021-08-22">
+    enum="CryptohomeDiskCleanupProgress" expires_after="2021-10-25">
   <owner>slangley@chromium.org</owner>
   <owner>weifangsun@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/download/histograms.xml b/tools/metrics/histograms/histograms_xml/download/histograms.xml
index c87ba49..aadfca34 100644
--- a/tools/metrics/histograms/histograms_xml/download/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/download/histograms.xml
@@ -103,7 +103,7 @@
 </histogram>
 
 <histogram name="Download.DangerousFile.DownloadValidatedByType"
-    enum="SBClientDownloadExtensions" expires_after="2021-08-22">
+    enum="SBClientDownloadExtensions" expires_after="2021-10-25">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/event/histograms.xml b/tools/metrics/histograms/histograms_xml/event/histograms.xml
index 0f8a2b3..d1400f47 100644
--- a/tools/metrics/histograms/histograms_xml/event/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/event/histograms.xml
@@ -324,7 +324,7 @@
 </histogram>
 
 <histogram name="Event.Latency.EndToEnd.TouchpadPinch2" units="microseconds"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>nzolghadr@chromium.org</owner>
   <owner>input-dev@chromium.org</owner>
   <summary>
@@ -764,7 +764,7 @@
 </histogram>
 
 <histogram name="Event.Latency.ScrollBegin.Touch.TimeToHandled2"
-    units="microseconds" expires_after="2021-08-22">
+    units="microseconds" expires_after="2021-10-25">
   <owner>flackr@chromium.org</owner>
   <owner>input-dev@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/extensions/histograms.xml b/tools/metrics/histograms/histograms_xml/extensions/histograms.xml
index 5512ce0..6b9ce8c 100644
--- a/tools/metrics/histograms/histograms_xml/extensions/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/extensions/histograms.xml
@@ -621,7 +621,7 @@
 </histogram>
 
 <histogram name="Extensions.DeclarativeNetRequest.IndexAndPersistRulesTime"
-    units="ms" expires_after="2021-08-22">
+    units="ms" expires_after="2021-10-25">
   <owner>karandeepb@chromium.org</owner>
   <owner>lazyboy@chromium.org</owner>
   <summary>
@@ -653,7 +653,7 @@
 </histogram>
 
 <histogram name="Extensions.DeclarativeNetRequest.ManifestEnabledRulesCount2"
-    units="rules" expires_after="2021-08-22">
+    units="rules" expires_after="2021-10-25">
   <owner>karandeepb@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
@@ -1349,7 +1349,7 @@
 </histogram>
 
 <histogram name="Extensions.ForceInstalledCreationStage"
-    enum="ExtensionInstallCreationStage" expires_after="2021-08-22">
+    enum="ExtensionInstallCreationStage" expires_after="2021-10-25">
   <owner>swapnilgupta@google.com</owner>
   <owner>burunduk@chromium.org</owner>
   <owner>managed-devices@google.com</owner>
@@ -1560,7 +1560,7 @@
 
 <histogram name="Extensions.ForceInstalledFailureWithCrxHeaderInvalidIsCWS"
     enum="IsForceInstalledExtensionFailedWithCrxHeaderInvalidFromCWSBoolean"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>swapnilgupta@google.com</owner>
   <owner>burunduk@chromium.org</owner>
   <owner>managed-devices@google.com</owner>
diff --git a/tools/metrics/histograms/histograms_xml/geolocation/histograms.xml b/tools/metrics/histograms/histograms_xml/geolocation/histograms.xml
index d63afe3..1484c0a 100644
--- a/tools/metrics/histograms/histograms_xml/geolocation/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/geolocation/histograms.xml
@@ -128,7 +128,7 @@
 </histogram>
 
 <histogram name="Geolocation.PositionCache.CacheHit" units="BooleanCacheHit"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>mattreynolds@chromium.org</owner>
   <owner>deviceapi-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml b/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
index d5d123bcf..43b63ffe 100644
--- a/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
+++ b/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
@@ -16522,6 +16522,8 @@
       name="SafeBrowsing.V4ProcessFullUpdate.ApplyUpdate.Result"/>
   <affected-histogram name="SafeBrowsing.V4ProcessFullUpdate.ApplyUpdate.Time"/>
   <affected-histogram
+      name="SafeBrowsing.V4ProcessFullUpdate.ApplyUpdateDuration"/>
+  <affected-histogram
       name="SafeBrowsing.V4ProcessFullUpdate.DecodeAdditions.Result"/>
   <affected-histogram
       name="SafeBrowsing.V4ProcessFullUpdate.DecodeAdditions.Time"/>
@@ -16535,6 +16537,8 @@
   <affected-histogram
       name="SafeBrowsing.V4ProcessPartialUpdate.ApplyUpdate.Time"/>
   <affected-histogram
+      name="SafeBrowsing.V4ProcessPartialUpdate.ApplyUpdateDuration"/>
+  <affected-histogram
       name="SafeBrowsing.V4ProcessPartialUpdate.DecodeAdditions.Result"/>
   <affected-histogram
       name="SafeBrowsing.V4ProcessPartialUpdate.DecodeAdditions.Time"/>
@@ -16552,6 +16556,8 @@
       name="SafeBrowsing.V4ReadFromDisk.DecodeAdditions.Result"/>
   <affected-histogram name="SafeBrowsing.V4ReadFromDisk.DecodeAdditions.Time"/>
   <affected-histogram name="SafeBrowsing.V4ReadFromDisk.MergeUpdate.Time"/>
+  <affected-histogram
+      name="SafeBrowsing.V4ReadFromDisk.VerifyChecksumDuration"/>
   <affected-histogram name="SafeBrowsing.V4Store.IsStoreValid"/>
 </histogram_suffixes>
 
diff --git a/tools/metrics/histograms/histograms_xml/input/histograms.xml b/tools/metrics/histograms/histograms_xml/input/histograms.xml
index c6c7a66..f9905b5 100644
--- a/tools/metrics/histograms/histograms_xml/input/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/input/histograms.xml
@@ -34,7 +34,7 @@
 </histogram>
 
 <histogram name="InputMethod.Assistive.Autocorrect.Actions"
-    enum="IMEAutocorrectActions" expires_after="2021-08-22">
+    enum="IMEAutocorrectActions" expires_after="2021-10-25">
   <owner>jopalmer@google.com</owner>
   <owner>essential-inputs-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/interstitial/histograms.xml b/tools/metrics/histograms/histograms_xml/interstitial/histograms.xml
index a89147d..28fdcba 100644
--- a/tools/metrics/histograms/histograms_xml/interstitial/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/interstitial/histograms.xml
@@ -87,7 +87,7 @@
 </histogram>
 
 <histogram name="interstitial.ssl.cause.nonoverridable" enum="SSLErrorCauses"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>estark@chromium.org</owner>
   <owner>carlosil@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/login/histograms.xml b/tools/metrics/histograms/histograms_xml/login/histograms.xml
index 76ce14a..3712b81 100644
--- a/tools/metrics/histograms/histograms_xml/login/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/login/histograms.xml
@@ -241,7 +241,7 @@
 </histogram>
 
 <histogram name="Login.SuccessReason" enum="LoginSuccessReason"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>achuith@chromium.org</owner>
   <summary>Chrome OS login success reason.</summary>
 </histogram>
diff --git a/tools/metrics/histograms/histograms_xml/media/histograms.xml b/tools/metrics/histograms/histograms_xml/media/histograms.xml
index 39b2a77a..b00e10c56 100644
--- a/tools/metrics/histograms/histograms_xml/media/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/media/histograms.xml
@@ -2056,7 +2056,7 @@
 </histogram>
 
 <histogram name="Media.GlobalMediaControls.DismissReason"
-    enum="GlobalMediaControlsDismissReason" expires_after="2021-08-22">
+    enum="GlobalMediaControlsDismissReason" expires_after="2021-10-25">
   <owner>steimel@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
@@ -2101,7 +2101,7 @@
 </histogram>
 
 <histogram name="Media.GlobalMediaControls.RepeatUsage" enum="BooleanIsRepeat"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>steimel@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
@@ -2464,7 +2464,7 @@
 </histogram>
 
 <histogram name="Media.LinuxAudioIO" enum="LinuxAudioIO"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>dalecurtis@chromium.org</owner>
   <summary>
     Audio IO layer used by the Linux OS, sampled once at startup of the browser.
@@ -2670,7 +2670,7 @@
 </histogram>
 
 <histogram name="Media.MojoVideoDecoder.InitialPlaybackErrorCodecCounter"
-    units="instances" expires_after="2021-08-22">
+    units="instances" expires_after="2021-10-25">
   <owner>eugene@chromium.org</owner>
   <owner>liberato@chromium.org</owner>
   <summary>
@@ -2681,7 +2681,7 @@
 </histogram>
 
 <histogram name="Media.MojoVideoDecoder.InitialPlaybackSuccessCodecCounter"
-    units="instances" expires_after="2021-08-22">
+    units="instances" expires_after="2021-10-25">
   <owner>eugene@chromium.org</owner>
   <owner>liberato@chromium.org</owner>
   <summary>
@@ -2748,7 +2748,7 @@
 </histogram>
 
 <histogram name="Media.MSE.DetectedShakaPackagerInMp4" enum="BooleanDetected"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>dalecurtis@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
@@ -2862,7 +2862,7 @@
 </histogram>
 
 <histogram name="Media.Notification.ArtworkPresent" enum="Boolean"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>beccahughes@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
@@ -2942,7 +2942,7 @@
 </histogram>
 
 <histogram name="Media.Notification.Count" units="count"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>beccahughes@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
@@ -2953,7 +2953,7 @@
 </histogram>
 
 <histogram name="Media.Notification.MetadataPresent"
-    enum="MediaNotificationMetadata" expires_after="2021-08-22">
+    enum="MediaNotificationMetadata" expires_after="2021-10-25">
   <owner>beccahughes@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
@@ -2975,7 +2975,7 @@
 </histogram>
 
 <histogram name="Media.Notification.UserAction" enum="MediaSessionAction"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>beccahughes@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/memory/histograms.xml b/tools/metrics/histograms/histograms_xml/memory/histograms.xml
index 9368f5d..d6e445cf 100644
--- a/tools/metrics/histograms/histograms_xml/memory/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/memory/histograms.xml
@@ -1115,7 +1115,7 @@
 </histogram>
 
 <histogram name="Memory.Experimental.Renderer.Uptime" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>keishi@chromium.org</owner>
   <summary>
     The uptime of a render process in time ticks (excludes extensions). Emitted
@@ -2350,7 +2350,7 @@
 </histogram>
 
 <histogram base="true" name="Memory.PressureWindowDuration" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
 <!-- Name completed by histogram_suffixes name="Memory.Pressure.TransitionType" -->
 
   <owner>sebmarchand@chromium.org</owner>
@@ -2457,7 +2457,7 @@
 </histogram>
 
 <histogram name="Memory.RendererProcessCount" units="processes"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>creis@chromium.org</owner>
   <owner>nasko@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/navigation/histograms.xml b/tools/metrics/histograms/histograms_xml/navigation/histograms.xml
index ed14d7be..e84ed4f 100644
--- a/tools/metrics/histograms/histograms_xml/navigation/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/navigation/histograms.xml
@@ -50,7 +50,7 @@
 </histogram>
 
 <histogram name="BackForwardCache.AllSites.HistoryNavigationOutcome"
-    enum="BackForwardCacheHistoryNavigationOutcome" expires_after="2021-08-22">
+    enum="BackForwardCacheHistoryNavigationOutcome" expires_after="2021-10-25">
   <owner>hajimehoshi@chromium.org</owner>
   <owner>bfcache-dev@chromium.org</owner>
   <summary>
@@ -1177,7 +1177,7 @@
 </histogram>
 
 <histogram name="Navigation.VerifyDidCommitParams"
-    enum="VerifyDidCommitParamsDifference" expires_after="2021-08-22">
+    enum="VerifyDidCommitParamsDifference" expires_after="2021-10-25">
   <owner>rakina@chromium.org</owner>
   <owner>altimin@chromium.org</owner>
   <summary>
@@ -1247,7 +1247,7 @@
 </histogram>
 
 <histogram name="NavigationPredictor.IsPubliclyRoutable" enum="Boolean"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>tbansal@chromium.org</owner>
   <owner>ryansturm@chromium.org</owner>
   <summary>
@@ -1318,7 +1318,7 @@
 </histogram>
 
 <histogram name="NavigationSuggestion.Event2" enum="NavigationSuggestionEvent"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>meacer@chromium.org</owner>
   <owner>security-enamel@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/nearby/OWNERS b/tools/metrics/histograms/histograms_xml/nearby/OWNERS
new file mode 100644
index 0000000..edffd6c2
--- /dev/null
+++ b/tools/metrics/histograms/histograms_xml/nearby/OWNERS
@@ -0,0 +1,5 @@
+per-file OWNERS=file://tools/metrics/histograms/histograms_xml/METRIC_REVIEWER_OWNERS
+
+# Prefer sending CLs to the owners listed below.
+# Use chromium-metrics-reviews@google.com as a backup.
+nohle@chromium.org
diff --git a/tools/metrics/histograms/histograms_xml/nearby/histograms.xml b/tools/metrics/histograms/histograms_xml/nearby/histograms.xml
index 54df4d6..472a0c3e 100644
--- a/tools/metrics/histograms/histograms_xml/nearby/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/nearby/histograms.xml
@@ -255,7 +255,7 @@
 
 <histogram
     name="Nearby.Share.Certificates.Manager.DownloadPublicCertificatesHttpResult"
-    enum="NearbyShareHttpResult" expires_after="2021-08-19">
+    enum="NearbyShareHttpResult" expires_after="2021-10-25">
   <owner>cclem@chromium.org</owner>
   <owner>nearby-share-chromeos-eng@google.com</owner>
   <summary>
@@ -279,7 +279,7 @@
 
 <histogram
     name="Nearby.Share.Certificates.Manager.DownloadPublicCertificatesSuccessRate"
-    enum="BooleanSuccess" expires_after="2021-08-19">
+    enum="BooleanSuccess" expires_after="2021-10-25">
   <owner>cclem@chromium.org</owner>
   <owner>nearby-share-chromeos-eng@google.com</owner>
   <summary>
@@ -291,7 +291,7 @@
 <histogram
     name="Nearby.Share.Certificates.Manager.GetDecryptedPublicCertificateResult"
     enum="NearbyShareCertificateManagerGetDecryptedPublicCertificateResult"
-    expires_after="2021-08-19">
+    expires_after="2021-10-25">
   <owner>cclem@chromium.org</owner>
   <owner>nearby-share-chromeos-eng@google.com</owner>
   <summary>
@@ -343,7 +343,7 @@
 </histogram>
 
 <histogram name="Nearby.Share.Connection.EstablishOutgoingConnectionStatus"
-    enum="NearbyShareFinalStatus" expires_after="2021-08-19">
+    enum="NearbyShareFinalStatus" expires_after="2021-10-25">
   <owner>nohle@chromium.org</owner>
   <owner>nearby-share-chromeos-eng@google.com</owner>
   <summary>
@@ -356,7 +356,7 @@
 </histogram>
 
 <histogram name="Nearby.Share.Connection.TimeToEstablishOutgoingConnection"
-    units="ms" expires_after="2021-08-19">
+    units="ms" expires_after="2021-10-25">
   <owner>nohle@chromium.org</owner>
   <owner>nearby-share-chromeos-eng@google.com</owner>
   <summary>
@@ -528,7 +528,7 @@
 </histogram>
 
 <histogram name="Nearby.Share.Discovery.FurthestDiscoveryProgress"
-    enum="NearbyShareDiscoveryProgress" expires_after="2021-08-19">
+    enum="NearbyShareDiscoveryProgress" expires_after="2021-10-25">
   <owner>nohle@chromium.org</owner>
   <owner>nearby-share-chromeos-eng@google.com</owner>
   <summary>
@@ -584,7 +584,7 @@
 </histogram>
 
 <histogram name="Nearby.Share.Enabled" enum="NearbyShareEnabledState"
-    expires_after="2021-08-19">
+    expires_after="2021-10-25">
   <owner>nohle@chromium.org</owner>
   <owner>nearby-share-chromeos-eng@google.com</owner>
   <summary>
@@ -597,7 +597,7 @@
 </histogram>
 
 <histogram name="Nearby.Share.IsKnownContact" enum="BooleanKnown"
-    expires_after="2021-08-19">
+    expires_after="2021-10-25">
   <owner>nohle@chromium.org</owner>
   <owner>nearby-share-chromeos-eng@google.com</owner>
   <summary>
@@ -608,7 +608,7 @@
 </histogram>
 
 <histogram name="Nearby.Share.LocalDeviceData.DeviceDataUpdater.HttpResult"
-    enum="NearbyShareHttpResult" expires_after="2021-08-19">
+    enum="NearbyShareHttpResult" expires_after="2021-10-25">
   <owner>cclem@chromium.org</owner>
   <owner>nearby-share-chromeos-eng@google.com</owner>
   <summary>
@@ -619,7 +619,7 @@
 </histogram>
 
 <histogram name="Nearby.Share.Medium.ChangedToMedium"
-    enum="NearbyConnectionsMedium" expires_after="2021-08-19">
+    enum="NearbyConnectionsMedium" expires_after="2021-10-25">
   <owner>nohle@chromium.org</owner>
   <owner>nearby-share-chromeos-eng@google.com</owner>
   <summary>
@@ -650,7 +650,7 @@
 </histogram>
 
 <histogram name="Nearby.Share.Medium.InitiateBandwidthUpgradeResult"
-    enum="BooleanSuccess" expires_after="2021-08-19">
+    enum="BooleanSuccess" expires_after="2021-10-25">
   <owner>nohle@chromium.org</owner>
   <owner>nearby-share-chromeos-eng@google.com</owner>
   <summary>
@@ -664,7 +664,7 @@
 </histogram>
 
 <histogram name="Nearby.Share.Medium.RequestedBandwidthUpgradeResult"
-    enum="BooleanUpgraded" expires_after="2021-08-19">
+    enum="BooleanUpgraded" expires_after="2021-10-25">
   <owner>nohle@chromium.org</owner>
   <owner>nearby-share-chromeos-eng@google.com</owner>
   <summary>
@@ -1132,7 +1132,7 @@
 </histogram>
 
 <histogram name="Nearby.Share.VisibilityChoice" enum="NearbyShareVisibility"
-    expires_after="2021-08-19">
+    expires_after="2021-10-25">
   <owner>nohle@chromium.org</owner>
   <owner>nearby-share-chromeos-eng@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/net/histograms.xml b/tools/metrics/histograms/histograms_xml/net/histograms.xml
index a9373ff8..e4e8523 100644
--- a/tools/metrics/histograms/histograms_xml/net/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/net/histograms.xml
@@ -371,7 +371,7 @@
 </histogram>
 
 <histogram name="Net.CertVerifier_TrialComparisonResult"
-    enum="CertVerifierTrialComparisonResult" expires_after="2021-08-22">
+    enum="CertVerifierTrialComparisonResult" expires_after="2021-10-25">
   <owner>mattm@chromium.org</owner>
   <owner>rsleevi@chromium.org</owner>
   <summary>
@@ -1202,7 +1202,7 @@
 </histogram>
 
 <histogram name="Net.DNS.JobQueueTime.Failure" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>dmcardle@chromium.org</owner>
   <owner>ericorth@chromium.org</owner>
   <summary>
@@ -1213,7 +1213,7 @@
 </histogram>
 
 <histogram name="Net.DNS.JobQueueTime.PerTransaction" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>dmcardle@chromium.org</owner>
   <owner>ericorth@chromium.org</owner>
   <summary>
@@ -1226,7 +1226,7 @@
 </histogram>
 
 <histogram name="Net.DNS.JobQueueTime.Success" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>dmcardle@chromium.org</owner>
   <owner>ericorth@chromium.org</owner>
   <summary>
@@ -1825,7 +1825,7 @@
 </histogram>
 
 <histogram name="Net.HttpAuthPromptType" enum="HttpAuthPromptType"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>meacer@chromium.org</owner>
   <owner>src/chrome/browser/ui/login/OWNERS</owner>
   <summary>Type of the HTTP auth prompt displayed.</summary>
@@ -2226,7 +2226,7 @@
 </histogram>
 
 <histogram name="Net.Ntlm.HashDependsOnLocale" enum="Boolean"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>eroman@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>
@@ -2585,7 +2585,7 @@
 </histogram>
 
 <histogram name="Net.QuicHandshakeState" enum="QuicHandshakeState"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -5274,7 +5274,7 @@
 </histogram>
 
 <histogram name="Net.TcpConnectAttempt.Latency.Success" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>eroman@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>
@@ -5284,7 +5284,7 @@
 </histogram>
 
 <histogram name="Net.TcpConnectAttempt.LatencyPercentRTT.Error" units="%"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>eroman@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>
@@ -5297,7 +5297,7 @@
 </histogram>
 
 <histogram name="Net.TcpConnectAttempt.LatencyPercentRTT.Success" units="%"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>eroman@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/new_tab_page/histograms.xml b/tools/metrics/histograms/histograms_xml/new_tab_page/histograms.xml
index d1ef6c54..862543f4 100644
--- a/tools/metrics/histograms/histograms_xml/new_tab_page/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/new_tab_page/histograms.xml
@@ -129,7 +129,7 @@
 </histogram>
 
 <histogram name="NewTabPage.ContentSuggestions.BackgroundFetchTrigger"
-    enum="BackgroundFetchTrigger" expires_after="2021-08-22">
+    enum="BackgroundFetchTrigger" expires_after="2021-10-25">
   <owner>carlosk@chromium.org</owner>
   <owner>freedjm@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -362,7 +362,7 @@
 </histogram>
 
 <histogram name="NewTabPage.ContentSuggestions.OpenedAge" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>carlosk@chromium.org</owner>
   <owner>freedjm@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -400,7 +400,7 @@
 </histogram>
 
 <histogram name="NewTabPage.ContentSuggestions.Preferences.RemoteSuggestions"
-    enum="BooleanEnabled" expires_after="2021-08-22">
+    enum="BooleanEnabled" expires_after="2021-10-25">
   <owner>carlosk@chromium.org</owner>
   <owner>freedjm@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -485,7 +485,7 @@
 </histogram>
 
 <histogram name="NewTabPage.ContentSuggestions.TimeSinceSuggestionFetched"
-    units="ms" expires_after="2021-08-22">
+    units="ms" expires_after="2021-10-25">
   <owner>carlosk@chromium.org</owner>
   <owner>freedjm@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -498,7 +498,7 @@
 
 <histogram base="true"
     name="NewTabPage.ContentSuggestions.TimeUntilFirstShownTrigger" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>carlosk@chromium.org</owner>
   <owner>freedjm@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -567,7 +567,7 @@
 </histogram>
 
 <histogram name="NewTabPage.ContentSuggestions.UsageTimeLocal" units="units"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>carlosk@chromium.org</owner>
   <owner>freedjm@chromium.org</owner>
   <owner>feed@chromium.org</owner>
@@ -1014,7 +1014,7 @@
 </histogram>
 
 <histogram name="NewTabPage.Modules.VisibleOnNTPLoad" enum="BooleanVisible"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>tiborg@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
@@ -1290,7 +1290,7 @@
 </histogram>
 
 <histogram name="NewTabPage.RepeatableQueries.ExtractedCount" units="count"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>mahmadi@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
   <summary>
@@ -1302,7 +1302,7 @@
 </histogram>
 
 <histogram name="NewTabPage.RepeatableQueries.ExtractionDuration" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>mahmadi@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
   <summary>
@@ -1762,7 +1762,7 @@
 </histogram>
 
 <histogram name="NewTabPage.TileType" enum="NTPTileVisualType"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>dbeam@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
   <summary>
@@ -1773,7 +1773,7 @@
 </histogram>
 
 <histogram name="NewTabPage.TileTypeClicked" enum="NTPTileVisualType"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>dbeam@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/notifications/histograms.xml b/tools/metrics/histograms/histograms_xml/notifications/histograms.xml
index e0d575b..7ae7cc5e 100644
--- a/tools/metrics/histograms/histograms_xml/notifications/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/notifications/histograms.xml
@@ -56,7 +56,7 @@
 </histogram>
 
 <histogram name="Notifications.Android.SitesChannel" enum="BooleanUsage"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>peter@chromium.org</owner>
   <summary>
     Recorded when the Sites channel (which we hope to deprecate) is used as a
@@ -291,7 +291,7 @@
 </histogram>
 
 <histogram name="Notifications.Database.OldestNotificationTimeInMinutes"
-    units="minutes" expires_after="2021-08-22">
+    units="minutes" expires_after="2021-10-25">
   <owner>knollr@chromium.org</owner>
   <owner>peter@chromium.org</owner>
   <owner>adelm@google.com</owner>
@@ -355,7 +355,7 @@
 </histogram>
 
 <histogram name="Notifications.Database.ReadResult"
-    enum="NotificationDatabaseStatus" expires_after="2021-08-22">
+    enum="NotificationDatabaseStatus" expires_after="2021-10-25">
   <owner>knollr@chromium.org</owner>
   <owner>peter@chromium.org</owner>
   <summary>
@@ -365,7 +365,7 @@
 </histogram>
 
 <histogram name="Notifications.Database.WriteResult"
-    enum="NotificationDatabaseStatus" expires_after="2021-08-22">
+    enum="NotificationDatabaseStatus" expires_after="2021-10-25">
   <owner>knollr@chromium.org</owner>
   <owner>peter@chromium.org</owner>
   <summary>
@@ -525,7 +525,7 @@
 </histogram>
 
 <histogram name="Notifications.Permissions.RevokeDeleteCount" units="units"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>knollr@chromium.org</owner>
   <owner>peter@chromium.org</owner>
   <summary>
@@ -536,7 +536,7 @@
 
 <histogram
     name="Notifications.Permissions.UNNotification.Banners.PermissionStatus"
-    enum="UNNotificationPermissionStatus" expires_after="2021-08-22">
+    enum="UNNotificationPermissionStatus" expires_after="2021-10-25">
   <owner>adelm@google.com</owner>
   <owner>knollr@chromium.org</owner>
   <owner>peter@chromium.org</owner>
@@ -571,7 +571,7 @@
 </histogram>
 
 <histogram name="Notifications.PersistentNotificationActionCount"
-    units="buttons" expires_after="2021-08-22">
+    units="buttons" expires_after="2021-10-25">
   <owner>peter@chromium.org</owner>
   <summary>
     The number of action buttons the developer provided for a persistent Web
@@ -580,7 +580,7 @@
 </histogram>
 
 <histogram name="Notifications.PersistentNotificationDisplayResult"
-    enum="PersistentNotificationDisplayResult" expires_after="2021-08-22">
+    enum="PersistentNotificationDisplayResult" expires_after="2021-10-25">
   <owner>knollr@chromium.org</owner>
   <owner>peter@chromium.org</owner>
   <summary>
@@ -600,7 +600,7 @@
 </histogram>
 
 <histogram name="Notifications.PersistentWebNotificationClickResult"
-    enum="PlatformNotificationStatus" expires_after="2021-08-22">
+    enum="PlatformNotificationStatus" expires_after="2021-10-25">
   <owner>peter@chromium.org</owner>
   <owner>deepak.m1@samsung.com</owner>
   <summary>
@@ -620,7 +620,7 @@
 </histogram>
 
 <histogram name="Notifications.PersistentWebNotificationCloseResult"
-    enum="PlatformNotificationStatus" expires_after="2021-08-22">
+    enum="PlatformNotificationStatus" expires_after="2021-10-25">
   <owner>peter@chromium.org</owner>
   <owner>knollr@chromium.org</owner>
   <summary>
@@ -842,7 +842,7 @@
 </histogram>
 
 <histogram name="Notifications.Triggers.HasShowTrigger" enum="Boolean"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>knollr@chromium.org</owner>
   <owner>peter@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/omnibox/OWNERS b/tools/metrics/histograms/histograms_xml/omnibox/OWNERS
new file mode 100644
index 0000000..bc7608b6c
--- /dev/null
+++ b/tools/metrics/histograms/histograms_xml/omnibox/OWNERS
@@ -0,0 +1,5 @@
+per-file OWNERS=file://tools/metrics/histograms/histograms_xml/METRIC_REVIEWER_OWNERS
+
+# Prefer sending CLs to the owners listed below.
+# Use chromium-metrics-reviews@google.com as a backup.
+ender@chromium.org
diff --git a/tools/metrics/histograms/histograms_xml/others/histograms.xml b/tools/metrics/histograms/histograms_xml/others/histograms.xml
index 1bb0a916..e967745 100644
--- a/tools/metrics/histograms/histograms_xml/others/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/others/histograms.xml
@@ -1614,7 +1614,7 @@
 </histogram>
 
 <histogram name="BookmarkManager.ResultsRenderedTime" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>calamity@chromium.org</owner>
   <owner>dbeam@chromium.org</owner>
   <summary>
@@ -1817,7 +1817,7 @@
 </histogram>
 
 <histogram name="Bookmarks.LaunchLocation" enum="BookmarkLaunchLocation"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>ianwen@chromium.org</owner>
   <summary>Logs a UI location from which a bookmark is launched.</summary>
 </histogram>
@@ -2724,7 +2724,7 @@
 </histogram>
 
 <histogram name="ClientHints.CriticalCHRestart" enum="CriticalCHRestart"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>aarontag@chromium.org</owner>
   <owner>yoavweiss@chromium.org</owner>
   <summary>
@@ -2733,7 +2733,7 @@
 </histogram>
 
 <histogram name="ClientHints.PersistDuration" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>yoavweiss@chromium.org</owner>
   <owner>tbansal@chromium.org</owner>
   <owner>mkwst@chromium.org</owner>
@@ -2745,7 +2745,7 @@
 </histogram>
 
 <histogram name="ClientHints.UpdateEventCount" units="count"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>yoavweiss@chromium.org</owner>
   <owner>tbansal@chromium.org</owner>
   <owner>mkwst@chromium.org</owner>
@@ -2757,7 +2757,7 @@
 </histogram>
 
 <histogram name="ClientHints.UpdateSize" units="count"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>yoavweiss@chromium.org</owner>
   <owner>tbansal@chromium.org</owner>
   <owner>mkwst@chromium.org</owner>
@@ -2996,7 +2996,7 @@
 </histogram>
 
 <histogram name="ConfigureDisplays.External.Modeset.RefreshRate" units="Hz"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>dcastagna@chromium.org</owner>
   <owner>marcheu@chromium.org</owner>
   <owner>chromeos-gfx@chromium.org</owner>
@@ -3199,7 +3199,7 @@
 </histogram>
 
 <histogram name="ContextMenu.CancelSystemTouches" enum="BooleanHit"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>michaeldo@chromium.org</owner>
   <owner>src/ios/web/OWNERS</owner>
   <summary>
@@ -4985,7 +4985,7 @@
 </histogram>
 
 <histogram name="Drive.PushNotificationInitiallyEnabled" enum="BooleanEnabled"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>simmonsjosh@google.com</owner>
   <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
@@ -4996,7 +4996,7 @@
 </histogram>
 
 <histogram name="Drive.PushNotificationRegistered" enum="BooleanRegistered"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>simmonsjosh@google.com</owner>
   <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
@@ -5022,7 +5022,7 @@
 </histogram>
 
 <histogram name="DriveCommon.Lifecycle.MountTime" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>simmonsjosh@google.com</owner>
   <owner>src/ui/file_manager/OWNERS</owner>
   <summary>How long did it take to mount Google Drive.</summary>
@@ -5337,7 +5337,7 @@
 </histogram>
 
 <histogram name="ExploreSites.MonthlyHostCount" units="hosts"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>dimich@chromium.org</owner>
   <summary>
     Number of unique hosts visited by the user during the last 30 days. Reported
@@ -5983,7 +5983,7 @@
 </histogram>
 
 <histogram name="FirstUserAction.HandsetUserActionType"
-    enum="FirstUserActionType" expires_after="2021-08-22">
+    enum="FirstUserActionType" expires_after="2021-10-25">
   <owner>justincohen@chromium.org</owner>
   <owner>olivierrobin@chromium.org</owner>
   <summary>
@@ -7696,7 +7696,7 @@
 </histogram>
 
 <histogram base="true" name="InProductHelp.NotifyEventReadyState"
-    enum="BooleanSuccess" expires_after="2021-08-22">
+    enum="BooleanSuccess" expires_after="2021-10-25">
   <owner>nyquist@chromium.org</owner>
   <owner>xingliu@chromium.org</owner>
 <!-- Name completed by histogram_suffixes name="IPHFeatures" -->
@@ -8400,7 +8400,7 @@
 </histogram>
 
 <histogram name="Launch.HomeScreen" enum="LaunchFromHomeScreen"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>dominickn@chromium.org</owner>
   <owner>hartmanng@chromium.org</owner>
   <owner>peconn@chromium.org</owner>
@@ -8678,7 +8678,7 @@
 </histogram>
 
 <histogram name="Lens.ImageClassification.ResultState"
-    enum="LensClassifyResultState" expires_after="2021-08-22">
+    enum="LensClassifyResultState" expires_after="2021-10-25">
   <owner>yusuyoutube@google.com</owner>
   <owner>benwgold@google.com</owner>
   <summary>
@@ -8688,7 +8688,7 @@
 </histogram>
 
 <histogram name="Lens.ImageClassification.SdkError" enum="LensSdkError"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>yusuyoutube@google.com</owner>
   <owner>benwgold@google.com</owner>
   <summary>
@@ -8761,7 +8761,7 @@
 </histogram>
 
 <histogram name="Linux.Distro.Debian" enum="LinuxDistroDebianVersion"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>thestig@chromium.org</owner>
   <owner>thomasanderson@chromium.org</owner>
   <summary>
@@ -8771,7 +8771,7 @@
 </histogram>
 
 <histogram name="Linux.Distro.Fedora" enum="LinuxDistroFedoraVersion"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>thestig@chromium.org</owner>
   <owner>thomasanderson@chromium.org</owner>
   <summary>
@@ -8781,7 +8781,7 @@
 </histogram>
 
 <histogram name="Linux.Distro.OpenSuseLeap"
-    enum="LinuxDistroOpenSuseLeapVersion" expires_after="2021-08-22">
+    enum="LinuxDistroOpenSuseLeapVersion" expires_after="2021-10-25">
   <owner>thestig@chromium.org</owner>
   <owner>thomasanderson@chromium.org</owner>
   <summary>
@@ -8791,7 +8791,7 @@
 </histogram>
 
 <histogram name="Linux.Distro.Ubuntu" enum="LinuxDistroUbuntuVersion"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>thestig@chromium.org</owner>
   <owner>thomasanderson@chromium.org</owner>
   <summary>
@@ -8800,7 +8800,7 @@
   </summary>
 </histogram>
 
-<histogram name="Linux.Distro2" enum="LinuxDistro2" expires_after="2021-08-22">
+<histogram name="Linux.Distro2" enum="LinuxDistro2" expires_after="2021-10-25">
   <owner>thestig@chromium.org</owner>
   <owner>thomasanderson@chromium.org</owner>
   <summary>The Linux distro used. Logged on each start up.</summary>
@@ -8930,7 +8930,7 @@
 
 <histogram name="LoadingPredictor.OptimizationHintsReceiveStatus"
     enum="LoadingPredictorOptimizationHintsReceiveStatus"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>sophiechang@chromium.org</owner>
   <owner>tbansal@chromium.org</owner>
   <summary>
@@ -9209,7 +9209,7 @@
 </histogram>
 
 <histogram name="ManagedUsers.FilteringResult"
-    enum="SupervisedUserSafetyFilterResult" expires_after="2021-08-15">
+    enum="SupervisedUserSafetyFilterResult" expires_after="2021-10-25">
   <owner>agawronska@chromium.org</owner>
   <owner>cros-families@google.com</owner>
   <summary>
@@ -9933,7 +9933,7 @@
 </histogram>
 
 <histogram name="MultiProfile.UsersPerSessionIncremental" units="units"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>skuhne@chromium.org</owner>
   <summary>
     The number of users simultaneously signed into a multiprofile session on
@@ -12211,7 +12211,7 @@
 </histogram>
 
 <histogram name="ProtoDB.InitStatus" enum="LevelDBStatus"
-    expires_after="2021-08-24">
+    expires_after="2021-10-25">
   <owner>nyquist@chromium.org</owner>
   <owner>ssid@chromium.org</owner>
   <summary>The LevelDB Status from a ProtoDatabase Init call.</summary>
@@ -12241,7 +12241,7 @@
 </histogram>
 
 <histogram name="ProtoDB.SharedDbInitStatus" enum="ProtoDatabaseInitState"
-    expires_after="2021-08-24">
+    expires_after="2021-10-25">
   <owner>ssid@chromium.org</owner>
   <owner>salg@chromium.org</owner>
   <summary>
@@ -15026,7 +15026,7 @@
 </histogram>
 
 <histogram name="SpellCheck.SuggestionHitRatio" units="%"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>gujen@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -16438,7 +16438,7 @@
 </histogram>
 
 <histogram name="TileManager.ExceededMemoryBudget" enum="TileMemoryBudget"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>pdr@chromium.org</owner>
   <owner>vmpstr@chromium.org</owner>
   <summary>
@@ -17541,7 +17541,7 @@
 </histogram>
 
 <histogram name="VoiceInteraction.DismissedEventSource"
-    enum="VoiceInteractionEventSource" expires_after="2021-08-22">
+    enum="VoiceInteractionEventSource" expires_after="2021-10-25">
   <owner>wylieb@chromium.org</owner>
   <owner>tedchoc@chromium.org</owner>
   <owner>yusufo@chromium.org</owner>
@@ -17564,7 +17564,7 @@
 </histogram>
 
 <histogram name="VoiceInteraction.FailureEventSource"
-    enum="VoiceInteractionEventSource" expires_after="2021-08-22">
+    enum="VoiceInteractionEventSource" expires_after="2021-10-25">
   <owner>wylieb@chromium.org</owner>
   <owner>tedchoc@chromium.org</owner>
   <owner>yusufo@chromium.org</owner>
@@ -17590,7 +17590,7 @@
 </histogram>
 
 <histogram name="VoiceInteraction.FinishEventSource"
-    enum="VoiceInteractionEventSource" expires_after="2021-08-22">
+    enum="VoiceInteractionEventSource" expires_after="2021-10-25">
   <owner>wylieb@chromium.org</owner>
   <owner>tedchoc@chromium.org</owner>
   <owner>yusufo@chromium.org</owner>
@@ -17669,7 +17669,7 @@
 </histogram>
 
 <histogram name="VoiceInteraction.StartEventSource"
-    enum="VoiceInteractionEventSource" expires_after="2021-08-22">
+    enum="VoiceInteractionEventSource" expires_after="2021-10-25">
   <owner>wylieb@chromium.org</owner>
   <owner>tedchoc@chromium.org</owner>
   <owner>yusufo@chromium.org</owner>
@@ -17746,7 +17746,7 @@
 </histogram>
 
 <histogram name="VoiceInteraction.VoiceSearchResult" enum="BooleanSuccess"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>wylieb@chromium.org</owner>
   <owner>tedchoc@chromium.org</owner>
   <owner>yusufo@chromium.org</owner>
@@ -17936,7 +17936,7 @@
 </histogram>
 
 <histogram name="Webapp.Install.InstallBounce" enum="WebappInstallSource"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>dominickn@chromium.org</owner>
   <owner>alancutter@chromium.org</owner>
   <summary>
@@ -18081,7 +18081,7 @@
 </histogram>
 
 <histogram name="Webapp.NavigationStatus" enum="BooleanSuccess"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>peter@chromium.org</owner>
   <owner>hartmanng@chromium.org</owner>
   <summary>
@@ -18707,7 +18707,7 @@
 </histogram>
 
 <histogram name="WebsiteSettings.OriginInfo.PermissionChanged"
-    enum="ContentType" expires_after="2021-08-22">
+    enum="ContentType" expires_after="2021-10-25">
   <owner>andypaicu@chromium.org</owner>
   <owner>engedy@chromium.org</owner>
   <owner>hkamila@chromium.org</owner>
@@ -18841,7 +18841,7 @@
 </histogram>
 
 <histogram name="WebUITabStrip.TabCreation" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>robliao@chromium.org</owner>
   <owner>johntlee@chromium.org</owner>
   <summary>
@@ -18850,7 +18850,7 @@
 </histogram>
 
 <histogram name="WebUITabStrip.TabDataReceived" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>robliao@chromium.org</owner>
   <owner>johntlee@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/page/histograms.xml b/tools/metrics/histograms/histograms_xml/page/histograms.xml
index 9865b52..d407645 100644
--- a/tools/metrics/histograms/histograms_xml/page/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/page/histograms.xml
@@ -789,7 +789,7 @@
 
 <histogram
     name="PageLoad.Clients.ThirdParty.Frames.NavigationToFirstContentfulPaint3"
-    units="ms" expires_after="2021-08-22">
+    units="ms" expires_after="2021-10-25">
   <owner>jkarlin@chromium.org</owner>
   <owner>johnidel@chromium.org</owner>
   <summary>
@@ -1123,7 +1123,7 @@
 </histogram>
 
 <histogram name="PageLoad.Experimental.InputTiming.InputToNavigationStart"
-    units="ms" expires_after="2021-08-22">
+    units="ms" expires_after="2021-10-25">
   <owner>sullivan@chromium.org</owner>
   <summary>
     The time between the OS-level input event that initiated a navigation, and
diff --git a/tools/metrics/histograms/histograms_xml/password/histograms.xml b/tools/metrics/histograms/histograms_xml/password/histograms.xml
index ac1c4e7..09bf0f4 100644
--- a/tools/metrics/histograms/histograms_xml/password/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/password/histograms.xml
@@ -153,7 +153,7 @@
 </histogram>
 
 <histogram name="PasswordManager.AbleToSavePasswordsOnSuccessfulLogin"
-    enum="BooleanSuccess" expires_after="2021-08-22">
+    enum="BooleanSuccess" expires_after="2021-10-25">
   <owner>vasilii@chromium.org</owner>
   <owner>src/components/password_manager/OWNERS</owner>
   <summary>
@@ -204,7 +204,7 @@
 </histogram>
 
 <histogram base="true" name="PasswordManager.AccountsPerSiteHiRes"
-    units="units" expires_after="2021-08-22">
+    units="units" expires_after="2021-10-25">
   <owner>kazinova@google.com</owner>
   <owner>vasilii@chromium.org</owner>
   <summary>
@@ -584,7 +584,7 @@
 </histogram>
 
 <histogram name="PasswordManager.AllPasswordsBottomSheet.UserAction"
-    enum="AllPasswordsBottomSheetActions" expires_after="2021-08-22">
+    enum="AllPasswordsBottomSheetActions" expires_after="2021-10-25">
   <owner>redatawfik@google.com</owner>
   <owner>fhorschig@chromium.org</owner>
   <summary>
@@ -822,7 +822,7 @@
 </histogram>
 
 <histogram name="PasswordManager.BulkCheck.PasswordCheckReferrerAndroid"
-    enum="PasswordCheckReferrerAndroid" expires_after="2021-08-22">
+    enum="PasswordCheckReferrerAndroid" expires_after="2021-10-25">
   <owner>ioanap@chromium.org</owner>
   <owner>fhorschig@chromium.org</owner>
   <summary>
@@ -1028,7 +1028,7 @@
 </histogram>
 
 <histogram name="PasswordManager.DefaultPasswordStoreSet"
-    enum="PasswordManager.Store" expires_after="2021-08-22">
+    enum="PasswordManager.Store" expires_after="2021-10-25">
   <owner>mamir@chromium.org</owner>
   <owner>treib@chromium.org</owner>
   <summary>
@@ -1589,7 +1589,7 @@
 </histogram>
 
 <histogram name="PasswordManager.LoginDatabaseInit"
-    enum="LoginDatabaseInitError" expires_after="2021-08-22">
+    enum="LoginDatabaseInitError" expires_after="2021-10-25">
   <owner>vasilii@chromium.org</owner>
   <owner>mamir@chromium.org</owner>
   <summary>An error on LoginDatabase initialization.</summary>
@@ -1674,7 +1674,7 @@
 </histogram>
 
 <histogram name="PasswordManager.MoveUIDismissalReason"
-    enum="PasswordManagerUIDismissalReason" expires_after="2021-08-22">
+    enum="PasswordManagerUIDismissalReason" expires_after="2021-10-25">
 <!-- Name completed by histogram_suffixes name="PasswordAccountStorageUserState" -->
 
   <owner>mamir@chromium.org</owner>
@@ -2120,7 +2120,7 @@
 </histogram>
 
 <histogram name="PasswordManager.SubmittedFormFrame"
-    enum="SubmittedPasswordFormFrame" expires_after="2021-08-22">
+    enum="SubmittedPasswordFormFrame" expires_after="2021-10-25">
   <owner>kazinova@google.com</owner>
   <owner>vasilii@chromium.org</owner>
   <summary>
@@ -2203,7 +2203,7 @@
 </histogram>
 
 <histogram name="PasswordManager.TimeBetweenStoreAndServer" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>kazinova@google.com</owner>
   <owner>battre@chromium.org</owner>
   <summary>
@@ -2227,7 +2227,7 @@
 </histogram>
 
 <histogram base="true" name="PasswordManager.TotalAccountsHiRes.ByType"
-    units="units" expires_after="2021-08-22">
+    units="units" expires_after="2021-10-25">
   <owner>battre@chromium.org</owner>
   <owner>vasilii@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/permissions/histograms.xml b/tools/metrics/histograms/histograms_xml/permissions/histograms.xml
index f37cd8f..e81656d 100644
--- a/tools/metrics/histograms/histograms_xml/permissions/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/permissions/histograms.xml
@@ -155,7 +155,7 @@
 </histogram>
 
 <histogram name="Permissions.Chip.TimeToInteraction" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>bsep@chromium.org</owner>
   <owner>engedy@chromium.org</owner>
   <owner>olesiamarukhno@google.com</owner>
@@ -233,7 +233,7 @@
 </histogram>
 
 <histogram name="Permissions.CrowdDeny.PreloadData.WarningOnly" enum="Boolean"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>andypaicu@chromium.org</owner>
   <owner>engedy@chromium.org</owner>
   <owner>hkamila@chromium.org</owner>
@@ -269,7 +269,7 @@
 </histogram>
 
 <histogram name="Permissions.Engagement.Accepted" units="%"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>engedy@chromium.org</owner>
   <owner>src/components/permissions/PERMISSIONS_OWNERS</owner>
   <summary>
@@ -290,7 +290,7 @@
 </histogram>
 
 <histogram name="Permissions.Engagement.Denied" units="%"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>engedy@chromium.org</owner>
   <owner>src/components/permissions/PERMISSIONS_OWNERS</owner>
   <summary>
@@ -300,7 +300,7 @@
 </histogram>
 
 <histogram name="Permissions.Engagement.Dismissed" units="%"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>engedy@chromium.org</owner>
   <owner>src/components/permissions/PERMISSIONS_OWNERS</owner>
   <summary>
@@ -310,7 +310,7 @@
 </histogram>
 
 <histogram name="Permissions.Engagement.Ignored" units="%"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>engedy@chromium.org</owner>
   <owner>src/components/permissions/PERMISSIONS_OWNERS</owner>
   <summary>
@@ -320,7 +320,7 @@
 </histogram>
 
 <histogram name="Permissions.MissingOSLevelPermission.Action"
-    enum="PermissionAction" expires_after="2021-08-22">
+    enum="PermissionAction" expires_after="2021-10-25">
   <owner>andypaicu@chromium.org</owner>
   <owner>engedy@chromium.org</owner>
   <owner>hkamila@chromium.org</owner>
@@ -348,7 +348,7 @@
 </histogram>
 
 <histogram name="Permissions.Prompt.Accepted" enum="PermissionRequestType"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>dominickn@chromium.org</owner>
   <owner>kcarattini@chromium.org</owner>
   <summary>
@@ -422,7 +422,7 @@
 </histogram>
 
 <histogram name="Permissions.Prompt.Denied" enum="PermissionRequestType"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>dominickn@chromium.org</owner>
   <owner>kcarattini@chromium.org</owner>
   <summary>
@@ -601,7 +601,7 @@
 </histogram>
 
 <histogram base="true" name="Permissions.Revocation.ElapsedTimeSinceGrant"
-    units="seconds" expires_after="2021-08-20">
+    units="seconds" expires_after="2021-10-25">
   <owner>engedy@chromium.org</owner>
   <owner>src/components/permissions/PERMISSIONS_OWNERS</owner>
   <summary>
@@ -628,7 +628,7 @@
 </histogram>
 
 <histogram base="true" name="Permissions.Usage.ElapsedTimeSinceGrant"
-    units="seconds" expires_after="2021-08-20">
+    units="seconds" expires_after="2021-10-25">
   <owner>engedy@chromium.org</owner>
   <owner>src/components/permissions/PERMISSIONS_OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/phonehub/OWNERS b/tools/metrics/histograms/histograms_xml/phonehub/OWNERS
new file mode 100644
index 0000000..edffd6c2
--- /dev/null
+++ b/tools/metrics/histograms/histograms_xml/phonehub/OWNERS
@@ -0,0 +1,5 @@
+per-file OWNERS=file://tools/metrics/histograms/histograms_xml/METRIC_REVIEWER_OWNERS
+
+# Prefer sending CLs to the owners listed below.
+# Use chromium-metrics-reviews@google.com as a backup.
+nohle@chromium.org
diff --git a/tools/metrics/histograms/histograms_xml/platform/histograms.xml b/tools/metrics/histograms/histograms_xml/platform/histograms.xml
index 86f4980..5b5f570f 100644
--- a/tools/metrics/histograms/histograms_xml/platform/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/platform/histograms.xml
@@ -593,7 +593,7 @@
   </summary>
 </histogram>
 
-<histogram name="Platform.Meminfo" units="KB" expires_after="2021-10-24">
+<histogram name="Platform.Meminfo" units="KB" expires_after="2021-10-25">
   <owner>hajimehoshi@chromium.org</owner>
   <owner>kouhei@chromium.org</owner>
   <owner>sonnyrao@chromium.org</owner>
@@ -862,7 +862,7 @@
 </histogram>
 
 <histogram name="Platform.StatefulLifetimeWrites" units="GiB"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>asavery@chromium.org</owner>
   <owner>gwendal@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/plugin/histograms.xml b/tools/metrics/histograms/histograms_xml/plugin/histograms.xml
index d885870..00487d4 100644
--- a/tools/metrics/histograms/histograms_xml/plugin/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/plugin/histograms.xml
@@ -115,7 +115,7 @@
 </histogram>
 
 <histogram name="PluginVm.AppsInstalledAtLogin" units="apps"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>timloh@google.com</owner>
   <owner>joelhockey@google.com</owner>
   <summary>
@@ -143,7 +143,7 @@
 </histogram>
 
 <histogram name="PluginVm.EngagementTime.Foreground" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>timloh@google.com</owner>
   <owner>joelhockey@google.com</owner>
   <summary>
@@ -153,7 +153,7 @@
 </histogram>
 
 <histogram name="PluginVm.EngagementTime.PluginVmTotal" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>timloh@google.com</owner>
   <owner>joelhockey@google.com</owner>
   <summary>
@@ -164,7 +164,7 @@
 </histogram>
 
 <histogram name="PluginVm.EngagementTime.Total" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>timloh@google.com</owner>
   <owner>joelhockey@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/power/histograms.xml b/tools/metrics/histograms/histograms_xml/power/histograms.xml
index 7eebea8..5f781a99 100644
--- a/tools/metrics/histograms/histograms_xml/power/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/power/histograms.xml
@@ -167,7 +167,7 @@
 </histogram>
 
 <histogram name="Power.BatteryRemainingAtEndOfSessionOnBattery" units="%"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>tbroch@chromium.org</owner>
   <summary>
     Chrome OS remaining battery charge as percent of the maximum battery charge,
@@ -176,7 +176,7 @@
 </histogram>
 
 <histogram name="Power.BatteryRemainingAtStartOfSessionOnAC" units="%"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>tbroch@chromium.org</owner>
   <summary>
     Chrome OS remaining battery charge as percent of the maximum battery charge,
@@ -235,7 +235,7 @@
 </histogram>
 
 <histogram name="Power.ConnectedChargingPorts"
-    enum="PowerConnectedChargingPorts" expires_after="2021-08-15">
+    enum="PowerConnectedChargingPorts" expires_after="2021-10-25">
   <owner>bleung@chromium.org</owner>
   <owner>tbroch@chromium.org</owner>
   <summary>
@@ -299,7 +299,7 @@
 </histogram>
 
 <histogram name="Power.CpuTimeSecondsPerProcessType" enum="ProcessType2"
-    expires_after="2021-10-17">
+    expires_after="2021-10-25">
   <owner>eseckler@chromium.org</owner>
   <owner>skyostil@chromium.org</owner>
   <summary>
@@ -790,7 +790,7 @@
 </histogram>
 
 <histogram name="Power.KernelSuspendTimeOnBattery" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>tbroch@chromium.org</owner>
   <summary>
     The time that the kernel took to suspend-to-RAM the Chrome OS device when
@@ -962,7 +962,7 @@
 </histogram>
 
 <histogram name="Power.PowerButtonPressInLaptopMode"
-    enum="PowerButtonPressType" expires_after="2021-08-22">
+    enum="PowerButtonPressType" expires_after="2021-10-25">
   <owner>minch@chromium.org</owner>
   <owner>xdai@chromium.org</owner>
   <summary>
@@ -1100,7 +1100,7 @@
 </histogram>
 
 <histogram name="Power.UserBrightnessAdjustmentsPerSessionOnBattery"
-    units="units" expires_after="2021-08-22">
+    units="units" expires_after="2021-10-25">
   <owner>tbroch@chromium.org</owner>
   <owner>napper@chromium.org</owner>
   <summary>
@@ -1131,7 +1131,7 @@
 </histogram>
 
 <histogram name="PowerML.ModelNoDim.Result" enum="PowerMLFinalResult"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>napper@chromium.org</owner>
   <owner>thanhdng@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/prefetch/histograms.xml b/tools/metrics/histograms/histograms_xml/prefetch/histograms.xml
index 83e9e68..80b67dbb 100644
--- a/tools/metrics/histograms/histograms_xml/prefetch/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/prefetch/histograms.xml
@@ -255,7 +255,7 @@
 </histogram>
 
 <histogram name="PrefetchProxy.AfterClick.Mainframe.CookieWaitTime" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>robertogden@chromium.org</owner>
   <owner>ryansturm@chromium.org</owner>
   <owner>tbansal@chromium.org</owner>
@@ -267,7 +267,7 @@
 </histogram>
 
 <histogram name="PrefetchProxy.AfterClick.Subresources.UsedCache"
-    enum="BooleanCacheHit" expires_after="2021-08-22">
+    enum="BooleanCacheHit" expires_after="2021-10-25">
   <owner>robertogden@chromium.org</owner>
   <owner>ryansturm@chromium.org</owner>
   <owner>tbansal@chromium.org</owner>
@@ -278,7 +278,7 @@
 </histogram>
 
 <histogram name="PrefetchProxy.Prefetch.Mainframe.BodyLength" units="bytes"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>robertogden@chromium.org</owner>
   <owner>ryansturm@chromium.org</owner>
   <owner>tbansal@chromium.org</owner>
@@ -302,7 +302,7 @@
 </histogram>
 
 <histogram name="PrefetchProxy.Prefetch.Mainframe.CookiesToCopy" units="count"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>robertogden@chromium.org</owner>
   <owner>ryansturm@chromium.org</owner>
   <owner>tbansal@chromium.org</owner>
@@ -372,7 +372,7 @@
 </histogram>
 
 <histogram name="PrefetchProxy.Prefetch.Subresources.NetError"
-    enum="NetErrorCodes" expires_after="2021-08-22">
+    enum="NetErrorCodes" expires_after="2021-10-25">
   <owner>robertogden@chromium.org</owner>
   <owner>ryansturm@chromium.org</owner>
   <owner>tbansal@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/printing/histograms.xml b/tools/metrics/histograms/histograms_xml/printing/histograms.xml
index 522c795..1942b43 100644
--- a/tools/metrics/histograms/histograms_xml/printing/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/printing/histograms.xml
@@ -435,7 +435,7 @@
 </histogram>
 
 <histogram name="Printing.PrintServers.ServersToQuery" units="servers"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>pawliczek@chromium.org</owner>
   <owner>skau@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/profile/histograms.xml b/tools/metrics/histograms/histograms_xml/profile/histograms.xml
index 9efe192..9e3e811d 100644
--- a/tools/metrics/histograms/histograms_xml/profile/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/profile/histograms.xml
@@ -376,7 +376,7 @@
 </histogram>
 
 <histogram name="Profile.NumberOfAccountsPerProfile" units="units"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>msarda@chromium.org</owner>
   <owner>droger@chromium.org</owner>
   <summary>
@@ -765,7 +765,7 @@
 </histogram>
 
 <histogram name="ProfileChooser.HasProfilesShown" enum="BooleanShown"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>vasilii@chromium.org</owner>
   <owner>ewald@chromium.org</owner>
   <summary>
@@ -775,7 +775,7 @@
 </histogram>
 
 <histogram name="ProfilePicker.AskOnStartup" enum="BooleanEnabled"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>msalama@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -786,7 +786,7 @@
 </histogram>
 
 <histogram name="ProfilePicker.AskOnStartupChanged" enum="BooleanEnabled"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>msalama@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -808,7 +808,7 @@
 </histogram>
 
 <histogram name="ProfilePicker.NewProfileTheme" enum="ChromeColorsInfo"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>msalama@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -818,7 +818,7 @@
 </histogram>
 
 <histogram name="ProfilePicker.Shown" enum="ProfilePickerEntryPoint"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>msalama@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -828,7 +828,7 @@
 </histogram>
 
 <histogram name="ProfilePicker.StartupTime.BeforeCreation" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>msalama@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -839,7 +839,7 @@
 </histogram>
 
 <histogram name="ProfilePicker.StartupTime.FirstPaint" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>msalama@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -849,7 +849,7 @@
 </histogram>
 
 <histogram name="ProfilePicker.StartupTime.FirstPaint.FromApplicationStart"
-    units="ms" expires_after="2021-08-22">
+    units="ms" expires_after="2021-10-25">
   <owner>alexilin@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -863,7 +863,7 @@
 </histogram>
 
 <histogram name="ProfilePicker.StartupTime.MainViewInitialized" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>msalama@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -873,7 +873,7 @@
 </histogram>
 
 <histogram name="ProfilePicker.StartupTime.WebViewCreated" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>msalama@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -883,7 +883,7 @@
 </histogram>
 
 <histogram name="ProfilePicker.UiVisited" enum="ProfilePickerPages"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>msalama@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -893,7 +893,7 @@
 </histogram>
 
 <histogram name="ProfilePicker.UserAction" enum="ProfilePickerAction"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>msalama@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/safe_browsing/histograms.xml b/tools/metrics/histograms/histograms_xml/safe_browsing/histograms.xml
index 2e148023..52d8e2e3 100644
--- a/tools/metrics/histograms/histograms_xml/safe_browsing/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/safe_browsing/histograms.xml
@@ -1049,7 +1049,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.ReferrerHasInvalidTabID" enum="BooleanInvalid"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -1060,7 +1060,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.ReferrerURLChainSize" units="units"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -1115,7 +1115,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.RT.CacheManager.RealTimeVerdictCount"
-    units="entries" expires_after="2021-08-22">
+    units="entries" expires_after="2021-10-25">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -1603,6 +1603,16 @@
   </summary>
 </histogram>
 
+<histogram name="SafeBrowsing.V4ProcessFullUpdate.ApplyUpdateDuration"
+    units="ms" expires_after="2022-04-27">
+  <owner>drubery@chromium.org</owner>
+  <owner>chrome-safebrowsing-alerts@google.com</owner>
+  <summary>
+    Records how long it took to perform a full update of a V4 store. This is
+    recorded in thread time, to avoid recording any disk operations.
+  </summary>
+</histogram>
+
 <histogram name="SafeBrowsing.V4ProcessFullUpdate.DecodeAdditions.Result"
     enum="SafeBrowsingV4DecodeResult" expires_after="never">
 <!-- expires-never: This reports the outcome of decoding the latest update
@@ -1649,6 +1659,16 @@
   </summary>
 </histogram>
 
+<histogram name="SafeBrowsing.V4ProcessPartialUpdate.ApplyUpdateDuration"
+    units="ms" expires_after="2022-04-27">
+  <owner>drubery@chromium.org</owner>
+  <owner>chrome-safebrowsing-alerts@google.com</owner>
+  <summary>
+    Records how long it took to perform a partial update of a V4 store. This is
+    recorded in thread time, to avoid recording any disk operations.
+  </summary>
+</histogram>
+
 <histogram name="SafeBrowsing.V4ProcessPartialUpdate.DecodeAdditions.Result"
     enum="SafeBrowsingV4DecodeResult" expires_after="never">
 <!-- expires-never: This reports the outcome of decoding the latest update
@@ -1722,6 +1742,16 @@
   </summary>
 </histogram>
 
+<histogram name="SafeBrowsing.V4ReadFromDisk.VerifyChecksumDuration" units="ms"
+    expires_after="2022-04-27">
+  <owner>drubery@chromium.org</owner>
+  <owner>chrome-safebrowsing-alerts@google.com</owner>
+  <summary>
+    Records how long it took to verify the checksum of a V4 store. This is
+    recorded in thread time, to avoid recording any disk operations.
+  </summary>
+</histogram>
+
 <histogram name="SafeBrowsing.V4Store.IsStoreValid" enum="BooleanValid"
     expires_after="2021-08-05">
   <owner>vakh@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/search/histograms.xml b/tools/metrics/histograms/histograms_xml/search/histograms.xml
index d12f5a1..0d03025 100644
--- a/tools/metrics/histograms/histograms_xml/search/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/search/histograms.xml
@@ -260,7 +260,7 @@
 </histogram>
 
 <histogram name="Search.ContextualSearch.TranslationNeeded"
-    enum="ContextualSearchGestureIsTap" expires_after="2021-08-22">
+    enum="ContextualSearchGestureIsTap" expires_after="2021-10-25">
   <owner>donnd@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
@@ -284,7 +284,7 @@
 </histogram>
 
 <histogram name="Search.ContextualSearch.TranslationsOptInIPHWorked"
-    enum="BooleanOptedIn" expires_after="2021-08-22">
+    enum="BooleanOptedIn" expires_after="2021-10-25">
   <owner>donnd@chromium.org</owner>
   <owner>contextual-search-eng@google.com</owner>
   <summary>
@@ -1281,7 +1281,7 @@
 </histogram>
 
 <histogram name="Search.QueryTiles.FetcherHttpResponseCode"
-    enum="HttpResponseCode" expires_after="2021-08-22">
+    enum="HttpResponseCode" expires_after="2021-10-25">
   <owner>hesen@chromium.org</owner>
   <owner>chrome-upboarding-eng@google.com</owner>
   <summary>Records the HTTP response code get from TileFetcher.</summary>
diff --git a/tools/metrics/histograms/histograms_xml/security/histograms.xml b/tools/metrics/histograms/histograms_xml/security/histograms.xml
index 517bc58..39c8fdb 100644
--- a/tools/metrics/histograms/histograms_xml/security/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/security/histograms.xml
@@ -99,7 +99,7 @@
 </histogram>
 
 <histogram name="Security.MixedForm.InterstitialTriggerState"
-    enum="MixedFormInterstitialTriggeredState" expires_after="2021-08-22">
+    enum="MixedFormInterstitialTriggeredState" expires_after="2021-10-25">
   <owner>carlosil@chromium.org</owner>
   <owner>security-enamel@chromium.org</owner>
   <summary>
@@ -237,7 +237,7 @@
 </histogram>
 
 <histogram name="Security.SafetyTips.DownloadStarted" enum="SafetyTipStatus"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>jdeblasio@chromium.org</owner>
   <owner>estark@chromium.org</owner>
   <summary>
@@ -250,7 +250,7 @@
 </histogram>
 
 <histogram name="Security.SafetyTips.FormSubmission" enum="SafetyTipStatus"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>jdeblasio@chromium.org</owner>
   <owner>estark@chromium.org</owner>
   <summary>
@@ -263,7 +263,7 @@
 </histogram>
 
 <histogram name="Security.SafetyTips.Interaction" enum="SafetyTipInteraction"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>jdeblasio@chromium.org</owner>
   <owner>estark@chromium.org</owner>
   <summary>
@@ -285,7 +285,7 @@
 </histogram>
 
 <histogram name="Security.SafetyTips.OpenTime.Dismiss" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>jdeblasio@chromium.org</owner>
   <owner>estark@chromium.org</owner>
   <summary>
@@ -296,7 +296,7 @@
 </histogram>
 
 <histogram name="Security.SafetyTips.OpenTime.DismissWithClose" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>jdeblasio@chromium.org</owner>
   <owner>estark@chromium.org</owner>
   <owner>livvielin@chromium.org</owner>
@@ -332,7 +332,7 @@
 </histogram>
 
 <histogram name="Security.SafetyTips.OpenTime.LeaveSite" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>jdeblasio@chromium.org</owner>
   <owner>estark@chromium.org</owner>
   <summary>
@@ -342,7 +342,7 @@
 </histogram>
 
 <histogram name="Security.SafetyTips.OpenTime.NoAction" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>jdeblasio@chromium.org</owner>
   <owner>estark@chromium.org</owner>
   <summary>
@@ -382,7 +382,7 @@
 </histogram>
 
 <histogram base="true" name="Security.SafetyTips.ReputationCheckComplete"
-    enum="SafetyTipStatus" expires_after="2021-08-22">
+    enum="SafetyTipStatus" expires_after="2021-10-25">
   <owner>jdeblasio@chromium.org</owner>
   <owner>src/chrome/browser/reputation/OWNERS</owner>
   <summary>
@@ -393,7 +393,7 @@
 </histogram>
 
 <histogram name="Security.SafetyTips.SafetyTipIgnoredPageLoad"
-    enum="SafetyTipStatus" expires_after="2021-08-22">
+    enum="SafetyTipStatus" expires_after="2021-10-25">
   <owner>jdeblasio@chromium.org</owner>
   <owner>livvielin@chromium.org</owner>
   <summary>
@@ -403,7 +403,7 @@
 </histogram>
 
 <histogram name="Security.SafetyTips.SafetyTipShown" enum="SafetyTipStatus"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>jdeblasio@chromium.org</owner>
   <owner>livvielin@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/service/histograms.xml b/tools/metrics/histograms/histograms_xml/service/histograms.xml
index bb1d4a6..9427972 100644
--- a/tools/metrics/histograms/histograms_xml/service/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/service/histograms.xml
@@ -44,7 +44,7 @@
 </histogram>
 
 <histogram name="ServiceWorker.ActivateEventStatus"
-    enum="ServiceWorkerStatusCode" expires_after="2021-08-22">
+    enum="ServiceWorkerStatusCode" expires_after="2021-10-25">
   <owner>falken@chromium.org</owner>
   <owner>chrome-worker@google.com</owner>
   <summary>
@@ -681,7 +681,7 @@
 </histogram>
 
 <histogram name="ServiceWorker.PageLoad" enum="ServiceWorkerSite"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>falken@chromium.org</owner>
   <owner>chrome-worker@google.com</owner>
   <summary>Counts page loads controlled by a service worker.</summary>
@@ -1221,7 +1221,7 @@
 </histogram>
 
 <histogram name="ServiceWorkerCache.ErrorStorageType"
-    enum="CacheStorageErrorStorageType" expires_after="2021-08-22">
+    enum="CacheStorageErrorStorageType" expires_after="2021-10-25">
   <owner>wanderview@chromium.org</owner>
   <owner>chrome-owp-storage@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/settings/histograms.xml b/tools/metrics/histograms/histograms_xml/settings/histograms.xml
index cb39e92..140ac80 100644
--- a/tools/metrics/histograms/histograms_xml/settings/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/settings/histograms.xml
@@ -160,7 +160,7 @@
 </histogram>
 
 <histogram name="Settings.PrivacyElementInteractions"
-    enum="SettingsPrivacyElementInteractions" expires_after="2021-08-22">
+    enum="SettingsPrivacyElementInteractions" expires_after="2021-10-25">
   <owner>harrisonsean@chromium.org</owner>
   <owner>msramek@chromium.org</owner>
   <owner>sauski@chromium.org</owner>
@@ -212,7 +212,7 @@
 </histogram>
 
 <histogram name="Settings.SafetyCheck.Interactions"
-    enum="SettingsSafetyCheckInteractions" expires_after="2021-08-22">
+    enum="SettingsSafetyCheckInteractions" expires_after="2021-10-25">
   <owner>rainhard@chromium.org</owner>
   <owner>msramek@chromium.org</owner>
   <owner>anaudrey@chromium.org</owner>
@@ -224,21 +224,21 @@
 </histogram>
 
 <histogram name="Settings.SafetyCheck.PasswordsResult"
-    enum="SafetyCheckPasswordsStatus" expires_after="2021-08-22">
+    enum="SafetyCheckPasswordsStatus" expires_after="2021-10-25">
   <owner>andzaytsev@google.com</owner>
   <owner>msramek@chromium.org</owner>
   <summary>Resulting state of the safety check password check.</summary>
 </histogram>
 
 <histogram name="Settings.SafetyCheck.SafeBrowsingResult"
-    enum="SafetyCheckSafeBrowsingStatus" expires_after="2021-08-22">
+    enum="SafetyCheckSafeBrowsingStatus" expires_after="2021-10-25">
   <owner>andzaytsev@google.com</owner>
   <owner>msramek@chromium.org</owner>
   <summary>Resulting state of the safety check Safe Browsing check.</summary>
 </histogram>
 
 <histogram name="Settings.SafetyCheck.UpdatesResult"
-    enum="SafetyCheckUpdateStatus" expires_after="2021-08-22">
+    enum="SafetyCheckUpdateStatus" expires_after="2021-10-25">
   <owner>andzaytsev@google.com</owner>
   <owner>msramek@chromium.org</owner>
   <summary>Resulting state of the safety check updates check.</summary>
@@ -485,7 +485,7 @@
 </histogram>
 
 <histogram name="Settings.TrackedPreferenceUnchanged" enum="TrackedPreference"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>proberge@chromium.org</owner>
   <summary>
     The id of a tracked preference whose value has not changed since the last
diff --git a/tools/metrics/histograms/histograms_xml/sharing/histograms.xml b/tools/metrics/histograms/histograms_xml/sharing/histograms.xml
index 799e550d..4e119e1 100644
--- a/tools/metrics/histograms/histograms_xml/sharing/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/sharing/histograms.xml
@@ -22,7 +22,7 @@
 <histograms>
 
 <histogram name="Sharing.ClickToCallAppsToShow" units="apps"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
 <!-- Name completed by histogram_suffixes name="SharingClickToCallUi" -->
 
   <owner>mvanouwerkerk@chromium.org</owner>
@@ -57,7 +57,7 @@
 </histogram>
 
 <histogram name="Sharing.ClickToCallDialogShown" enum="SharingDialogType"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>mvanouwerkerk@chromium.org</owner>
   <owner>knollr@chromium.org</owner>
   <summary>
@@ -103,7 +103,7 @@
 </histogram>
 
 <histogram name="Sharing.ClickToCallSelectedDeviceIndex" units="index"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
 <!-- Name completed by histogram_suffixes name="SharingClickToCallUi" -->
 
   <owner>mvanouwerkerk@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/signin/histograms.xml b/tools/metrics/histograms/histograms_xml/signin/histograms.xml
index ed7ab6ad..fc475250 100644
--- a/tools/metrics/histograms/histograms_xml/signin/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/signin/histograms.xml
@@ -443,7 +443,7 @@
 </histogram>
 
 <histogram name="Signin.GetAccessTokenFinished" enum="GoogleServiceAuthError"
-    expires_after="2021-04-19">
+    expires_after="2022-04-19">
   <owner>droger@chromium.org</owner>
   <owner>msarda@chromium.org</owner>
   <summary>
@@ -696,7 +696,7 @@
 </histogram>
 
 <histogram name="Signin.LoadedIdentities.Count" units="identities"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>jlebel@chromium.org</owner>
   <owner>msarda@chromium.org</owner>
   <summary>
@@ -708,7 +708,7 @@
 </histogram>
 
 <histogram name="Signin.LoadedIdentities.Duration" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>jlebel@chromium.org</owner>
   <owner>msarda@chromium.org</owner>
   <summary>
@@ -720,7 +720,7 @@
 </histogram>
 
 <histogram name="Signin.LoadedIdentities.DurationPerIdentity" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>jlebel@chromium.org</owner>
   <owner>msarda@chromium.org</owner>
   <summary>
@@ -1041,7 +1041,7 @@
 </histogram>
 
 <histogram name="Signin.SignedInAccountsViewImpression" enum="BooleanShown"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>fernandex@chromium.org</owner>
   <owner>jlebel@chromium.org</owner>
   <owner>msarda@chromium.org</owner>
@@ -1122,7 +1122,7 @@
 </histogram>
 
 <histogram name="Signin.SSOAuth.GetIdentities.ErrorCode"
-    enum="SigninSSOAuthGetIdentitiesErrorCode" expires_after="2021-08-22">
+    enum="SigninSSOAuthGetIdentitiesErrorCode" expires_after="2021-10-25">
   <owner>jlebel@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -1132,7 +1132,7 @@
 </histogram>
 
 <histogram base="true" name="Signin.SSOIdentityListRequest.CacheState"
-    enum="SigninSSOIdentityListRequestCacheState" expires_after="2021-08-22">
+    enum="SigninSSOIdentityListRequestCacheState" expires_after="2021-10-25">
 <!-- Name completed by histogram_suffixes name="SigninSSOIdentityListRequestCacheRequestState" -->
 
   <owner>jlebel@chromium.org</owner>
@@ -1146,7 +1146,7 @@
 </histogram>
 
 <histogram base="true" name="Signin.SSOIdentityListRequest.Duration" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
 <!-- Name completed by histogram_suffixes name="SigninSSOIdentityListRequestDurationCacheState" -->
 
   <owner>jlebel@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/stability/histograms.xml b/tools/metrics/histograms/histograms_xml/stability/histograms.xml
index 8907fba..c75ada28 100644
--- a/tools/metrics/histograms/histograms_xml/stability/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/stability/histograms.xml
@@ -602,7 +602,7 @@
 </histogram>
 
 <histogram name="Stability.MobileSessionShutdownType"
-    enum="MobileSessionShutdownType" expires_after="2021-08-22">
+    enum="MobileSessionShutdownType" expires_after="2021-10-25">
   <owner>michaeldo@chromium.org</owner>
   <owner>olivierrobin@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/startup/histograms.xml b/tools/metrics/histograms/histograms_xml/startup/histograms.xml
index 1553d084..5a738e0 100644
--- a/tools/metrics/histograms/histograms_xml/startup/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/startup/histograms.xml
@@ -199,7 +199,7 @@
 </histogram>
 
 <histogram name="Startup.Android.StartupTabPreloader.TabLoaded" units="Boolean"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>skyostil@chromium.org</owner>
   <summary>
     Android: Whether or not creation of a profile lead to the
@@ -234,7 +234,7 @@
 </histogram>
 
 <histogram name="Startup.Android.TimeToGTSFirstMeaningfulPaint" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>yusufo@chromium.org</owner>
   <owner>yfriedman@chromium.org</owner>
   <summary>
@@ -260,7 +260,7 @@
 </histogram>
 
 <histogram name="Startup.Android.TimeToGTSFirstMeaningfulPaint.Warm" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>yusufo@chromium.org</owner>
   <owner>yfriedman@chromium.org</owner>
   <summary>
@@ -692,7 +692,7 @@
 </histogram>
 
 <histogram name="Startup.PreMainMessageLoopRunImplLongTime" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>rkaplow@chromium.org</owner>
   <summary>
     The amount of time that elapsed during
diff --git a/tools/metrics/histograms/histograms_xml/subresource/histograms.xml b/tools/metrics/histograms/histograms_xml/subresource/histograms.xml
index d10a000e..66d2a1b8 100644
--- a/tools/metrics/histograms/histograms_xml/subresource/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/subresource/histograms.xml
@@ -670,7 +670,7 @@
 </histogram>
 
 <histogram name="SubresourceRedirect.CompressionAttempt.ResponseCode"
-    enum="HttpResponseCode" expires_after="2021-08-22">
+    enum="HttpResponseCode" expires_after="2021-10-25">
   <owner>robertogden@chromium.org</owner>
   <owner>tbansal@chromium.org</owner>
   <summary>
@@ -723,7 +723,7 @@
 </histogram>
 
 <histogram name="SubresourceRedirect.ImageCompressionNotificationInfoBar"
-    enum="HttpsImageCompressionInfoBarAction" expires_after="2021-08-09">
+    enum="HttpsImageCompressionInfoBarAction" expires_after="2021-10-25">
   <owner>rajendrant@chromium.org</owner>
   <owner>src/components/data_reduction_proxy/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/sync/histograms.xml b/tools/metrics/histograms/histograms_xml/sync/histograms.xml
index f0372b1..085f11b 100644
--- a/tools/metrics/histograms/histograms_xml/sync/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/sync/histograms.xml
@@ -108,7 +108,7 @@
 </histogram>
 
 <histogram name="Sync.BookmarksGUIDDuplicates" enum="BookmarksGUIDDuplicates"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>mastiz@chromium.org</owner>
   <owner>rushans@google.com</owner>
   <component>Services&gt;Sync</component>
@@ -855,7 +855,7 @@
 </histogram>
 
 <histogram name="Sync.PassphraseType" enum="SyncPassphraseType"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>treib@chromium.org</owner>
   <owner>mastiz@chromium.org</owner>
   <component>Services&gt;Sync</component>
@@ -1059,7 +1059,7 @@
 </histogram>
 
 <histogram name="Sync.ProblematicServerSideBookmarks"
-    enum="RemoteBookmarkUpdateError" expires_after="2021-08-22">
+    enum="RemoteBookmarkUpdateError" expires_after="2021-10-25">
   <owner>mastiz@chromium.org</owner>
   <owner>rushans@google.com</owner>
   <component>Services&gt;Sync</component>
@@ -1070,7 +1070,7 @@
 </histogram>
 
 <histogram name="Sync.ProblematicServerSideBookmarksDuringMerge"
-    enum="RemoteBookmarkUpdateError" expires_after="2021-08-22">
+    enum="RemoteBookmarkUpdateError" expires_after="2021-10-25">
   <owner>mastiz@chromium.org</owner>
   <owner>rushans@google.com</owner>
   <component>Services&gt;Sync</component>
@@ -1255,7 +1255,7 @@
 </histogram>
 
 <histogram name="Sync.SyncErrorInfobarDisplayed" enum="SyncErrorInfobarTypes"
-    expires_after="2021-08-24">
+    expires_after="2021-10-25">
   <owner>fernandex@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <component>Services&gt;Sync</component>
diff --git a/tools/metrics/histograms/histograms_xml/tab/histograms.xml b/tools/metrics/histograms/histograms_xml/tab/histograms.xml
index e01045f..fc18dcb 100644
--- a/tools/metrics/histograms/histograms_xml/tab/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/tab/histograms.xml
@@ -449,7 +449,7 @@
 </histogram>
 
 <histogram name="Tab.Screenshot.ScreenshotsPerPage" units="screenshots"
-    expires_after="2021-08-24">
+    expires_after="2021-10-25">
   <owner>skare@chromium.org</owner>
   <summary>
     Records the number of screenshots taken of a specific page. It is recorded
@@ -1920,7 +1920,7 @@
   </summary>
 </histogram>
 
-<histogram name="Tabs.ScrubDistance" units="tabs" expires_after="2021-08-22">
+<histogram name="Tabs.ScrubDistance" units="tabs" expires_after="2021-10-25">
   <owner>afakhry@chromium.org</owner>
   <owner>tclaiborne@chromium.org</owner>
   <summary>
@@ -2232,7 +2232,7 @@
 </histogram>
 
 <histogram name="Tabs.TabSearch.CloseAction" enum="TabSearchCloseActions"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>tluk@chromium.org</owner>
   <owner>robliao@chromium.org</owner>
   <summary>
@@ -2449,7 +2449,7 @@
 </histogram>
 
 <histogram name="Tabs.TabSearch.WindowTimeToShowCachedWebView" units="ms"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>tluk@chromium.org</owner>
   <owner>robliao@chromium.org</owner>
   <owner>yuhengh@chromium.org</owner>
@@ -2775,7 +2775,7 @@
   <token key="BatteryState" variants="BatteryState"/>
 </histogram>
 
-<histogram name="Tabs.WindowWidth" units="DIPs" expires_after="2021-08-22">
+<histogram name="Tabs.WindowWidth" units="DIPs" expires_after="2021-10-25">
   <owner>collinbaker@chromium.org</owner>
   <owner>chrome-desktop-ui-sea@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/translate/histograms.xml b/tools/metrics/histograms/histograms_xml/translate/histograms.xml
index 5209175e..2df2975a 100644
--- a/tools/metrics/histograms/histograms_xml/translate/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/translate/histograms.xml
@@ -760,7 +760,7 @@
 </histogram>
 
 <histogram name="Translate.Ranker.Model.Status" enum="RankerModelStatus"
-    expires_after="2021-08-15">
+    expires_after="2021-10-25">
   <owner>rogerm@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -988,7 +988,7 @@
 </histogram>
 
 <histogram name="Translate.TranslateAssistContentResult"
-    enum="TranslateAssistContentResult" expires_after="2021-08-22">
+    enum="TranslateAssistContentResult" expires_after="2021-10-25">
   <owner>jds@chromium.org</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/uma/histograms.xml b/tools/metrics/histograms/histograms_xml/uma/histograms.xml
index 57772b4..cd9147d0 100644
--- a/tools/metrics/histograms/histograms_xml/uma/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/uma/histograms.xml
@@ -43,7 +43,7 @@
 </variants>
 
 <histogram name="UMA.ActualLogUploadInterval" units="minutes"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>asvitkine@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
   <summary>
@@ -298,7 +298,7 @@
 </histogram>
 
 <histogram name="UMA.LowEntropySource3Value" units="units"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>asvitkine@chromium.org</owner>
   <owner>mpearson@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
@@ -378,7 +378,7 @@
 </histogram>
 
 <histogram name="UMA.NegativeSamples.Histogram" enum="HistogramNameHash"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>asvitkine@chromium.org</owner>
   <owner>bcwhite@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
@@ -589,7 +589,7 @@
 </histogram>
 
 <histogram name="UMA.TruncatedEvents.UserAction" units="events"
-    expires_after="2021-10-17">
+    expires_after="2021-10-25">
   <owner>rkaplow@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
   <summary>
@@ -701,7 +701,7 @@
 </histogram>
 
 <histogram name="UMA.UserDemographics.Status" enum="UserDemographicsStatus"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>vincb@google.com</owner>
   <owner>src/base/metrics/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/v8/histograms.xml b/tools/metrics/histograms/histograms_xml/v8/histograms.xml
index 0c7d9c6..e4e47ff 100644
--- a/tools/metrics/histograms/histograms_xml/v8/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/v8/histograms.xml
@@ -80,7 +80,7 @@
 </histogram>
 
 <histogram name="V8.CompileDeserializeMicroSeconds" units="microseconds"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>vogelheim@chromium.org</owner>
   <summary>
     Time spent deseriailzing code, used by V8 code caching.
diff --git a/tools/metrics/histograms/histograms_xml/variations/histograms.xml b/tools/metrics/histograms/histograms_xml/variations/histograms.xml
index 03efc34..cd4b87c3 100644
--- a/tools/metrics/histograms/histograms_xml/variations/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/variations/histograms.xml
@@ -109,7 +109,7 @@
 </histogram>
 
 <histogram name="Variations.FirstRunResult" enum="VariationsFirstRunResult"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>asvitkine@chromium.org</owner>
   <owner>rkaplow@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
@@ -236,7 +236,7 @@
 </histogram>
 
 <histogram name="Variations.ResourceRequestsAllowed"
-    enum="VariationsResourceRequestsAllowedState" expires_after="2021-08-22">
+    enum="VariationsResourceRequestsAllowedState" expires_after="2021-10-25">
   <owner>asvitkine@chromium.org</owner>
   <owner>rkaplow@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
@@ -248,7 +248,7 @@
 </histogram>
 
 <histogram name="Variations.SafeMode.FellBackToSafeMode2"
-    enum="BooleanSafeMode" expires_after="2021-08-22">
+    enum="BooleanSafeMode" expires_after="2021-10-25">
   <owner>isherman@chromium.org</owner>
   <owner>asvitkine@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
@@ -307,7 +307,7 @@
 </histogram>
 
 <histogram name="Variations.SafeMode.Streak.Crashes" units="crashes"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>isherman@chromium.org</owner>
   <owner>asvitkine@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
@@ -331,7 +331,7 @@
 </histogram>
 
 <histogram name="Variations.SeedDateChange" enum="VariationsSeedDateChange"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>jwd@chromium.org</owner>
   <owner>asvitkine@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
@@ -345,7 +345,7 @@
 </histogram>
 
 <histogram name="Variations.SeedFetchResponseOrErrorCode"
-    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2021-08-22">
+    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2021-10-25">
   <owner>asvitkine@chromium.org</owner>
   <owner>rkaplow@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
@@ -357,7 +357,7 @@
 </histogram>
 
 <histogram name="Variations.SeedFetchResponseOrErrorCode.HTTP"
-    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2021-08-22">
+    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2021-10-25">
   <owner>asvitkine@chromium.org</owner>
   <owner>rkaplow@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
@@ -419,7 +419,7 @@
 </histogram>
 
 <histogram name="Variations.SeedStoreResult" enum="VariationsSeedStoreResult"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>asvitkine@chromium.org</owner>
   <owner>rkaplow@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
@@ -484,7 +484,7 @@
 </histogram>
 
 <histogram name="Variations.StoreSeed.DataSize" units="KiB"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>asvitkine@chromium.org</owner>
   <owner>rkaplow@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
@@ -529,7 +529,7 @@
 </histogram>
 
 <histogram name="Variations.UserChannel" enum="UserChannels"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>asvitkine@chromium.org</owner>
   <owner>rkaplow@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
diff --git a/tools/metrics/histograms/histograms_xml/web_audio/histograms.xml b/tools/metrics/histograms/histograms_xml/web_audio/histograms.xml
index 99f913f..0c1777e 100644
--- a/tools/metrics/histograms/histograms_xml/web_audio/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/web_audio/histograms.xml
@@ -42,7 +42,7 @@
 </histogram>
 
 <histogram name="WebAudio.AudioBuffer.SampleRate384kHz" units="Hz"
-    expires_after="2021-08-22">
+    expires_after="2021-10-25">
   <owner>rtoy@chromium.org</owner>
   <owner>hongchan@chromium.org</owner>
   <summary>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index b459e1b..ccec1c0f7 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -1,16 +1,16 @@
 {
     "trace_processor_shell": {
         "win": {
-            "hash": "0de6582e17aa4582d700e243c3c5ed55174545d3",
-            "remote_path": "perfetto_binaries/trace_processor_shell/win/e01ad9a6a83bd8df88dc01bc86fd0ec9080296c7/trace_processor_shell.exe"
+            "hash": "420daabcb280c424a32d12a86da6dfb0bee3040f",
+            "remote_path": "perfetto_binaries/trace_processor_shell/win/eabc2f4aab92456471eaea253c0d6d9cc2bb5b3b/trace_processor_shell.exe"
         },
         "mac": {
             "hash": "ae04e80011f5918c2c3ec673c3c553d8a4698f18",
             "remote_path": "perfetto_binaries/trace_processor_shell/mac/e01ad9a6a83bd8df88dc01bc86fd0ec9080296c7/trace_processor_shell"
         },
         "linux": {
-            "hash": "38a7401903f838271fc4cb6cfff67c90f25c2176",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/14c4d4957d5e2cb7c6fd4d6b8a3f51318c1b22da/trace_processor_shell"
+            "hash": "cbb9056ab2d4559ed050b53b175b17370db1429c",
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/ee1e7359d1d243b3f4059a659c2bb0684c2b3fd0/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/ui/accessibility/ax_assistant_structure.cc b/ui/accessibility/ax_assistant_structure.cc
index ffc101c..d3b95fa 100644
--- a/ui/accessibility/ax_assistant_structure.cc
+++ b/ui/accessibility/ax_assistant_structure.cc
@@ -35,13 +35,8 @@
 }
 
 // TODO(muyuanli): share with BrowserAccessibility.
-bool IsSimpleTextControl(const AXNode* node, uint32_t state) {
-  return (node->data().role == ax::mojom::Role::kTextField ||
-          node->data().role == ax::mojom::Role::kTextFieldWithComboBox ||
-          node->data().role == ax::mojom::Role::kSearchBox ||
-          node->data().HasBoolAttribute(
-              ax::mojom::BoolAttribute::kEditableRoot)) &&
-         !node->data().HasState(ax::mojom::State::kRichlyEditable);
+bool IsTextField(const AXNode* node, uint32_t state) {
+  return node->data().IsTextField();
 }
 
 bool IsRichTextEditable(const AXNode* node) {
@@ -51,7 +46,7 @@
           !parent->data().HasState(ax::mojom::State::kRichlyEditable));
 }
 
-bool IsNativeTextControl(const AXNode* node) {
+bool IsAtomicTextField(const AXNode* node) {
   const std::string& html_tag =
       node->data().GetStringAttribute(ax::mojom::StringAttribute::kHtmlTag);
   if (html_tag == "input") {
@@ -70,7 +65,7 @@
   if (node->children().empty())
     return true;
 
-  if (IsNativeTextControl(node) || node->IsText()) {
+  if (IsAtomicTextField(node) || node->IsText()) {
     return true;
   }
 
@@ -107,9 +102,8 @@
       node->data().GetString16Attribute(ax::mojom::StringAttribute::kValue);
 
   if (value.empty() &&
-      (IsSimpleTextControl(node, node->data().state) ||
-       IsRichTextEditable(node)) &&
-      !IsNativeTextControl(node)) {
+      (IsTextField(node, node->data().state) || IsRichTextEditable(node)) &&
+      !IsAtomicTextField(node)) {
     value = GetInnerText(node);
   }
 
diff --git a/ui/accessibility/ax_enum_util.cc b/ui/accessibility/ax_enum_util.cc
index 2ae78da..4c028fe 100644
--- a/ui/accessibility/ax_enum_util.cc
+++ b/ui/accessibility/ax_enum_util.cc
@@ -967,8 +967,8 @@
       return "none";
     case ax::mojom::BoolAttribute::kBusy:
       return "busy";
-    case ax::mojom::BoolAttribute::kEditableRoot:
-      return "editableRoot";
+    case ax::mojom::BoolAttribute::kContentEditableRoot:
+      return "contentEditableRoot";
     case ax::mojom::BoolAttribute::kContainerLiveAtomic:
       return "containerLiveAtomic";
     case ax::mojom::BoolAttribute::kContainerLiveBusy:
diff --git a/ui/accessibility/ax_enums.mojom b/ui/accessibility/ax_enums.mojom
index 3bc034ec..75aac2e0 100644
--- a/ui/accessibility/ax_enums.mojom
+++ b/ui/accessibility/ax_enums.mojom
@@ -706,9 +706,9 @@
   // Generic busy state, does not have to be on a live region.
   kBusy,
 
-  // The object is at the root of an editable field, such as a content
-  // editable.
-  kEditableRoot,
+  // The object is at the root of a content editable region, or at a <body>
+  // element that has "design-mode" set to "on".
+  kContentEditableRoot,
 
   // Live region attributes.
   kContainerLiveAtomic,
diff --git a/ui/accessibility/ax_event_generator_unittest.cc b/ui/accessibility/ax_event_generator_unittest.cc
index e612489e..9ed23892 100644
--- a/ui/accessibility/ax_event_generator_unittest.cc
+++ b/ui/accessibility/ax_event_generator_unittest.cc
@@ -523,7 +523,6 @@
   text_field.role = ax::mojom::Role::kTextField;
   text_field.SetValue("Testing");
   text_field.AddState(ax::mojom::State::kEditable);
-  text_field.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot, true);
 
   root.child_ids = {text_field.id};
 
@@ -615,7 +614,6 @@
   text_field.id = 1;
   text_field.role = ax::mojom::Role::kTextField;
   text_field.AddState(ax::mojom::State::kEditable);
-  text_field.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot, true);
   text_field.SetValue("Before");
 
   AXTreeUpdate initial_state;
@@ -665,7 +663,6 @@
   text_field.id = 1;
   text_field.role = ax::mojom::Role::kTextField;
   text_field.AddState(ax::mojom::State::kEditable);
-  text_field.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot, true);
   text_field.AddStringAttribute(ax::mojom::StringAttribute::kValue, "Text");
 
   AXTreeUpdate initial_state;
@@ -2442,7 +2439,6 @@
   text_field.id = 1;
   text_field.role = ax::mojom::Role::kTextField;
   text_field.AddState(ax::mojom::State::kEditable);
-  text_field.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot, true);
 
   AXTreeUpdate initial_state;
   initial_state.root_id = text_field.id;
@@ -2654,7 +2650,6 @@
   text_field.id = 2;
   text_field.role = ax::mojom::Role::kTextField;
   text_field.AddState(ax::mojom::State::kEditable);
-  text_field.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot, true);
   text_field.SetValue("Before");
   root.child_ids = {text_field.id};
 
diff --git a/ui/accessibility/ax_node.cc b/ui/accessibility/ax_node.cc
index fee36a5b..81aeb210 100644
--- a/ui/accessibility/ax_node.cc
+++ b/ui/accessibility/ax_node.cc
@@ -690,9 +690,9 @@
   // value or its placeholder. Otherwise we prefer to look at its descendant
   // text nodes because Blink doesn't always add all trailing white space to the
   // value attribute.
-  const bool is_plain_text_field_without_descendants =
+  const bool is_atomic_text_field_without_descendants =
       (node->data().IsTextField() && !node->GetUnignoredChildCount());
-  if (is_plain_text_field_without_descendants) {
+  if (is_atomic_text_field_without_descendants) {
     std::string value =
         node->data().GetStringAttribute(ax::mojom::StringAttribute::kValue);
     // If the value is empty, then there might be some placeholder text in the
@@ -702,13 +702,13 @@
       return value;
   }
 
-  // Ordinarily, plain text fields are leaves. We need to exclude them from the
+  // Ordinarily, atomic text fields are leaves. We need to exclude them from the
   // set of leaf nodes when they expose any descendants. This is because we want
   // to compute their inner text from their descendant text nodes as we don't
   // always trust the "value" attribute provided by Blink.
-  const bool is_plain_text_field_with_descendants =
+  const bool is_atomic_text_field_with_descendants =
       (node->data().IsTextField() && node->GetUnignoredChildCount());
-  if (node->IsLeaf() && !is_plain_text_field_with_descendants) {
+  if (node->IsLeaf() && !is_atomic_text_field_with_descendants) {
     switch (node->data().GetNameFrom()) {
       case ax::mojom::NameFrom::kNone:
       case ax::mojom::NameFrom::kUninitialized:
@@ -767,12 +767,12 @@
     DCHECK(node) << "All child trees should have a non-null rootnode.";
   }
 
-  const bool is_plain_text_field_with_descendants =
+  const bool is_atomic_text_field_with_descendants =
       (node->data().IsTextField() && node->GetUnignoredChildCount());
-  // Plain text fields are always leaves so we need to exclude them when
+  // Atomic text fields are always leaves so we need to exclude them when
   // computing the length of their inner text if that text should be derived
   // from their descendant nodes.
-  if (node->IsLeaf() && !is_plain_text_field_with_descendants)
+  if (node->IsLeaf() && !is_atomic_text_field_with_descendants)
     return int{node->GetInnerText().length()};
 
   int inner_text_length = 0;
@@ -1392,7 +1392,7 @@
   // Some screen readers like Jaws and VoiceOver require a value to be set in
   // text fields with rich content, even though the same information is
   // available on the children.
-  if (value.empty() && data().IsNonNativeTextField())
+  if (value.empty() && data().IsNonAtomicTextField())
     return GetInnerText();
   return value;
 }
@@ -1483,7 +1483,7 @@
   // different return values here, even though 'contenteditable' has no effect.
   // This needs to be modified from the Blink side, so 'kRichlyEditable' isn't
   // added in this case.
-  if (data().IsNativeTextField() || IsText())
+  if (data().IsAtomicTextField() || IsText())
     return true;
 
   // Roles whose children are only presentational according to the ARIA and
@@ -1621,9 +1621,9 @@
   return nullptr;
 }
 
-bool AXNode::IsDescendantOfNativeTextField() const {
+bool AXNode::IsDescendantOfAtomicTextField() const {
   AXNode* text_field_node = GetTextFieldAncestor();
-  return text_field_node && text_field_node->data().IsNativeTextField();
+  return text_field_node && text_field_node->data().IsAtomicTextField();
 }
 
 }  // namespace ui
diff --git a/ui/accessibility/ax_node.h b/ui/accessibility/ax_node.h
index 00b9c7a3b..7843c8b5 100644
--- a/ui/accessibility/ax_node.h
+++ b/ui/accessibility/ax_node.h
@@ -376,14 +376,14 @@
   // Returns empty string if no appropriate language was found.
   std::string GetLanguage() const;
 
-  // Returns the value of a control such as a plain text field, a content
-  // editable, a submit button, a slider, a progress bar, a scroll bar, a meter,
-  // a spinner, a <select> element, a date picker or an ARIA combo box. In order
-  // to minimize cross-process communication between the renderer and the
-  // browser, this method may compute the value from the control's inner text in
-  // the case of a content editable. For range controls, such as sliders and
-  // scroll bars, the value of aria-valuetext takes priority over the value of
-  // aria-valuenow.
+  // Returns the value of a control such as an atomic text field (<input> or
+  // <textarea>), a content editable, a submit button, a slider, a progress bar,
+  // a scroll bar, a meter, a spinner, a <select> element, a date picker or an
+  // ARIA combo box. In order to minimize cross-process communication between
+  // the renderer and the browser, this method may compute the value from the
+  // control's inner text in the case of a content editable. For range controls,
+  // such as sliders and scroll bars, the value of aria-valuetext takes priority
+  // over the value of aria-valuenow.
   std::string GetValueForControl() const;
 
   //
@@ -514,10 +514,10 @@
   // layer.
   //
   // The definition of a leaf includes nodes with children that are exclusively
-  // an internal renderer implementation, such as the children of an HTML native
-  // text field, as well as nodes with presentational children according to the
-  // ARIA and HTML5 Specs. Also returns true if all of the node's descendants
-  // are ignored.
+  // an internal renderer implementation, such as the children of an HTML-based
+  // text field (<input> and <textarea>), as well as nodes with presentational
+  // children according to the ARIA and HTML5 Specs. Also returns true if all of
+  // the node's descendants are ignored.
   //
   // A leaf node should never have children that are focusable or
   // that might send notifications.
@@ -547,9 +547,12 @@
   // an editable region is synonymous to a text field.
   AXNode* GetTextFieldAncestor() const;
 
-  // Returns true if this node is either a native text field , or one of its
-  // ancestors is.
-  bool IsDescendantOfNativeTextField() const;
+  // Returns true if this node is either an atomic text field , or one of its
+  // ancestors is. An atomic text field does not expose its internal
+  // implementation to assistive software, appearing as a single leaf node in
+  // the accessibility tree. It includes <input>, <textarea> and Views-based
+  // text fields.
+  bool IsDescendantOfAtomicTextField() const;
 
   // Finds and returns a pointer to ordered set containing node.
   AXNode* GetOrderedSet() const;
diff --git a/ui/accessibility/ax_node_data.cc b/ui/accessibility/ax_node_data.cc
index 5cccec4..8329e35 100644
--- a/ui/accessibility/ax_node_data.cc
+++ b/ui/accessibility/ax_node_data.cc
@@ -1021,25 +1021,25 @@
 }
 
 bool AXNodeData::IsTextField() const {
-  return IsNativeTextField() || IsNonNativeTextField();
+  return IsAtomicTextField() || IsNonAtomicTextField();
 }
 
 bool AXNodeData::IsPasswordField() const {
   return IsTextField() && HasState(ax::mojom::State::kProtected);
 }
 
-bool AXNodeData::IsNativeTextField() const {
+bool AXNodeData::IsAtomicTextField() const {
   // ARIA-based textboxes or searchboxes could mistakenly be identified as
   // atomic text fields, i.e. be identified as an <input> or a <textarea>. This
   // can only occur when the web author hasn't specified the "contenteditable"
   // attribute. Since these kinds of text fields are not really usable, we
   // decide not to support them.
   return ui::IsTextField(role) &&
-         !HasBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot);
+         !HasBoolAttribute(ax::mojom::BoolAttribute::kContentEditableRoot);
 }
 
-bool AXNodeData::IsNonNativeTextField() const {
-  return HasBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot);
+bool AXNodeData::IsNonAtomicTextField() const {
+  return HasBoolAttribute(ax::mojom::BoolAttribute::kContentEditableRoot);
 }
 
 bool AXNodeData::IsReadOnlyOrDisabled() const {
@@ -1587,8 +1587,8 @@
        bool_attributes) {
     std::string value = bool_attribute.second ? "true" : "false";
     switch (bool_attribute.first) {
-      case ax::mojom::BoolAttribute::kEditableRoot:
-        result += " editable_root=" + value;
+      case ax::mojom::BoolAttribute::kContentEditableRoot:
+        result += " contentEditable_root=" + value;
         break;
       case ax::mojom::BoolAttribute::kLiveAtomic:
         result += " atomic=" + value;
diff --git a/ui/accessibility/ax_node_data.h b/ui/accessibility/ax_node_data.h
index 29e698e..92fd945 100644
--- a/ui/accessibility/ax_node_data.h
+++ b/ui/accessibility/ax_node_data.h
@@ -267,12 +267,20 @@
   // This data belongs to a text field that is used for entering passwords.
   bool IsPasswordField() const;
 
-  // This data belongs to a native text field, i.e. <input> or <textarea>.
-  bool IsNativeTextField() const;
+  // This data belongs to an atomic text field. An atomic text field does not
+  // expose its internal implementation to assistive software, appearing as a
+  // single leaf node in the accessibility tree. Examples include: An <input> or
+  // a <textarea> on the Web, a text field in a PDF form, a Views-based text
+  // field, or a native Android one.
+  bool IsAtomicTextField() const;
 
-  // This data belongs to a text field that is created using the CSS
-  // "user-modify" property, or the "contenteditable" attribute.
-  bool IsNonNativeTextField() const;
+  // This data belongs to a text field whose value is exposed both on the field
+  // itself as well as on descendant nodes which are expose to platform
+  // accessibility APIs. A non-native text field also exposes stylistic and
+  // document marker information on descendant nodes. Examples include fields
+  // created using the CSS "user-modify" property, or the "contenteditable"
+  // attribute.
+  bool IsNonAtomicTextField() const;
 
   // Helper to determine if |GetRestriction| is either ReadOnly or Disabled.
   // By default, all nodes that can't be edited are readonly.
diff --git a/ui/accessibility/ax_node_position_unittest.cc b/ui/accessibility/ax_node_position_unittest.cc
index af894ec..91e5496 100644
--- a/ui/accessibility/ax_node_position_unittest.cc
+++ b/ui/accessibility/ax_node_position_unittest.cc
@@ -2909,9 +2909,9 @@
   // This test ensures that "At{Start|End}OfParagraph" work correctly when there
   // are embedded objects present near a paragraph boundary.
   //
-  // Nodes represented by an embedded object character, such as a plain text
-  // field or a check box, should create an implicit paragraph boundary for
-  // assistive software.
+  // Nodes represented by an embedded object character, such as an <input> or a
+  // <textarea> text field, or a check box, should create an implicit paragraph
+  // boundary for assistive software.
   // ++1 kRootWebArea isLineBreakingObject
   // ++++2 kLink
   // ++++++3 kStaticText "hello"
diff --git a/ui/accessibility/ax_position.h b/ui/accessibility/ax_position.h
index 3feacf72..b8b314f8b 100644
--- a/ui/accessibility/ax_position.h
+++ b/ui/accessibility/ax_position.h
@@ -3703,8 +3703,8 @@
   // a collapsed popup menu. The presence or the absence of accessible content
   // inside a control might alter whether an "object replacement character"
   // would be exposed in that control, in contrast to ordinary text such as in
-  // the case of a non-empty plain text field which should only have textual
-  // nodes inside it. This is because empty controls need to act as a word and
+  // the case of a non-empty text field which should only have textual nodes
+  // inside it. This is because empty controls need to act as a word and
   // character boundary.
   bool IsEmptyObjectReplacedByCharacter() const {
     if (g_ax_embedded_object_behavior ==
@@ -4080,8 +4080,8 @@
         // that are descendants of platform leaves should maintain the actual
         // text of all their static text descendants, otherwise there would be
         // loss of information while traversing the accessibility tree upwards.
-        // An example of a platform leaf is a plain text field, because all of
-        // the accessibility subtree inside the text field is hidden from
+        // An example of a platform leaf is an <input> text field, because all
+        // of the accessibility subtree inside the text field is hidden from
         // platform APIs. An example of how an ignored node can affect the
         // hypertext of an unignored ancestor is shown below:
         // ++kTextField "Hello"
diff --git a/ui/accessibility/platform/ax_platform_node_auralinux.cc b/ui/accessibility/platform/ax_platform_node_auralinux.cc
index 1037cf08..db3ae04a 100644
--- a/ui/accessibility/platform/ax_platform_node_auralinux.cc
+++ b/ui/accessibility/platform/ax_platform_node_auralinux.cc
@@ -23,6 +23,7 @@
 #include "base/optional.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/strings/utf_string_conversion_utils.h"
 #include "base/strings/utf_string_conversions.h"
@@ -2372,7 +2373,7 @@
 
   if (!IsImageOrVideo(data.role)) {
     interface_mask.Add(ImplementedAtkInterfaces::Value::kText);
-    if (!data.IsNativeTextField())
+    if (!data.IsAtomicTextField())
       interface_mask.Add(ImplementedAtkInterfaces::Value::kHypertext);
   }
 
@@ -3749,7 +3750,7 @@
   DCHECK(focus_node_id);
   DCHECK(focus_offset);
 
-  if (IsNativeTextField() &&
+  if (IsAtomicTextField() &&
       GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart, anchor_offset) &&
       GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, focus_offset)) {
     int32_t node_id = GetData().id != -1 ? GetData().id : GetUniqueId();
@@ -3767,7 +3768,8 @@
 AXPlatformNodeAuraLinux& AXPlatformNodeAuraLinux::FindEditableRootOrDocument() {
   if (GetAtkRole() == ATK_ROLE_DOCUMENT_WEB)
     return *this;
-  if (GetData().GetBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot))
+  if (GetData().GetBoolAttribute(
+          ax::mojom::BoolAttribute::kContentEditableRoot))
     return *this;
   if (auto* parent = FromAtkObject(GetParent()))
     return parent->FindEditableRootOrDocument();
@@ -4499,7 +4501,7 @@
 bool AXPlatformNodeAuraLinux::
     GrabFocusOrSetSequentialFocusNavigationStartingPointAtOffset(int offset) {
   int child_count = delegate_->GetChildCount();
-  if (IsNativeTextField() || child_count == 0)
+  if (IsAtomicTextField() || child_count == 0)
     return GrabFocusOrSetSequentialFocusNavigationStartingPoint();
 
   // When this node has children, we walk through them to figure out what child
diff --git a/ui/accessibility/platform/ax_platform_node_base.cc b/ui/accessibility/platform/ax_platform_node_base.cc
index 73fb2eb..43dab79 100644
--- a/ui/accessibility/platform/ax_platform_node_base.cc
+++ b/ui/accessibility/platform/ax_platform_node_base.cc
@@ -657,12 +657,12 @@
   return GetData().IsTextField();
 }
 
-bool AXPlatformNodeBase::IsNativeTextField() const {
-  return GetData().IsNativeTextField();
+bool AXPlatformNodeBase::IsAtomicTextField() const {
+  return GetData().IsAtomicTextField();
 }
 
-bool AXPlatformNodeBase::IsNonNativeTextField() const {
-  return GetData().IsNonNativeTextField();
+bool AXPlatformNodeBase::IsNonAtomicTextField() const {
+  return GetData().IsNonAtomicTextField();
 }
 
 bool AXPlatformNodeBase::IsText() const {
@@ -902,7 +902,7 @@
 }
 
 bool AXPlatformNodeBase::HasCaret(const AXTree::Selection* selection) {
-  if (IsNativeTextField() &&
+  if (IsAtomicTextField() &&
       HasIntAttribute(ax::mojom::IntAttribute::kTextSelStart) &&
       HasIntAttribute(ax::mojom::IntAttribute::kTextSelEnd)) {
     return true;
@@ -1330,7 +1330,7 @@
   std::string type;
   std::string html_tag =
       GetStringAttribute(ax::mojom::StringAttribute::kHtmlTag);
-  if (IsNativeTextField() && base::LowerCaseEqualsASCII(html_tag, "input") &&
+  if (IsAtomicTextField() && base::LowerCaseEqualsASCII(html_tag, "input") &&
       GetData().GetHtmlAttribute("type", &type)) {
     AddAttributeToList("text-input-type", type, attributes);
   }
@@ -1716,7 +1716,7 @@
                                              int* selection_end) {
   DCHECK(selection_start && selection_end);
 
-  if (IsNativeTextField() &&
+  if (IsAtomicTextField() &&
       GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart,
                       selection_start) &&
       GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, selection_end)) {
diff --git a/ui/accessibility/platform/ax_platform_node_base.h b/ui/accessibility/platform/ax_platform_node_base.h
index 6b9f827..aacb46a 100644
--- a/ui/accessibility/platform/ax_platform_node_base.h
+++ b/ui/accessibility/platform/ax_platform_node_base.h
@@ -257,11 +257,11 @@
   // See AXNodeData::IsTextField().
   bool IsTextField() const;
 
-  // See AXNodeData::IsNativeTextField().
-  bool IsNativeTextField() const;
+  // See AXNodeData::IsAtomicTextField().
+  bool IsAtomicTextField() const;
 
-  // See AXNodeData::IsNonNativeTextField().
-  bool IsNonNativeTextField() const;
+  // See AXNodeData::IsNonAtomicTextField().
+  bool IsNonAtomicTextField() const;
 
   // See AXNode::IsText().
   bool IsText() const;
diff --git a/ui/accessibility/platform/ax_platform_node_delegate.h b/ui/accessibility/platform/ax_platform_node_delegate.h
index 6a38d5d..0b1b3c9 100644
--- a/ui/accessibility/platform/ax_platform_node_delegate.h
+++ b/ui/accessibility/platform/ax_platform_node_delegate.h
@@ -157,9 +157,12 @@
   // platform's accessibility layer.
   virtual bool IsChildOfLeaf() const = 0;
 
-  // Returns true if this node is either a plain text field , or one of its
-  // ancestors is.
-  virtual bool IsDescendantOfNativeTextField() const = 0;
+  // Returns true if this node is either an atomic text field , or one of its
+  // ancestors is. An atomic text field does not expose its internal
+  // implementation to assistive software, appearing as a single leaf node in
+  // the accessibility tree. It includes <input>, <textarea> and Views-based
+  // text fields.
+  virtual bool IsDescendantOfAtomicTextField() const = 0;
 
   // Returns true if this is a leaf node, meaning all its
   // children should not be exposed to any platform's native accessibility
diff --git a/ui/accessibility/platform/ax_platform_node_delegate_base.cc b/ui/accessibility/platform/ax_platform_node_delegate_base.cc
index cab3296..1b7a3a1 100644
--- a/ui/accessibility/platform/ax_platform_node_delegate_base.cc
+++ b/ui/accessibility/platform/ax_platform_node_delegate_base.cc
@@ -172,7 +172,7 @@
   return false;
 }
 
-bool AXPlatformNodeDelegateBase::IsDescendantOfNativeTextField() const {
+bool AXPlatformNodeDelegateBase::IsDescendantOfAtomicTextField() const {
   return false;
 }
 
@@ -608,7 +608,7 @@
 }
 
 bool AXPlatformNodeDelegateBase::HasVisibleCaretOrSelection() const {
-  return IsDescendantOfNativeTextField();
+  return IsDescendantOfAtomicTextField();
 }
 
 AXPlatformNode* AXPlatformNodeDelegateBase::GetTargetNodeForRelation(
diff --git a/ui/accessibility/platform/ax_platform_node_delegate_base.h b/ui/accessibility/platform/ax_platform_node_delegate_base.h
index f132c75..0ebe8f2 100644
--- a/ui/accessibility/platform/ax_platform_node_delegate_base.h
+++ b/ui/accessibility/platform/ax_platform_node_delegate_base.h
@@ -72,7 +72,7 @@
   gfx::NativeViewAccessible GetPreviousSibling() override;
 
   bool IsChildOfLeaf() const override;
-  bool IsDescendantOfNativeTextField() const override;
+  bool IsDescendantOfAtomicTextField() const override;
   bool IsLeaf() const override;
   bool IsFocused() const override;
   bool IsToplevelBrowserWindow() override;
diff --git a/ui/accessibility/platform/ax_platform_node_mac.mm b/ui/accessibility/platform/ax_platform_node_mac.mm
index cfbbd1f..003c3920 100644
--- a/ui/accessibility/platform/ax_platform_node_mac.mm
+++ b/ui/accessibility/platform/ax_platform_node_mac.mm
@@ -870,7 +870,7 @@
 
 - (NSValue*)AXSelectedTextRange {
   int start = 0, end = 0;
-  if (_node->IsNativeTextField() &&
+  if (_node->IsAtomicTextField() &&
       _node->GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart, &start) &&
       _node->GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, &end)) {
     // NSRange cannot represent the direction the text was selected in.
diff --git a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
index 3f97be0..f15022a 100644
--- a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
@@ -1243,8 +1243,8 @@
   // the TextPattern must be preserved so that the UIA client can handle
   // scenarios such as determining which characters were deleted. So
   // normalization must be bypassed.
-  if (HasCaretOrSelectionInPlainTextField(start) ||
-      HasCaretOrSelectionInPlainTextField(end)) {
+  if (HasCaretOrSelectionInAtomicTextField(start) ||
+      HasCaretOrSelectionInAtomicTextField(end)) {
     return;
   }
 
@@ -1395,21 +1395,28 @@
   return platform_node->GetLowestAccessibleElement();
 }
 
-bool AXPlatformNodeTextRangeProviderWin::HasCaretOrSelectionInPlainTextField(
+bool AXPlatformNodeTextRangeProviderWin::HasCaretOrSelectionInAtomicTextField(
     const AXPositionInstance& position) const {
-  // This condition fixes issues when the caret is inside a plain text field,
-  // but causes more issues when used inside of a rich text field. For this
-  // reason, if we have a caret or a selection inside of an editable node,
-  // restrict this to a plain text field as we gain nothing from using it in a
-  // rich text field.
+  // This condition fixes issues when the caret is inside an atomic text field,
+  // but causes more issues when used inside of a non-atomic text field. An
+  // atomic text field does not expose its internal implementation to assistive
+  // software, appearing as a single leaf node in the accessibility tree. It
+  // includes <input>, <textarea> and Views-based text fields.
   //
-  // Note that "AXPlatformNodeDelegate::IsDescendantOfNativeTextField()" also
-  // returns true when this node is at the root of a plain text field, i.e. the
-  // node could either be a descendant or it could be equivalent to the field's
-  // root node.
+  // For this reason, if we have a caret or a selection inside of an editable
+  // node, restrict this to an atomic text field as we gain nothing from using
+  // it in a non-atomic text field.
+  //
+  // Note that "AXPlatformNodeDelegate::IsDescendantOfAtomicTextField()" also
+  // returns true when this node is at the root of an atomic text field, i.e.
+  // the node could either be a descendant or it could be equivalent to the
+  // field's root node. An atomic text field does not expose its internal
+  // implementation to assistive software, appearing as a single leaf node in
+  // the accessibility tree. It includes <input>, <textarea> and Views-based
+  // text fields.
   AXPlatformNodeDelegate* delegate = GetDelegate(position.get());
   return delegate && delegate->HasVisibleCaretOrSelection() &&
-         delegate->IsDescendantOfNativeTextField();
+         delegate->IsDescendantOfAtomicTextField();
 }
 
 // static
diff --git a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h
index 35e1096..b814aed 100644
--- a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h
+++ b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h
@@ -178,7 +178,7 @@
   void RemoveFocusFromPreviousSelectionIfNeeded(
       const AXNodeRange& new_selection);
   AXPlatformNodeWin* GetLowestAccessibleCommonPlatformNode() const;
-  bool HasCaretOrSelectionInPlainTextField(
+  bool HasCaretOrSelectionInAtomicTextField(
       const AXPositionInstance& position) const;
 
   void SetStart(AXPositionInstance start);
diff --git a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
index 7a518d9..e10c6f0 100644
--- a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
@@ -3680,8 +3680,8 @@
   generic_container_2.role = ax::mojom::Role::kGenericContainer;
   generic_container_2.AddState(ax::mojom::State::kEditable);
   generic_container_2.AddState(ax::mojom::State::kRichlyEditable);
-  generic_container_2.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot,
-                                       true);
+  generic_container_2.AddBoolAttribute(
+      ax::mojom::BoolAttribute::kContentEditableRoot, true);
   generic_container_2.child_ids = {static_text_3.id, static_text_6.id,
                                    static_text_7.id, button_8.id};
 
@@ -3700,7 +3700,8 @@
   button_8.role = ax::mojom::Role::kButton;
   // Hack: The kEditableRoot attribute is needed to get a text range provider
   // located on this element (see AXPlatformNodeWin::GetPatternProvider).
-  button_8.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot, true);
+  button_8.AddBoolAttribute(ax::mojom::BoolAttribute::kContentEditableRoot,
+                            true);
   // When kEditableRoot is set, kEditable is also expected.
   button_8.AddState(ax::mojom::State::kEditable);
   button_8.child_ids = {image_9.id, static_text_10.id};
@@ -5663,8 +5664,8 @@
   tree_update.nodes[1].AddState(ax::mojom::State::kIgnored);
   tree_update.nodes[1].AddState(ax::mojom::State::kEditable);
   tree_update.nodes[1].AddState(ax::mojom::State::kRichlyEditable);
-  tree_update.nodes[1].AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot,
-                                        true);
+  tree_update.nodes[1].AddBoolAttribute(
+      ax::mojom::BoolAttribute::kContentEditableRoot, true);
   tree_update.nodes[1].role = ax::mojom::Role::kGenericContainer;
 
   tree_update.nodes[2].id = 3;
diff --git a/ui/accessibility/platform/ax_platform_node_unittest.cc b/ui/accessibility/platform/ax_platform_node_unittest.cc
index d6fc3dad..99b9dd5 100644
--- a/ui/accessibility/platform/ax_platform_node_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_unittest.cc
@@ -103,7 +103,7 @@
   content_editable_node.AddState(ax::mojom::State::kEditable);
   content_editable_node.AddState(ax::mojom::State::kRichlyEditable);
   content_editable_node.AddBoolAttribute(
-      ax::mojom::BoolAttribute::kEditableRoot, true);
+      ax::mojom::BoolAttribute::kContentEditableRoot, true);
   content_editable_node.SetValue("How now brown cow.");
 
   AXTreeUpdate update;
@@ -123,7 +123,7 @@
   content_editable_node.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected,
                                          true);
   content_editable_node.AddBoolAttribute(
-      ax::mojom::BoolAttribute::kEditableRoot, true);
+      ax::mojom::BoolAttribute::kContentEditableRoot, true);
   content_editable_node.SetValue("How now brown cow.");
 
   AXTreeUpdate update;
diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc
index dcde7fac..28b51aa3 100644
--- a/ui/accessibility/platform/ax_platform_node_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_win.cc
@@ -4973,7 +4973,7 @@
   MarkerTypeRangeResult grammar_result = MarkerTypeRangeResult::kNone;
   MarkerTypeRangeResult spelling_result = MarkerTypeRangeResult::kNone;
 
-  if (IsText() || IsNativeTextField()) {
+  if (IsText() || IsAtomicTextField()) {
     grammar_result = GetMarkerTypeFromRange(start_offset, end_offset,
                                             ax::mojom::MarkerType::kGrammar);
     spelling_result = GetMarkerTypeFromRange(start_offset, end_offset,
@@ -5183,13 +5183,13 @@
     const base::Optional<int>& start_offset,
     const base::Optional<int>& end_offset,
     ax::mojom::MarkerType marker_type) {
-  DCHECK(IsText() || IsNativeTextField());
+  DCHECK(IsText() || IsAtomicTextField());
   std::vector<std::pair<int, int>> relevant_ranges;
 
   if (IsText()) {
     AggregateRangesForMarkerType(this, marker_type, /*offset_ranges_amount=*/0,
                                  &relevant_ranges);
-  } else if (IsNativeTextField()) {
+  } else if (IsAtomicTextField()) {
     int offset_ranges_amount = 0;
     for (AXPlatformNodeBase* static_text = GetFirstTextOnlyDescendant();
          static_text; static_text = static_text->GetNextSibling()) {
@@ -5835,7 +5835,7 @@
       // enable paste operations. Eventually this need should go away once IE11
       // support is no longer needed and Slides instead relies on paste events.
       if (!data.HasState(ax::mojom::State::kFocusable) ||
-          GetBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot))
+          GetBoolAttribute(ax::mojom::BoolAttribute::kContentEditableRoot))
         break;  // Not used with activedescendant, so preserve editable state.
       FALLTHROUGH;  // Will clear editable state.
     case ax::mojom::Role::kMenuListPopup:
@@ -7419,7 +7419,8 @@
     // content as not controls.
     // Doing so helps Narrator find all the content of live regions.
     if (!data.GetBoolAttribute(ax::mojom::BoolAttribute::kHasAriaAttribute) &&
-        !data.GetBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot) &&
+        !data.GetBoolAttribute(
+            ax::mojom::BoolAttribute::kContentEditableRoot) &&
         GetName().empty() &&
         data.GetStringAttribute(ax::mojom::StringAttribute::kDescription)
             .empty() &&
@@ -7490,7 +7491,7 @@
 }
 
 bool AXPlatformNodeWin::ShouldHideChildrenForUIA() const {
-  if (IsNativeTextField())
+  if (IsAtomicTextField())
     return true;
 
   auto role = GetData().role;
diff --git a/ui/accessibility/platform/ax_platform_node_win_unittest.cc b/ui/accessibility/platform/ax_platform_node_win_unittest.cc
index 56fa930..e28d5c6 100644
--- a/ui/accessibility/platform/ax_platform_node_win_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_win_unittest.cc
@@ -4744,8 +4744,8 @@
   update.nodes[12].role = ax::mojom::Role::kGenericContainer;
   update.nodes[12].AddState(ax::mojom::State::kEditable);
   update.nodes[12].AddState(ax::mojom::State::kRichlyEditable);
-  update.nodes[12].AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot,
-                                    true);
+  update.nodes[12].AddBoolAttribute(
+      ax::mojom::BoolAttribute::kContentEditableRoot, true);
   update.nodes[13].id = 14;
   update.nodes[13].role = ax::mojom::Role::kGenericContainer;
   update.nodes[13].SetName("name");
diff --git a/ui/accessibility/platform/test_ax_node_wrapper.cc b/ui/accessibility/platform/test_ax_node_wrapper.cc
index 64677de..7a514b9 100644
--- a/ui/accessibility/platform/test_ax_node_wrapper.cc
+++ b/ui/accessibility/platform/test_ax_node_wrapper.cc
@@ -854,7 +854,7 @@
 
   // Selection or caret will be visible in a focused editable area.
   if (GetData().HasState(ax::mojom::State::kEditable)) {
-    return GetData().IsNativeTextField() ? focus_object == node_
+    return GetData().IsAtomicTextField() ? focus_object == node_
                                          : focus_object->IsDescendantOf(node_);
   }
 
diff --git a/ui/android/java/src/org/chromium/ui/base/Clipboard.java b/ui/android/java/src/org/chromium/ui/base/Clipboard.java
index ef5bd3f..a84042b 100644
--- a/ui/android/java/src/org/chromium/ui/base/Clipboard.java
+++ b/ui/android/java/src/org/chromium/ui/base/Clipboard.java
@@ -500,11 +500,20 @@
     /** Clears the Clipboard Primary clip. */
     @CalledByNative
     private void clear() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
+        // clearPrimaryClip() has been observed to throw unexpected exceptions for Android P (see
+        // crbug/1203377)
+        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
             setPrimaryClipNoException(ClipData.newPlainText(null, null));
             return;
         }
-        ApiHelperForP.clearPrimaryClip(mClipboardManager);
+
+        try {
+            ApiHelperForP.clearPrimaryClip(mClipboardManager);
+        } catch (Exception e) {
+            // Fall back to set an empty string to the clipboard.
+            setPrimaryClipNoException(ClipData.newPlainText(null, null));
+            return;
+        }
     }
 
     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
diff --git a/ui/aura/window.cc b/ui/aura/window.cc
index 303a0290..5840df9 100644
--- a/ui/aura/window.cc
+++ b/ui/aura/window.cc
@@ -291,9 +291,6 @@
 }
 
 gfx::Rect Window::GetBoundsInRootWindow() const {
-  // TODO(beng): There may be a better way to handle this, and the existing code
-  //             is likely wrong anyway in a multi-display world, but this will
-  //             do for now.
   if (!GetRootWindow())
     return bounds();
   gfx::Rect bounds_in_root(bounds().size());
@@ -301,6 +298,17 @@
   return bounds_in_root;
 }
 
+gfx::Rect Window::GetActualBoundsInRootWindow() const {
+  if (!GetRootWindow())
+    return bounds();
+  gfx::Rect bounds_in_root(bounds().size());
+  gfx::PointF origin_f = gfx::PointF(bounds_in_root.origin());
+  ui::Layer::ConvertPointToLayer(layer(), GetRootWindow()->layer(),
+                                 /*use_target_transform=*/false, &origin_f);
+  bounds_in_root.set_origin(gfx::ToFlooredPoint(origin_f));
+  return bounds_in_root;
+}
+
 gfx::Rect Window::GetBoundsInScreen() const {
   gfx::Rect bounds(GetBoundsInRootWindow());
   const Window* root = GetRootWindow();
@@ -316,6 +324,17 @@
   return bounds;
 }
 
+gfx::Rect Window::GetActualBoundsInScreen() const {
+  gfx::Rect bounds(GetActualBoundsInRootWindow());
+  const Window* root = GetRootWindow();
+  if (root) {
+    gfx::Point origin_in_screen = root->GetBoundsInScreen().origin();
+    origin_in_screen += bounds.OffsetFromOrigin();
+    bounds.set_origin(origin_in_screen);
+  }
+  return bounds;
+}
+
 void Window::SetTransform(const gfx::Transform& transform) {
   WindowOcclusionTracker::ScopedPause pause_occlusion_tracking;
   for (WindowObserver& observer : observers_)
@@ -505,7 +524,8 @@
     if (target_client)
       target_client->ConvertPointFromScreen(target, point);
   } else {
-    ui::Layer::ConvertPointToLayer(source->layer(), target->layer(), point);
+    ui::Layer::ConvertPointToLayer(source->layer(), target->layer(),
+                                   /*use_target_transform=*/true, point);
   }
 }
 
diff --git a/ui/aura/window.h b/ui/aura/window.h
index d303446..4f52876 100644
--- a/ui/aura/window.h
+++ b/ui/aura/window.h
@@ -256,15 +256,31 @@
     return subtree_capture_id_;
   }
 
-  // Returns the window's bounds in root window's coordinates.
+  // Returns the window's bounds in root window's coordinates. The returned
+  // value is calculated using the target transform. The target transform is the
+  // end value of a transform animation. If there is no animation ongoing, the
+  // target transform is the same as the current transform.
   gfx::Rect GetBoundsInRootWindow() const;
 
-  // Returns the window's bounds in screen coordinates.
+  // Similar to `GetBoundsInRootWindow()` except that the returned value is
+  // calculated using the current transform. If there is no animation ongoing,
+  // this function returns the same value as `GetBoundsInRootWindow()`.
+  gfx::Rect GetActualBoundsInRootWindow() const;
+
+  // Returns the window's bounds in screen coordinates. The returned
+  // value is calculated using the target transform. The target transform is the
+  // end value of a transform animation. If there is no animation ongoing, the
+  // target transform is the same as the current transform.
   // How the root window's coordinates is mapped to screen's coordinates
   // is platform dependent and defined in the implementation of the
   // |aura::client::ScreenPositionClient| interface.
   gfx::Rect GetBoundsInScreen() const;
 
+  // Similar to `GetBoundsInScreen()` except that the returned value is
+  // calculated using the current transform. If there is no animation ongoing,
+  // this function returns the same value as `GetBoundsInScreen()`.
+  gfx::Rect GetActualBoundsInScreen() const;
+
   void SetTransform(const gfx::Transform& transform);
   const gfx::Transform& transform() const { return layer()->transform(); }
 
@@ -330,7 +346,10 @@
 
   // Converts |point| from |source|'s coordinates to |target|'s. If |source| is
   // nullptr, the function returns without modifying |point|. |target| cannot be
-  // nullptr.
+  // nullptr. Use layers' target transform in coordinate conversions. The target
+  // transform is the end value of a transform animation. If there is no
+  // animation ongoing, the target transform is the same as the current
+  // transform.
   static void ConvertPointToTarget(const Window* source,
                                    const Window* target,
                                    gfx::PointF* point);
diff --git a/ui/aura/window_unittest.cc b/ui/aura/window_unittest.cc
index 8190c0ae..e4c279e 100644
--- a/ui/aura/window_unittest.cc
+++ b/ui/aura/window_unittest.cc
@@ -39,6 +39,7 @@
 #include "ui/aura/window_tree_host.h"
 #include "ui/base/class_property.h"
 #include "ui/base/hit_test.h"
+#include "ui/compositor/compositor_observer.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/layer_animation_observer.h"
 #include "ui/compositor/layer_animator.h"
@@ -96,6 +97,68 @@
   DISALLOW_COPY_AND_ASSIGN(DeletionTracker);
 };
 
+// The helper class to wait for the animation completion and run callbacks.
+class LayerTranslationAnimationNotifier : public ui::CompositorObserver {
+ public:
+  using AnimationCallback =
+      base::RepeatingCallback<void(const gfx::Transform&)>;
+
+  LayerTranslationAnimationNotifier(
+      ui::Layer* animation_layer,
+      const gfx::Transform& target_transform,
+      AnimationCallback animation_start_callback,
+      AnimationCallback animation_end_callback,
+      AnimationCallback animation_progress_callback)
+      : animation_layer_(animation_layer),
+        target_transform_(target_transform),
+        animation_start_callback_(animation_start_callback),
+        animation_end_callback_(animation_end_callback),
+        animation_progress_callback_(animation_progress_callback) {
+    DCHECK(!target_transform_.IsIdentity());
+    animation_layer_->GetCompositor()->AddObserver(this);
+  }
+  LayerTranslationAnimationNotifier(const LayerTranslationAnimationNotifier&) =
+      delete;
+  LayerTranslationAnimationNotifier& operator=(
+      const LayerTranslationAnimationNotifier&) = delete;
+  ~LayerTranslationAnimationNotifier() override {
+    animation_layer_->GetCompositor()->RemoveObserver(this);
+  }
+
+  void WaitForAnimationCompletion() { run_loop_.Run(); }
+
+  // ui::CompositorObserver:
+  void OnCompositingDidCommit(ui::Compositor* compositor) override {
+    const gfx::Transform current_transform = animation_layer_->transform();
+    if (current_transform.IsIdentity()) {
+      animation_start_callback_.Run(current_transform);
+    } else if (current_transform == target_transform_) {
+      animation_end_callback_.Run(current_transform);
+      run_loop_.Quit();
+    } else {
+      animation_progress_callback_.Run(current_transform);
+    }
+  }
+
+ private:
+  // The layer to be animated.
+  ui::Layer* const animation_layer_;
+
+  // The target transform.
+  gfx::Transform target_transform_;
+
+  // The callback to run at the start of the animation.
+  AnimationCallback animation_start_callback_;
+
+  // The callback to run at the end of the animation.
+  AnimationCallback animation_end_callback_;
+
+  // The callback to run during the progress of the animation.
+  AnimationCallback animation_progress_callback_;
+
+  base::RunLoop run_loop_;
+};
+
 class DeletionTestProperty {
  public:
   explicit DeletionTestProperty(DeletionTracker* tracker) : tracker_(tracker) {}
@@ -1812,6 +1875,63 @@
   EXPECT_EQ(DeletionOrder::LAYOUT_MANAGER_FIRST, tracker.order());
 }
 
+// Verifies that the function to get the window's screen bounds during layer
+// animation works as expected.
+TEST_F(WindowTest, VerifyWindowActualBoundsDuringAnimation) {
+  ui::ScopedAnimationDurationScaleMode test_duration_mode(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+
+  std::unique_ptr<Window> viewport(
+      CreateTestWindowWithBounds(gfx::Rect(100, 50, 200, 200), root_window()));
+  std::unique_ptr<Window> child(
+      CreateTestWindowWithBounds(gfx::Rect(0, 0, 100, 100), viewport.get()));
+
+  // Store `child''s bounds in screen before animation starts.
+  const gfx::Rect start_screen_bounds = child->GetBoundsInScreen();
+
+  // Start a translation animation on `viewport`.
+  auto* viewport_layer = viewport->layer();
+  auto* viewport_layer_animator = viewport_layer->GetAnimator();
+  gfx::Transform target_transform;
+  target_transform.Translate(-50, -50);
+  {
+    ui::ScopedLayerAnimationSettings settings(viewport_layer_animator);
+    viewport_layer->SetTransform(target_transform);
+  }
+
+  // Verify at the start of the animation.
+  auto animation_start_callback = [](const aura::Window* child,
+                                     const gfx::Transform& transform) {
+    EXPECT_EQ("100,50 100x100", child->GetActualBoundsInScreen().ToString());
+    EXPECT_EQ("50,0 100x100", child->GetBoundsInScreen().ToString());
+  };
+
+  // Verify at the end of the animation.
+  auto animation_end_callback = [](const aura::Window* child,
+                                   const gfx::Transform& transform) {
+    EXPECT_EQ("50,0 100x100", child->GetActualBoundsInScreen().ToString());
+    EXPECT_EQ("50,0 100x100", child->GetBoundsInScreen().ToString());
+  };
+
+  // Verify during the progress of the animation.
+  auto animation_progress_callback = [](const aura::Window* child,
+                                        const gfx::Rect& start_screen_bounds,
+                                        const gfx::Transform& transform) {
+    gfx::RectF current_screen_bounds = gfx::RectF(start_screen_bounds);
+    transform.TransformRect(&current_screen_bounds);
+    EXPECT_EQ(gfx::ToEnclosedRect(current_screen_bounds),
+              child->GetActualBoundsInScreen());
+  };
+
+  LayerTranslationAnimationNotifier bounds_checker(
+      viewport_layer, target_transform,
+      base::BindRepeating(animation_start_callback, child.get()),
+      base::BindRepeating(animation_end_callback, child.get()),
+      base::BindRepeating(animation_progress_callback, child.get(),
+                          start_screen_bounds));
+  bounds_checker.WaitForAnimationCompletion();
+}
+
 TEST_F(WindowTest, SetBoundsInternalShouldCheckTargetBounds) {
   // We cannot short-circuit animations in this test.
   ui::ScopedAnimationDurationScaleMode test_duration_mode(
diff --git a/ui/compositor/layer.cc b/ui/compositor/layer.cc
index 26f483cc..ccaa1e4 100644
--- a/ui/compositor/layer.cc
+++ b/ui/compositor/layer.cc
@@ -715,9 +715,22 @@
     mirror->dest()->SetIsFastRoundedCorner(enable);
 }
 
+bool Layer::GetTargetTransformRelativeTo(const Layer* ancestor,
+                                         gfx::Transform* transform) const {
+  return GetTransformRelativeToImpl(ancestor, /*is_target_transform=*/true,
+                                    transform);
+}
+
+bool Layer::GetTransformRelativeTo(const Layer* ancestor,
+                                   gfx::Transform* transform) const {
+  return GetTransformRelativeToImpl(ancestor, /*is_target_transform=*/false,
+                                    transform);
+}
+
 // static
 void Layer::ConvertPointToLayer(const Layer* source,
                                 const Layer* target,
+                                bool use_target_transform,
                                 gfx::PointF* point) {
   if (source == target)
     return;
@@ -726,25 +739,9 @@
   CHECK_EQ(root_layer, GetRoot(target));
 
   if (source != root_layer)
-    source->ConvertPointForAncestor(root_layer, point);
+    source->ConvertPointForAncestor(root_layer, use_target_transform, point);
   if (target != root_layer)
-    target->ConvertPointFromAncestor(root_layer, point);
-}
-
-bool Layer::GetTargetTransformRelativeTo(const Layer* ancestor,
-                                         gfx::Transform* transform) const {
-  const Layer* p = this;
-  for (; p && p != ancestor; p = p->parent()) {
-    gfx::Transform translation;
-    translation.Translate(static_cast<float>(p->bounds().x()),
-                          static_cast<float>(p->bounds().y()));
-    // Use target transform so that result will be correct once animation is
-    // finished.
-    if (!p->GetTargetTransform().IsIdentity())
-      transform->ConcatTransform(p->GetTargetTransform());
-    transform->ConcatTransform(translation);
-  }
-  return p == ancestor;
+    target->ConvertPointFromAncestor(root_layer, use_target_transform, point);
 }
 
 void Layer::SetFillsBoundsOpaquely(bool fills_bounds_opaquely) {
@@ -1379,9 +1376,12 @@
 }
 
 bool Layer::ConvertPointForAncestor(const Layer* ancestor,
+                                    bool use_target_transform,
                                     gfx::PointF* point) const {
   gfx::Transform transform;
-  bool result = GetTargetTransformRelativeTo(ancestor, &transform);
+  bool result = use_target_transform
+                    ? GetTargetTransformRelativeTo(ancestor, &transform)
+                    : GetTransformRelativeTo(ancestor, &transform);
   auto p = gfx::Point3F(*point);
   transform.TransformPoint(&p);
   *point = p.AsPointF();
@@ -1389,9 +1389,12 @@
 }
 
 bool Layer::ConvertPointFromAncestor(const Layer* ancestor,
+                                     bool use_target_transform,
                                      gfx::PointF* point) const {
   gfx::Transform transform;
-  bool result = GetTargetTransformRelativeTo(ancestor, &transform);
+  bool result = use_target_transform
+                    ? GetTargetTransformRelativeTo(ancestor, &transform)
+                    : GetTransformRelativeTo(ancestor, &transform);
   auto p = gfx::Point3F(*point);
   transform.TransformPointReverse(&p);
   *point = p.AsPointF();
@@ -1720,4 +1723,21 @@
     delegate_->OnLayerFillsBoundsOpaquelyChanged(reason);
 }
 
+bool Layer::GetTransformRelativeToImpl(const Layer* ancestor,
+                                       bool is_target_transform,
+                                       gfx::Transform* transform) const {
+  const Layer* p = this;
+  for (; p && p != ancestor; p = p->parent()) {
+    gfx::Transform translation;
+    translation.Translate(static_cast<float>(p->bounds().x()),
+                          static_cast<float>(p->bounds().y()));
+    if (!p->GetTargetTransform().IsIdentity()) {
+      transform->ConcatTransform(is_target_transform ? p->GetTargetTransform()
+                                                     : p->transform());
+    }
+    transform->ConcatTransform(translation);
+  }
+  return p == ancestor;
+}
+
 }  // namespace ui
diff --git a/ui/compositor/layer.h b/ui/compositor/layer.h
index 78a5a4b..f055bcbf 100644
--- a/ui/compositor/layer.h
+++ b/ui/compositor/layer.h
@@ -330,14 +330,19 @@
 
   // Converts a point from the coordinates of |source| to the coordinates of
   // |target|. Necessarily, |source| and |target| must inhabit the same Layer
-  // tree.
+  // tree. If `use_target_transform` is true, the target transform is used in
+  // coordinate conversions; otherwise, the current transform is used. If there
+  // is no animation ongoing, the target transform is the same as the current
+  // transform.
   static void ConvertPointToLayer(const Layer* source,
                                   const Layer* target,
+                                  bool use_target_transform,
                                   gfx::PointF* point);
 
-  // Converts a transform to be relative to the given |ancestor|. Returns
-  // whether success (that is, whether the given ancestor was really an
-  // ancestor of this layer).
+  // Calculates the relative transform. See the comment of
+  // `GetTransformRelativeToImpl()` for further details.
+  bool GetTransformRelativeTo(const Layer* ancestor,
+                              gfx::Transform* transform) const;
   bool GetTargetTransformRelativeTo(const Layer* ancestor,
                                     gfx::Transform* transform) const;
 
@@ -540,8 +545,16 @@
   // StackBelow().
   void StackRelativeTo(Layer* child, Layer* other, bool above);
 
-  bool ConvertPointForAncestor(const Layer* ancestor, gfx::PointF* point) const;
+  // If `use_target_transform` is true, coordinate conversions use the target
+  // transform. The target transform is the end value of a transform animation.
+  // If `use_target_transform` is false, coordinate conversions use the current
+  // transform. If there is no animation ongoing, the target transform is the
+  // same as the current transform.
+  bool ConvertPointForAncestor(const Layer* ancestor,
+                               bool use_target_transform,
+                               gfx::PointF* point) const;
   bool ConvertPointFromAncestor(const Layer* ancestor,
+                                bool use_target_transform,
                                 gfx::PointF* point) const;
 
   // Implementation of LayerAnimatorDelegate
@@ -625,6 +638,15 @@
   void SetFillsBoundsOpaquelyWithReason(bool fills_bounds_opaquely,
                                         PropertyChangeReason reason);
 
+  // Converts a transform to be relative to the given |ancestor|. If
+  // `is_target_transform` is true, the target transform is used in the
+  // coordinate conversions; otherwise, the current transform is used. Returns
+  // whether success (that is, whether the given ancestor was really an ancestor
+  // of this layer).
+  bool GetTransformRelativeToImpl(const Layer* ancestor,
+                                  bool is_target_transform,
+                                  gfx::Transform* transform) const;
+
   const LayerType type_;
 
   Compositor* compositor_;
diff --git a/ui/compositor/layer_unittest.cc b/ui/compositor/layer_unittest.cc
index c6357a1..3ab486fc1 100644
--- a/ui/compositor/layer_unittest.cc
+++ b/ui/compositor/layer_unittest.cc
@@ -586,12 +586,14 @@
   DrawTree(l1.get());
 
   gfx::PointF point1_in_l2_coords(5, 5);
-  Layer::ConvertPointToLayer(l2.get(), l1.get(), &point1_in_l2_coords);
+  Layer::ConvertPointToLayer(l2.get(), l1.get(), /*use_target_transform=*/true,
+                             &point1_in_l2_coords);
   gfx::PointF point1_in_l1_coords(15, 15);
   EXPECT_EQ(point1_in_l1_coords, point1_in_l2_coords);
 
   gfx::PointF point2_in_l1_coords(5, 5);
-  Layer::ConvertPointToLayer(l1.get(), l2.get(), &point2_in_l1_coords);
+  Layer::ConvertPointToLayer(l1.get(), l2.get(), /*use_target_transform=*/true,
+                             &point2_in_l1_coords);
   gfx::PointF point2_in_l2_coords(-5, -5);
   EXPECT_EQ(point2_in_l2_coords, point2_in_l1_coords);
 }
@@ -611,12 +613,14 @@
   DrawTree(l1.get());
 
   gfx::PointF point1_in_l3_coords(5, 5);
-  Layer::ConvertPointToLayer(l3.get(), l1.get(), &point1_in_l3_coords);
+  Layer::ConvertPointToLayer(l3.get(), l1.get(), /*use_target_transform=*/true,
+                             &point1_in_l3_coords);
   gfx::PointF point1_in_l1_coords(25, 25);
   EXPECT_EQ(point1_in_l1_coords, point1_in_l3_coords);
 
   gfx::PointF point2_in_l1_coords(5, 5);
-  Layer::ConvertPointToLayer(l1.get(), l3.get(), &point2_in_l1_coords);
+  Layer::ConvertPointToLayer(l1.get(), l3.get(), /*use_target_transform=*/true,
+                             &point2_in_l1_coords);
   gfx::PointF point2_in_l3_coords(-15, -15);
   EXPECT_EQ(point2_in_l3_coords, point2_in_l1_coords);
 }
diff --git a/ui/gfx/geometry/mojom/geometry.mojom b/ui/gfx/geometry/mojom/geometry.mojom
index 30c1992..a21cc11 100644
--- a/ui/gfx/geometry/mojom/geometry.mojom
+++ b/ui/gfx/geometry/mojom/geometry.mojom
@@ -48,6 +48,7 @@
   float height;
 };
 
+[Stable]
 struct Insets {
   int32 top;
   int32 left;
diff --git a/ui/webui/resources/cr_elements/cr_input/cr_input.html b/ui/webui/resources/cr_elements/cr_input/cr_input.html
index 13d6012b..a20592e1 100644
--- a/ui/webui/resources/cr_elements/cr_input/cr_input.html
+++ b/ui/webui/resources/cr_elements/cr_input/cr_input.html
@@ -139,6 +139,7 @@
               readonly$="[[readonly]]" maxlength$="[[maxlength]]"
               pattern$="[[pattern]]" required="[[required]]"
               minlength$="[[minlength]]" inputmode$="[[inputmode]]"
+              aria-description$="[[ariaDescription]]"
               aria-label$="[[getAriaLabel_(ariaLabel, label, placeholder)]]"
               aria-invalid$="[[getAriaInvalid_(invalid)]]"
               max="[[max]]" min="[[min]]" on-focus="onInputFocus_"
diff --git a/ui/webui/resources/cr_elements/cr_input/cr_input.js b/ui/webui/resources/cr_elements/cr_input/cr_input.js
index 1c455bbb..3019a63 100644
--- a/ui/webui/resources/cr_elements/cr_input/cr_input.js
+++ b/ui/webui/resources/cr_elements/cr_input/cr_input.js
@@ -52,6 +52,11 @@
   is: 'cr-input',
 
   properties: {
+    /** @type {string|undefined} */
+    ariaDescription: {
+      type: String,
+    },
+
     ariaLabel: {
       type: String,
       value: '',